Развёртывание без простоя: create_before_destroy

Рейтинг: 66.7% · 13 голосов
Подробный курс по Terraform и OpenTofu на примерах Yandex Cloud: ресурсы и провайдеры, состояние и бэкенды, модули, циклы и условия HCL, секреты и Lockbox, тестирование, CI/CD и командная работа. С актуализацией на 2026 (Terraform 1.3-1.10, OpenTofu).
Ответить
Аватара пользователя
Vlad_DevOps
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Развёртывание без простоя: create_before_destroy

Сообщение Vlad_DevOps »

Оглавление курса (47)
  1. Что такое инфраструктура как код и зачем она нужна
  2. Категории инструментов IaC: чем отличаются Ansible, Packer, Kubernetes и Terraform
  3. Чем Terraform отличается от Ansible, Pulumi и CloudFormation
  4. Как работает Terraform под капотом и кто такой OpenTofu
  5. Установка Terraform и OpenTofu, подготовка Yandex Cloud
  6. Первый ресурс: разворачиваем виртуальную машину в Yandex Cloud
  7. Веб-сервер на ВМ: метаданные, user-data и группы безопасности
  8. Переменные ввода и выходные значения в Terraform
  9. Кластер ВМ: группа экземпляров Yandex Compute Instance Group
  10. Балансировщик нагрузки: Application Load Balancer в Yandex Cloud
  11. Состояние Terraform: что такое tfstate и чем опасен локальный файл
  12. Удалённое хранилище состояния: бэкенд на Yandex Object Storage
  13. Изоляция состояния: рабочие области против раскладки по папкам
  14. Источники данных и terraform_remote_state: связываем компоненты
  15. Модули Terraform: переиспользуем инфраструктуру
  16. Входные, локальные и выходные переменные модуля
  17. Версионирование модулей и подводные камни
  18. Циклы в Terraform: параметр count
  19. Циклы в Terraform: for_each по множествам и картам
  20. Выражения for и строковые директивы
  21. Условная логика: count-трюк, for_each и директива if
  22. Встроенные функции, типы и выражения HCL
  23. Развёртывание без простоя: create_before_destroy (вы здесь)
  24. Подводные камни Terraform и рефакторинг с блоком moved
  25. Управление секретами: основы и типы
  26. Инструменты управления секретами: Yandex Lockbox, Vault, SOPS
  27. Аутентификация провайдера и секреты: сервисные аккаунты и OIDC
  28. Секреты в ресурсах и состоянии, ephemeral-значения
  29. Провайдеры Terraform: установка, версии, required_providers
  30. Несколько копий одного провайдера: alias и мультизона
  31. Несколько провайдеров и аккаунтов: configuration_aliases
  32. Terraform, Docker и Kubernetes: Managed Kubernetes в Yandex Cloud
  33. Код Terraform промышленного уровня: почему это долго
  34. Мелкие компонуемые модули вместо монолита
  35. Тестируемые модули и проверки: validation, precondition, postcondition
  36. Версионирование, публикация модулей и Terragrunt
  37. Ручное тестирование Terraform и очистка ресурсов
  38. Автоматические тесты на Terratest: модульные тесты
  39. Интеграционные и сквозные тесты, пирамида тестирования
  40. Нативный terraform test и статический анализ
  41. Внедрение Terraform в команде: процессы и культура
  42. Конвейер развёртывания прикладного кода
  43. Конвейер инфраструктурного кода и золотое правило Terraform
  44. Стиль, ревью и CI/CD-инструменты: Atlantis, TACOS, policy as code
  45. Что нового в Terraform 1.3-1.10: моды, импорт, провайдер-функции, Stacks
  46. OpenTofu в 2026: чем живёт форк и как мигрировать
  47. Сертификация HashiCorp Terraform Associate и карьера
Представь картину. Ты поменял образ диска в продакшене, запустил terraform apply, налил себе кофе. Возвращаешься - а мониторинг красный, в чате паника, балансировщик отдаёт 502. Что произошло? Terraform честно сделал то, что ты попросил: снёс старый ресурс и начал поднимать новый. Вот только между "снёс" и "поднял" прошло полторы минуты, и всё это время твой сервис был мёртв.

