Подводные камни Terraform и рефакторинг с блоком moved

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

Подводные камни Terraform и рефакторинг с блоком moved

Сообщение 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 и карьера
Рано или поздно ты ловишь это ощущение: код красивый, инфраструктура как код работает, ты гордо переименовываешь ресурс с aws_instance... тьфу, с yandex_compute_instance.web на yandex_compute_instance.frontend, запускаешь terraform plan - и видишь страшное. Terraform хочет уничтожить твою боевую виртуалку и создать новую. А на ней база, ssh-ключи, тонна состояния. Поздравляю, ты наступил на самые классические грабли IaC.

В этом уроке разберём подводные камни, на которых спотыкаются все - и новички, и те, кто "уже год пишет на терраформе". А потом возьмём главное лекарство 2026 года - блок moved, который превращает опасный рефакторинг в безопасную рутину.

Грабли первая: значения, известные только после apply

Terraform строит граф зависимостей и хочет знать структуру ресурсов ДО того, как что-то создаст. Поэтому есть жёсткое правило: аргументы count и for_each должны быть вычислимы на этапе plan. Если ты подсунешь туда значение, которое появится только после apply (например, id ещё не созданного ресурса или длину списка, зависящего от вывода другого ресурса), Terraform упрётся и выдаст что-то вроде "The count value depends on resource attributes that cannot be determined until apply".

Пример, как НЕ надо:

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

# Так делать нельзя: count зависит от id, который появится только после apply
resource "yandex_compute_instance" "vm" {
  count = length(yandex_vpc_subnet.this.*.id)  # ловушка
  # ...
}
Лечится это просто: count и for_each должны опираться на статические данные или на variable, а не на ещё не созданные атрибуты. Считай заранее, сколько подсетей будет, и держи это в переменной:

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

variable "subnet_count" {
  type    = number
  default = 3
}

resource "yandex_vpc_subnet" "this" {
  count          = var.subnet_count
  name           = "subnet-${count.index}"
  zone           = "ru-central1-a"
  network_id     = yandex_vpc_network.this.id
  v4_cidr_blocks = ["10.1.${count.index}.0/24"]
}
Здесь var.subnet_count известен заранее, граф собирается, plan не падает.

Изображение

Грабли вторая: "без простоя" - это с оговорками

Многие думают, что Terraform умеет менять ресурсы вообще без даунтайма. Не всегда. Есть атрибуты, которые провайдер помечает как "ForceNew" - их нельзя изменить на лету, поэтому ресурс пересоздаётся: destroy, потом create. По умолчанию сначала старый удаляется, и в этот промежуток сервис лежит.

Частично спасает lifecycle с create_before_destroy - тогда новый ресурс поднимется ДО удаления старого:

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

resource "yandex_compute_instance" "web" {
  name = "web-1"
  # ...
  lifecycle {
    create_before_destroy = true
  }
}
Но и тут есть ограничения. Если у ресурса уникальное имя или он держит уникальный внешний адрес, два экземпляра одновременно могут конфликтовать - имя занято, ip занят. Тогда "без простоя" не выйдет, нужно городить миграцию через балансировщик или blue-green. Вывод простой: create_before_destroy - инструмент, а не магия, и работает он не для каждого ресурса.

Грабли третья: даже идеальный план может упасть

terraform plan показал ровно то, что ты хотел. Ты делаешь apply - и получаешь ошибку. Так бывает, и это нормально. Причины:
  • Гонки (race conditions). Между plan и apply что-то поменялось - кто-то руками удалил ресурс, другой пайплайн дёрнул тот же объект.
  • Лимиты и квоты. В Yandex Cloud есть квоты на число виртуалок, ядра, внешние адреса. План про них не знает, а API при apply скажет "quota exceeded".
  • Eventual consistency. Облачные API не всегда мгновенно согласованы. Ресурс создан, но ещё "не виден" другому вызову - и зависимый ресурс падает с "not found".
Что с этим делать. Во-первых, не паниковать: повторный apply часто проходит, потому что состояние уже устаканилось. Во-вторых, для известных гонок помогает depends_on, чтобы явно выстроить порядок. В-третьих, держи квоты под контролем заранее. Запомни мантру: plan - это прогноз, а не гарантия. Реальность проверяется только на apply.

Главное блюдо: рефакторинг и блок moved

Теперь к той самой боли из вступления. Любое переименование ресурса или перенос его в модуль для Terraform выглядит так: старого адреса в конфиге больше нет (значит, надо удалить), появился новый адрес (значит, надо создать). Итог - destroy + create. Для stateful-ресурсов это катастрофа.

Исторически лечилось это командой terraform state mv. Она работает, но у неё три беды: её надо помнить и запускать руками, она не попадает в git (коллега подтянет твой код и снова получит destroy), и ошибиться в ней легко. Императивщина в мире, который весь про декларативность.

В Terraform 1.1 (а полноценно с 1.3) появился блок moved - и это, на мой взгляд, одна из лучших фич за всю историю. Ты прямо в коде объявляешь: "ресурс переехал с адреса А на адрес Б". Terraform при plan/apply просто обновляет state, НЕ трогая саму инфраструктуру. Никакого пересоздания.

Переименование ресурса выглядит так:

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

# Было: resource "yandex_compute_instance" "web"
# Стало: resource "yandex_compute_instance" "frontend"

resource "yandex_compute_instance" "frontend" {
  name = "web-1"
  # ... те же аргументы
}

