Зачем moved
До TF 1.1 переименование ресурса = terraform state mv руками. Это
работает, но:
- Только один человек запустил, у остальных state'ы разъехались
(если local backend) или они должны быстро
pull. - В git нет следа: HCL поменялся, а почему не упал в destroy+create, знает только тот, кто crash'нул терминал командой.
- В PR-ревью невозможно увидеть «это рефакторинг» vs «это новый ресурс».
С TF 1.1, есть moved блок. Декларативный, в HCL, видно в diff, plan
показывает как move явно.
Минимальный пример: переименование
Было:
resource "aws_s3_bucket" "logs" {bucket = "linuxlab-logs"
}
Хочешь:
resource "aws_s3_bucket" "log_storage" {bucket = "linuxlab-logs"
}
moved {from = aws_s3_bucket.logs
to = aws_s3_bucket.log_storage
}
Запускаешь plan:
# aws_s3_bucket.logs has moved to aws_s3_bucket.log_storage
bucket = "linuxlab-logs"
...
Plan: 0 to add, 0 to change, 0 to destroy.
Apply, ничего в облаке не меняется. State обновлён.
Без moved блока plan показал бы:
- aws_s3_bucket.logs will be destroyed
+ aws_s3_bucket.log_storage will be created
Это destroy+create, потеря данных. Moved предотвращает.
Кейсы
Переименование
Самый частый. Описан выше. После апплая moved блок можно удалить
(но многие команды оставляют в HCL как документ, «когда-то это
переименовали»).
Перенос в модуль
Был ресурс в root, вынес в модуль:
# root
module "buckets" {source = "./modules/buckets"
}
moved {from = aws_s3_bucket.logs
to = module.buckets.aws_s3_bucket.logs
}
Внутри модуля:
# modules/buckets/main.tf
resource "aws_s3_bucket" "logs" {bucket = "linuxlab-logs"
}
terraform plan → 0 to add, 0 to change, 0 to destroy. Ресурс
переместился в state из root-уровня в module.buckets.
Из модуля наружу
Обратное: разбираешь модуль, ресурс возвращается в root:
moved {from = module.buckets.aws_s3_bucket.logs
to = aws_s3_bucket.logs
}
count → for_each
Один из самых полезных кейсов. Раньше:
resource "aws_s3_bucket" "logs" {count = 3
bucket = "linuxlab-logs-${count.index}"}
Адреса в state: aws_s3_bucket.logs[0], [1], [2].
Хочешь for_each для стабильности ключей:
variable "log_levels" {type = set(string)
default = ["debug", "info", "error"]
}
resource "aws_s3_bucket" "logs" {for_each = var.log_levels
bucket = "linuxlab-logs-${each.key}"}
moved {from = aws_s3_bucket.logs[0]
to = aws_s3_bucket.logs["debug"]
}
moved {from = aws_s3_bucket.logs[1]
to = aws_s3_bucket.logs["info"]
}
moved {from = aws_s3_bucket.logs[2]
to = aws_s3_bucket.logs["error"]
}
Важно: имена бакетов меняются! linuxlab-logs-0 → linuxlab-logs-debug.
Если бакеты уже созданы с числовыми именами. Нужно либо оставить старые
имена в HCL, либо принять bucket = изменение (которое для S3 = destroy+create,
потому что имя, immutable).
Чаще count → for_each делают до того как ресурсы созданы.
Если уже созданы, оставь имена в HCL такими как в облаке.
Что moved умеет, что нет
| Может | Не может |
|---|---|
| Переименовать ресурс в state | Изменить тип ресурса (aws_s3_bucket → aws_s3_bucket_v2) |
| Переместить в модуль / из модуля | Сменить provider |
| Переключить count ↔ for_each | Изменить provider configuration |
| Перенести между разными root'ами | Объединить несколько ресурсов в один |
Для смены типа, destroy+create или import. Для смены provider, обычно тоже destroy+create, потому что под капотом это другой API.
С чем сочетается
- С
removedблоком (tf-removed-block).movedпереносит,removedзабирает из state. Часто рядом в одном PR. - С
importблоком (tf-state-import): иногда меняешь HCL и одновременно захватываешь существующий ресурс. - С
lifecycle.ignore_changes, moved не отменяет ignore_changes, они продолжают применяться после переноса.
Подводные камни
-
moved не удаляется автоматически. После apply блок остаётся в HCL. Можно удалить вручную; некоторые команды оставляют в архивных файлах (
refactoring.tf) для аудита. Безопасно удалять через 1-2 спринта после miграции. -
moved не переносит данные. Это state-only операция. Если у тебя
aws_s3_bucket.logsсо 100GB данных, и ты делаешьmoved, данные в том же бакете. Меняется только адрес в state. -
Цепочки moved работают.
A → B,B → Cв одном HCL. Terraform разрулит. Но это плохой стиль: при возможности, один прямойA → C. -
moved между провайдерами не работает.
aws.usиaws.eu, разные provider instances. Move между ними = destroy+create (бакет в одном регионе не «переезжает» в другой). -
moved не работает с разными типами.
aws_s3_bucket.x → aws_s3_bucket_acl.x, нет. Тип ресурса должен совпадать в from и to. -
moved блок проверяется при init. Опечатка в имени ресурса,
terraform planупадёт ещё до plan'а. -
Идеален для PR-ревью. Видно в diff: «вот ресурс был тут, теперь тут, это рефакторинг, не destroy». Объясняет интент. Поэтому предпочтительнее ручного
state mv.