Зачем нужен lifecycle
По умолчанию Terraform: «если в HCL стало по-другому, изменить ресурс. Если убрал из HCL, удалить ресурс». Это работает в 90% случаев, но иногда поведение надо подкрутить.
lifecycle, вложенный блок внутри resource, четыре опции для тонкой настройки.
prevent_destroy = true, защита от случайностей
Самая ценная опция для продакшена:
resource "aws_db_instance" "main" {identifier = "prod-db"
# ... Параметры ...
lifecycle {prevent_destroy = true
}
}
Теперь любая попытка удалить этот ресурс через apply или destroy упадёт с ошибкой:
Error: Instance cannot be destroyed
Resource aws_db_instance.main has lifecycle.prevent_destroy set, but the
plan calls for this resource to be destroyed.
Чтобы реально снести, придётся убрать prevent_destroy = true, сделать apply, и только потом снова попробовать destroy. Двухшаговая защита. Нужно осознанно сначала отключить флаг.
Ставьте на: prod-БД, S3-бакеты с данными, KMS-ключи, любые ресурсы, потеря которых катастрофа.
create_before_destroy = true, zero-downtime
По умолчанию при пересоздании ресурса (-/+) Terraform делает так:
- Удалить старый.
- Создать новый.
Между шагами 1 и 2, даунтайм. Для критичных ресурсов это плохо.
resource "aws_launch_template" "web" {name_prefix = "web-"
instance_type = "t3.micro"
lifecycle {create_before_destroy = true
}
}
Теперь Terraform:
- Создаст новый.
- Переключит зависимости на новый.
- Удалит старый.
Нюансы:
- Должно быть уникальное имя. Нельзя
name = "web", будет конфликт (старый ещё не удалён, новый с тем же именем не создаётся). Используйтеname_prefixили включайте суффикс в имя. - У некоторых ресурсов есть ограничения. AWS RDS, нельзя два экземпляра с одинаковым identifier.
ignore_changes = [...], игнор drift
Иногда атрибут меняется снаружи Terraform, и нам всё равно. Тег last_modified от внешнего скрипта. Auto-scaling меняет desired_capacity. Без ignore_changes каждый plan показывал бы изменение и предлагал «откатить».
resource "aws_autoscaling_group" "web" {name = "web-asg"
min_size = 2
max_size = 10
desired_capacity = 2
lifecycle {# ASG-controller меняет desired_capacity автоматически, не трогаем.
ignore_changes = [desired_capacity]
}
}
Можно игнорировать всё:
lifecycle {ignore_changes = all
}
Но это редко правильно, превращает ресурс в «создано один раз, дальше не управляем».
replace_triggered_by = [...], пересоздать по сигналу
Иногда надо пересоздать ресурс когда меняется другой ресурс. Например, EC2-инстанс должен пересоздаваться при изменении user_data, даже если AWS-провайдер не считает это причиной для replace.
resource "null_resource" "config_version" { triggers = { config_hash = filemd5("config.yaml")}
}
resource "aws_instance" "web" {ami = "ami-..."
instance_type = "t3.micro"
lifecycle {replace_triggered_by = [null_resource.config_version]
}
}
Если файл config.yaml поменялся, null_resource.config_version пересоздастся (новый triggers): и aws_instance.web тоже пересоздастся.
Подводные камни
-
prevent_destroyне защищает отterraform state rm. Если кто-то руками удалит ресурс из state, Terraform его больше не «видит», иprevent_destroyне сработает. Защищает только от изменений через apply/destroy. -
create_before_destroyтребует уникальности. Имя, identifier, любой атрибут с unique-constraint, должен быть динамическим. Иначе будет конфликт. -
ignore_changesне работает на add/remove. Если вы добавите тег в HCL который раньше не существовал,ignore_changes = [tags]не помешает Terraform его создать. Игнор работает только когда облако и HCL расходятся, а не когда вы явно меняете HCL. -
lifecycle нельзя в data-блоке. Это конструкция только для resource.
-
lifecycle блок один на ресурс. Несколько lifecycle-блоков в одном resource, ошибка.
-
Не использовать
ignore_changesчтобы скрыть проблему. Если plan показывает странный drift, сначала разберитесь, почему атрибут меняется снаружи. Лечите причину, не симптом.