moved {
  from = yandex_compute_instance.web
  to   = yandex_compute_instance.frontend
}
Запускаешь terraform plan - и вместо "1 to destroy, 1 to add" видишь что-то вроде "yandex_compute_instance.web has moved to yandex_compute_instance.frontend". Ноль изменений в реальном облаке. Магия, но честная.

Блок moved умеет больше, чем простое переименование:
  • Перенос ресурса внутрь модуля: from = yandex_compute_instance.db, to = module.database.yandex_compute_instance.db.
  • Переезд между индексами count: from = yandex_vpc_subnet.this[0], to = yandex_vpc_subnet.this["a"] (это же и помогает мигрировать с count на for_each).
  • Переименование самого модуля: from = module.old_name, to = module.new_name.
Пример миграции с count на for_each без пересоздания подсетей:

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

resource "yandex_vpc_subnet" "this" {
  for_each       = toset(["a", "b", "c"])
  name           = "subnet-${each.key}"
  zone           = "ru-central1-${each.key}"
  network_id     = yandex_vpc_network.this.id
  v4_cidr_blocks = ["10.1.0.0/24"]
}

moved {
  from = yandex_vpc_subnet.this[0]
  to   = yandex_vpc_subnet.this["a"]
}
moved {
  from = yandex_vpc_subnet.this[1]
  to   = yandex_vpc_subnet.this["b"]
}
moved {
  from = yandex_vpc_subnet.this[2]
  to   = yandex_vpc_subnet.this["c"]
}
Важный нюанс: блок moved живёт в коде и коммитится в git. Поэтому когда коллега подтянет твои изменения, у него рефакторинг применится автоматически и так же безопасно - ему не надо знать никаких state mv. После того как все окружения прокатили переезд, блоки moved можно спокойно удалить из конфига, они одноразовые и больше не нужны.

Пара слов про экосистему 2026. Всё это работает не только в самом Terraform (актуальные ветки 1.x), но и в OpenTofu - открытом форке, который к лету 2026 дошёл до версии 1.12.x. moved там тоже поддерживается, так что если ты сидишь на opentofu, ничего нового учить не надо. А ещё в новых версиях есть императивные команды terraform plan -generate-config и блок import {} для обратной операции - затягивания существующих ресурсов в state декларативно. Но это уже тема отдельного урока.

Мини-лаба: проверь руками

Подними тестовый стенд в Yandex Cloud (сеть, подсеть, одна виртуалка). Затем по шагам:
  • Переименуй ресурс виртуалки (поменяй local name) БЕЗ блока moved и сделай terraform plan. Убедись, что Terraform хочет destroy + create. Не применяй.
  • Добавь блок moved с from старого адреса на to нового. Снова terraform plan - теперь должно быть "moved" и ноль реальных изменений. Сделай apply.
  • Заверни виртуалку в модуль module.compute и пропиши moved на module.compute.yandex_compute_instance... - снова проверь, что пересоздания нет.
  • Бонус: переделай подсеть с count на for_each и пропиши moved по индексам, как в примере выше.
После лабы у тебя в голове должно щёлкнуть: state - это карта, а moved - это всего лишь правка карты, а не перестройка территории.

Контрольные вопросы
  • Почему count и for_each не принимают значения, известные только после apply, и как это обойти?
  • Что реально делает Terraform при переименовании ресурса без блока moved и чем это опасно для stateful-ресурсов?
  • Чем блок moved лучше команды terraform state mv - назови минимум два преимущества.
  • Назови три причины, по которым корректный plan всё равно может упасть на apply.
Итог: что запомнить
  • count и for_each должны быть вычислимы на этапе plan - не вешай на них значения "после apply".
  • Развёртывание без простоя возможно не всегда; create_before_destroy помогает, но не для всех ресурсов.
  • plan - это прогноз. apply может упасть из-за гонок, квот и eventual consistency - повторный прогон часто спасает.
  • Переименование ресурса по умолчанию = destroy + create. Это лечит блок moved.
  • moved переносит ресурс в terraform state без пересоздания, живёт в git и применяется автоматически у всех - забудь про ручной state mv. Работает и в Terraform 1.x, и в OpenTofu 1.12.
👍2 ❤️3 🔥 😄 🤔
Аватара пользователя
torops
Сообщения: 1
Зарегистрирован: 12 май 2026, 12:06

Re: Подводные камни Terraform и рефакторинг с блоком moved

Сообщение torops »

Вот это прям моя боль, на прошлой неделе снёс боевую тачку при переименовании, хорошо что снапшот был. Теперь буду всегда через moved, спасибо!
👍2 ❤️1 🔥 😄 🤔1
Аватара пользователя
uheinz
Сообщения: 1
Зарегистрирован: 29 май 2026, 11:16

Re: Подводные камни Terraform и рефакторинг с блоком moved

Сообщение uheinz »

А подскажите, блоки moved обязательно потом удалять или можно оставить в коде навсегда? Боюсь забыть и потом мусор копится.
👍1 ❤️1 🔥 😄 🤔1
Ответить
← Предыдущая глава
Развёртывание без простоя: create_before_destroy
Следующая глава →
Управление секретами: основы и типы

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

Поделиться темой: ✈ Telegram VK
Похожие запросы: terraform yandex cloud с чего начать новичкукак установить terraform и opentofu на linuxчто такое terraform state и tfstate простыми словамиудаленный backend terraform на object storage yandex cloudчто такое инфраструктура как код iac простыми словамиterraform plan и apply что делают и чем отличаются

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

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

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