Это и есть боль, которую решает сегодняшний урок. Дефолтное поведение Terraform при замене ресурса - destroy, потом create. Логично для диска в шкафу, катастрофично для живого трафика. К счастью, в инфраструктуре как код (а это и есть iac, ради которого мы тут собрались) есть аккуратный набор инструментов, чтобы менять ресурсы без секунды простоя. Разберём их по порядку и доведём до полноценной zero-downtime стратегии для группы виртуалок в Yandex Cloud.

Почему apply вообще что-то ломает

Сначала про механику. Когда ты меняешь атрибут, который провайдер помечает как ForceNew (например image_id у диска), Terraform не может изменить ресурс на месте. Он обязан его пересоздать. В terraform plan ты увидишь страшное:

Код: Выделить всё

-/+ resource "yandex_compute_disk" "data" {
      ~ image_id = "fd8..." -> "fd9..." # forces replacement
    }
Этот символ -/+ читается как "сначала минус (удалить), потом плюс (создать)". Дефолтный порядок - destroy-before-create. Вот ровно тут и рвётся трафик: старого ресурса уже нет, нового ещё нет.

Управляет порядком и защитой специальный мета-блок lifecycle. Он не относится к конкретному провайдеру - это часть ядра Terraform, и работает одинаково в terraform и opentofu. Внутри живут три ключевые ручки: create_before_destroy, prevent_destroy и ignore_changes.

Изображение

create_before_destroy: поменять местами создание и удаление

Идея простая до гениальности. Разверни порядок: сначала подними новый ресурс, дождись, что он готов, переключи на него ссылки, и только потом снеси старый. Один флаг:

Код: Выделить всё

resource "yandex_compute_instance" "app" {
  name        = "app-vm"
  platform_id = "standard-v3"

  resources {
    cores  = 2
    memory = 4
  }

  boot_disk {
    initialize_params {
      image_id = var.image_id
      size     = 20
    }
  }

  network_interface {
    subnet_id = yandex_vpc_subnet.app.id
    nat       = true
  }

  lifecycle {
    create_before_destroy = true
  }
}
Теперь при смене image_id Terraform сначала создаст вторую виртуалку с новым образом, и только убедившись, что она поднялась, прибьёт старую. В plan порядок сменится на +/- ("сначала плюс").

Подвох, о который спотыкаются все: ресурсы с уникальными именами. Если у тебя name = "app-vm" жёстко прописан, то новый инстанс не создастся - имя занято старым, который ещё жив. Решение - давать имена с суффиксом, который меняется вместе с образом, либо отдавать генерацию имени самому облаку. На одиночной виртуалке create_before_destroy полезен, но настоящий zero-downtime начинается, когда за дело берётся группа экземпляров и балансировщик. К этому и идём.

prevent_destroy и ignore_changes: предохранители

Пока мы тут, закроем две соседние ручки lifecycle - они спасают нервы.

prevent_destroy = true превращает ресурс в неуязвимый: любой план, который пытается его удалить, падает с ошибкой ещё до apply. Ставь на то, что больно терять - продакшен-базу, бакет с бэкапами, managed-кластер.

Код: Выделить всё

resource "yandex_mdb_postgresql_cluster" "main" {
  name = "prod-pg"
  # ... остальная конфигурация ...

  lifecycle {
    prevent_destroy = true
  }
}
Маленькая, но важная заметка на 2026 год: исторически prevent_destroy принимал только литерал true или false, переменную туда было не подставить. В OpenTofu 1.12 (вышел в мае 2026) завезли динамический prevent_destroy - теперь можно писать prevent_destroy = var.is_prod. В обычном Terraform по состоянию на середину 2026 (ветка 1.x) это всё ещё статичный литерал, так что если хочешь переключать защиту переменной - это аргумент в пользу OpenTofu.

ignore_changes - про мир, а не про порядок. Бывает, что какой-то атрибут крутит снаружи кто-то ещё: автоскейлер меняет размер группы, оператор правит метку руками, облако само дописывает поле. Terraform на каждом plan будет упорно возвращать "как в коде". Скажи ему игнорировать конкретные поля:

Код: Выделить всё

resource "yandex_compute_instance" "app" {
  # ...
  lifecycle {
    ignore_changes = [
      metadata,
      boot_disk[0].initialize_params[0].image_id,
    ]
  }
}
Можно и ignore_changes = all, но это уже из разряда "выключить пожарную сигнализацию, чтобы не пищала". Применяй точечно.

Zero-downtime для группы: deploy_policy и health checks

А вот теперь главное блюдо. Для реального продакшена одну виртуалку обычно не держат - берут yandex_compute_instance_group, группу одинаковых машин за балансировщиком. И самое приятное: для плавной выкатки новой версии тут create_before_destroy уже почти не нужен. Группа умеет катить обновление сама, а правила задаёт блок deploy_policy.

Когда ты меняешь instance_template (например тот самый image_id), группа не сносит все машины разом. Она идёт по политике развёртывания. Ключевые поля:
  • max_unavailable - сколько работающих инстансов можно одновременно вывести из строя во время обновления. Поставь 0 (или близко к нулю), если простой недопустим вообще.
  • max_expansion - сколько машин можно временно поднять сверх целевого размера группы. Это и есть аналог create_before_destroy на уровне группы: сначала создаём лишние, потом гасим старые.
  • max_creating и max_deleting - потолки на одновременное создание и удаление, чтобы не перегружать квоты.
  • startup_duration - сколько секунд дать машине на старт; пока не прошло это время и не прошли health-чеки, трафик на неё не пускают.
  • strategy - proactive (по умолчанию, группа может сама принудительно гасить инстанс) или opportunistic (ждёт, пока инстанс освободится сам).
Связка с балансировщиком - через блок health_check и target group. Группа сама регистрирует новые машины в целевой группе балансировщика и не отдаёт на них трафик, пока health check не позеленеет. Вот рабочий каркас:

Код: Выделить всё

resource "yandex_compute_instance_group" "app" {
  name               = "app-ig"
  folder_id          = var.folder_id
  service_account_id = yandex_iam_service_account.ig.id

  instance_template {
    platform_id = "standard-v3"
    resources {
      cores  = 2
      memory = 4
    }
    boot_disk {
      mode = "READ_WRITE"
      initialize_params {
        image_id = var.image_id
        size     = 20
      }
    }
    network_interface {
      network_id = yandex_vpc_network.app.id
      subnet_ids = [yandex_vpc_subnet.app.id]
    }
  }

  scale_policy {
    fixed_scale {
      size = 4
    }
  }

  allocation_policy {
    zones = ["ru-central1-a"]
  }

  deploy_policy {
    max_unavailable = 0
    max_expansion   = 2
    max_creating    = 2
    max_deleting    = 2
    startup_duration = 30
    strategy         = "proactive"
  }

  health_check {
    interval            = 5
    timeout             = 2
    healthy_threshold   = 2
    unhealthy_threshold = 3
    http_options {
      port = 80
      path = "/health"
    }
  }

  load_balancer {
    target_group_name = "app-tg"
  }
}
Что тут происходит при terraform apply после смены image_id: max_unavailable = 0 запрещает гасить живые машины, max_expansion = 2 разрешает поднять две новых поверх. Группа создаёт пару свежих инстансов с новым образом, ждёт startup_duration и зелёный health_check, регистрирует их в target group балансировщика, переливает на них трафик - и только теперь гасит две старых. Повторяет, пока не обновит все четыре. Снаружи - ноль пятисоток. Вот это и есть честный zero-downtime, собранный из реальных атрибутов провайдера, а не из обещаний.

Ещё две полезные ручки рядом: max_checking_health_duration на самой группе (таймаут ожидания, пока VM станет healthy, иначе её прибьют по политике) и внутри load_balancer - max_opening_traffic_duration (сколько ждать, пока балансировщик проверит машину).

