ADG Оставить заявку
Блог DevOps 4 мин чтения

Terraform 1.1 и блок moved: рефакторинг модулей без боли со state

Terraform 1.1 GA: блок moved {} позволяет переименовывать и перемещать ресурсы без потери state. Показываем паттерн на реальном кейсе реструктуризации модуля.

Контекст момента

Terraform 1.1 GA (декабрь 2021): блок moved {} для безопасного рефакторинга state без потери ресурсов

Terraform 1.1 вышел в декабре 2021, и главная новинка релиза - блок moved {}. На первый взгляд скромная фича. На второй - именно то, чего не хватало последние несколько лет.

В чём была проблема

Рефакторинг Terraform-модулей всегда был неудобным местом. Переименовать ресурс, вынести его в подмодуль, перейти от одного ресурса к for_each - всё это физически несложно, но сопряжено с ручной работой со state. Стандартный рецепт: terraform state mv old_name new_name, и молиться чтобы не ошибиться в адресе. На проекте с несколькими окружениями и разными workspace это превращается в церемонию: сделать mv в dev, убедиться что plan чистый, повторить для staging, повторить для prod. Забыл одно окружение - plan показывает destroy/create там, где ты ожидал no-op.

Ещё веселее, когда модуль используется несколькими клиентами. Нельзя просто переименовать ресурс внутри модуля - у каждого клиента свой state, и надо либо трогать state у всех, либо жить с неудобным именованием вечно.

В итоге Terraform-модули у многих деградируют в «не трогать без крайней необходимости». Страх что-то сломать при рефакторинге приводит к тому, что плохие решения фиксируются навсегда.

Что делает moved

Блок moved {} позволяет объявить в коде, что ресурс переехал с одного адреса на другой. Terraform считывает это при plan и обновляет state автоматически - без ручных команд, без скриптов, без церемоний.

moved {
  from = aws_security_group.app
  to   = aws_security_group.application
}

После этого terraform plan покажет:

# aws_security_group.app has moved to aws_security_group.application

Никаких destroy, никаких create. State обновился, в облаке ничего не изменилось.

Блоки moved работают и для for_each. Вот кейс, с которым мы столкнулись на прошлой неделе.

Реальный кейс: модуль для нескольких клиентов

У нас есть модуль, который создаёт типовую инфраструктуру приложения: security groups, IAM role, несколько S3 bucket-ов. Модуль появился под конкретного клиента, потом начал использоваться на других проектах. И где-то по дороге именование ресурсов в нём стало... скажем так, историческим. Ресурс aws_s3_bucket.data в контексте одного клиента означал одно, в контексте другого - другое, и добавление третьего клиента с другим набором bucket-ов потребовало переработки.

Хотели перейти от единственного ресурса к for_each:

# Было
resource "aws_s3_bucket" "data" {
  bucket = "${var.project}-data"
}

# Стало
resource "aws_s3_bucket" "buckets" {
  for_each = var.buckets
  bucket   = "${var.project}-${each.key}"
}

Раньше это означало: у каждого клиента нужно вручную сделать terraform state mv, потом проверить plan. Сейчас добавляем в модуль:

moved {
  from = aws_s3_bucket.data
  to   = aws_s3_bucket.buckets["data"]
}

Один блок в коде модуля - и при следующем terraform plan у всех клиентов, которые используют этот модуль, state обновится автоматически. Bucket не пересоздаётся, данные не трогаются, plan показывает только реальные изменения конфигурации.

Несколько практических наблюдений

Блоки moved накапливаются. Это не баг, а особенность - они остаются в коде как документация истории рефакторинга. Если это мешает, HashiCorp рекомендует удалять их после того, как все окружения прошли apply. Мы пока оставляем - для модуля, у которого несколько потребителей, иметь историю переездов полезно.

Ошибки в адресе ловятся при plan. Если написать неправильный from, Terraform скажет об этом явно, а не тихо создаст дубликат. Это лучше чем ситуация с ручным state mv, где опечатка в адресе обнаруживалась уже после того как что-то пошло не так.

С workspace работает правильно. Каждый workspace имеет свой state, и moved применяется к каждому из них независимо при следующем terraform plan/apply в этом workspace. Не нужно помнить пробегать по всем workspace вручную.

Для модулей в registry - отдельная история. Если модуль опубликован и версионирован, блок moved попадает в следующую версию модуля. Потребители модуля получают безопасный рефакторинг просто обновив версию. Это меняет то, как можно эволюционировать публичные модули - без breaking change для пользователей.

Где это нас не спасает

Честно: moved не решает всё. Перенести ресурс между разными провайдерами - нельзя. Перенести ресурс из одного Terraform-проекта в другой (другой backend) - тоже нельзя. Это по-прежнему ручная работа с state pull/state push или state mv между бэкендами.

Для managed-сопровождения это всё равно большой шаг вперёд. Основная боль была именно в рефакторинге внутри одного проекта с несколькими окружениями - и она закрыта.

Где сейчас

Пока применили к одному модулю в качестве пилота. Plan у всех клиентов прошёл чисто, apply тоже. Следующий шаг - пересмотреть ещё несколько модулей, в которых именование ресурсов давно раздражало, но трогать не хотелось. Посмотрим насколько это ускорит рефакторинг, который откладывался месяцами.

Terraform 1.0 дал гарантию стабильности. 1.1 начинает закрывать реальные операционные неудобства, которые копились годами. Это приятно.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.