Как переименовать ресурс Terraform или OpenTofu без пересоздания: moved

В модуле ресурс назван server, хотя давно представляет worker. Переименование кажется косметическим, но после правки plan показывает удаление старого адреса и создание нового. Инструмент связывает объект с конфигурацией через адрес в state, а не через сходство полей. Поэтому нужно записать, что существующий объект теперь принадлежит другому адресу.

Для этого служит блок moved. Он полезен при изменении имени ресурса, перемещении в модуль и переходе к адресам с count или for_each. Перенос входит в проверяемую конфигурацию и становится виден в plan до изменения state.

Минимальный пример без облака

Возьмём встроенный terraform_data, доступный в современных Terraform и OpenTofu. Он хранит входное значение, не создавая сервер или диск. Для Terraform этому примеру нужна версия 1.4 или новее из-за самого типа terraform_data; блок moved появился раньше, в 1.1. В OpenTofu используйте установленную поддерживаемую версию.

Сначала в пустом учебном каталоге создайте main.tf со старым именем:

resource "terraform_data" "server" {
  input = "worker-a"
}

output "worker_id" {
  value = terraform_data.server.id
}

Выберите один инструмент. Выполните terraform init, terraform plan и затем terraform apply для этого локального учебного состояния. Для OpenTofu используйте tofu init, tofu plan и tofu apply. После подтверждения создания запишите worker_id. Эти apply нужны только для воспроизведения примера в новом каталоге; не подставляйте команды в каталог рабочей инфраструктуры.

Изменим адрес с явной связью

Замените main.tf целиком следующим содержимым. Значение input остаётся прежним, ссылка output обновлена, а moved содержит старый и новый адреса без строковых кавычек вокруг выражения.

resource "terraform_data" "worker" {
  input = "worker-a"
}

moved {
  from = terraform_data.server
  to   = terraform_data.worker
}

output "worker_id" {
  value = terraform_data.worker.id
}
terraform plan -out=rename.tfplan
terraform show -no-color rename.tfplan
tofu plan -out=rename.tfplan
tofu show -no-color rename.tfplan

Выполняйте только свой набор команд. В плане ожидается сообщение о переносе server в worker, без создания и удаления этого ресурса. ID должен остаться прежним. После проверки применение сохранённого плана обновит привязку в state. Команда state list затем должна показывать terraform_data.worker вместо terraform_data.server. Здесь plan сам по себе ещё не сохраняет перенос в рабочий state.

Почему plan всё ещё может предлагать замену

Moved отвечает за адрес. Если одновременно изменить свойства облачного ресурса, провайдер может потребовать замену уже по другой причине. Поэтому сначала проверяйте чистое переименование, затем отдельным изменением меняйте параметры. При переносе в модуль сохраните те же входы, выбор provider alias и настройки провайдера; новая структура кода не должна случайно направить объект в другой регион.

Сопоставьте from с фактическим адресом из state list. У ресурса с for_each адрес включает ключ, а у count — индекс. Перенос одиночного ресурса в worker["primary"] должен явно указать, какой экземпляр сохраняется. Если переименовали один ключ из нескольких, переносите этот ключ, а не угадывайте соответствие по порядку элементов.

Адреса в moved разрешаются относительно модуля, где записан блок. Для изменения имени ресурса внутри переиспользуемого модуля обычно логично хранить перенос рядом с этим ресурсом. Для перемещения между дочерними модулями понадобится общий контекст, который может назвать обе стороны. Синтаксис и ограничения следует сверять для Terraform и OpenTofu.

История нужна тем, кто обновляется позже

После успешного запуска в одном окружении легко удалить moved как отработавшую миграцию. Но другое окружение ещё может хранить старый адрес. Если оно обновится сразу до версии без переноса, инструмент снова увидит старый объект и новое независимое объявление. Для общего модуля сохраняйте историю переносов, пока поддерживается обновление с прежних версий.

При втором переименовании добавьте переход от промежуточного адреса к следующему, оставив прежний. Цепочка позволяет обработать состояния, которые остановились на разных версиях. Это особенно полезно для модулей, которые потребители обновляют независимо.

Команда state mv решает похожую задачу вручную и сразу меняет состояние. Для согласованного рефакторинга декларативный moved удобен тем, что diff проходит ревью вместе с кодом и результат заранее виден в плане. Ручную операцию стоит согласовывать с остальными писателями state, иначе параллельный запуск может увидеть промежуточное состояние.

Завершённое переименование проверяется по трём связанным фактам: plan распознал перенос, state после применения содержит новый адрес, а идентификатор нужного объекта сохранился. Если появился destroy/create, не применяйте его как косметическую правку: вернитесь к адресам и отличиям свойств.

Ещё по теме

Продолжить изучение

Хотите управлять инфраструктурой через код и понимать последствия изменений? Plan, state, модули и работа с существующими ресурсами — темы программы «Terraform и OpenTofu: инфраструктура как код».

Продолжить на практике

  • Terraform и OpenTofu: инфраструктура как код ↗

    Описывайте инфраструктуру в коде, проверяйте изменения до применения и работайте со state без сюрпризов. HCL, модули, remote state, импорт, CI и различия Terraform/OpenTofu — на воспроизводимом учебном окружении.