Типичные грабли
  • Жёсткое name и create_before_destroy на одиночной VM. Имя занято старым ресурсом - новый не создаётся. Либо суффикс в имени, либо отдай нейминг группе.
  • Цепная реакция замены. Флаг create_before_destroy "заразен": если ресурс с ним зависит от другого, тот тоже начнёт создаваться раньше удаления. Иногда это неожиданно перестраивает половину графа. Читай plan внимательно.
  • max_unavailable не равен нулю там, где нужен ноль. Дефолты могут позволить гасить машины раньше времени. Для строгого zero-downtime ставь явно 0 и давай группе место через max_expansion.
  • Нет /health эндпоинта. Health check бьёт в путь, которого нет, машина вечно unhealthy, выкатка стоит колом или машины гасятся как битые. Сначала ручка здоровья в приложении, потом health_check.
  • prevent_destroy мешает осознанному пересозданию. Когда ресурс реально надо заменить, флаг придётся снять в коде - terraform destroy его не обойдёт. Это не баг, это смысл.
Мини-лаба
  • Подними одиночный yandex_compute_instance без lifecycle. Поменяй image_id, посмотри plan - убедись, что порядок -/+ (destroy-before-create).
  • Добавь lifecycle { create_before_destroy = true }. Снова смени image_id, сравни plan - порядок должен стать +/-.
  • Навесь prevent_destroy = true на этот инстанс и попробуй terraform destroy. Полюбуйся на ошибку, потом убери флаг.
  • Собери yandex_compute_instance_group с deploy_policy (max_unavailable = 0, max_expansion = 2) и health_check. Поменяй образ, запусти apply и параллельно дёргай сервис curl-ом в цикле - убедись, что 5xx не прилетает.
Контрольные вопросы
  • Какой порядок операций по умолчанию у Terraform при замене ресурса и почему он опасен для живого трафика?
  • Зачем нужен max_expansion в deploy_policy и как он связан с идеей create_before_destroy?
  • Чем отличаются prevent_destroy и ignore_changes - что каждый из них защищает?
  • Что произойдёт при выкатке, если у приложения нет рабочего health-эндпоинта, на который смотрит health_check?
Итог: что запомнить

Дефолт Terraform - удалить, потом создать, и это рвёт трафик. Флаг lifecycle { create_before_destroy = true } разворачивает порядок для одиночных ресурсов. prevent_destroy - предохранитель от случайного удаления критичного (в OpenTofu 1.12 его наконец можно задавать переменной), ignore_changes - чтобы Terraform не воевал с тем, что меняют снаружи. Но настоящий zero-downtime в Yandex Cloud живёт в yandex_compute_instance_group: deploy_policy с max_unavailable = 0 и max_expansion, плюс health_check и связка с балансировщиком через target group дают плавную выкатку без единой секунды простоя. Меняй образ смело - инфраструктура как код больше не значит "сломать продакшен одной командой".
👍1 ❤️2 🔥1 😄 🤔
Аватара пользователя
mamouse
Сообщения: 1
Зарегистрирован: 14 май 2026, 15:15

Re: Развёртывание без простоя: create_before_destroy

Сообщение mamouse »

Спасибо, наконец дошло зачем max_expansion. Я раньше ставил max_unavailable побольше чтобы быстрее катилось и удивлялся почему пятисотки лезут. Теперь поставил 0 и расширение 2, тишина.
👍2 ❤️1 🔥 😄 🤔
Аватара пользователя
wasmaddict
Сообщения: 1
Зарегистрирован: 19 май 2026, 20:48

Re: Развёртывание без простоя: create_before_destroy

Сообщение wasmaddict »

А если health_check у меня tcp а не http, путь /health не нужен ведь? У меня сервис просто порт слушает, hello-world без ручки здоровья пока.
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Встроенные функции, типы и выражения HCL
Следующая глава →
Подводные камни Terraform и рефакторинг с блоком moved

Все главы курса «Terraform: инфраструктура как код на Yandex Cloud»

Поделиться темой: ✈ Telegram VK
Похожие запросы: terraform destroy снес прод как восстановить

Вернуться в «Terraform: инфраструктура как код на Yandex Cloud»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость