terraform state mv を打ち間違えたら取り返しがつかない」Terraform で本番インフラを管理していると、そんな不安を抱える場面は必ずあります。
CLI コマンドによる state 操作は強力ですが、誰がいつ実行したかの履歴が残らず、チームで共有もできません。
Terraform 1.1 以降で導入された moved ブロック、そして 1.7 以降の removed ブロックを使えば、リソースの移動や state 管理除外を HCL コードとして宣言できます。変更内容を
terraform plan で事前確認し、Pull Request でレビューして、チームに共有しながら安全にリファクタリングできる。これが宣言型アプローチの本質です。この記事では、moved ブロック・removed ブロックの構文から実務リファクタリングパターン、よくあるエラーの対処まで体系的に解説します。
この記事のポイント
・moved ブロックは state mv の宣言型代替で VCS 管理・PR レビューが可能
・removed ブロックの destroy=false でインフラを残したまま state 除外できる
・plan 実行時に「Moved」表示で事前確認できるため誤操作リスクがほぼゼロ
・Terraform 1.1 以降で moved、1.7 以降で removed が利用可能
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
movedブロックとは何か:state mvとの本質的な違い
Terraform でリソース名を変更したり、モジュール間で移動したりするとき、従来はterraform state mv コマンドを使っていました。しかし CLI 操作には次の問題があります。・実行ログが state ファイルに残らない(誰がいつ変更したか追跡困難)
・コマンドを誰かが個別に実行する必要がある(チーム運用でアドホックになりやすい)
・ミスった場合のロールバックが難しい
moved ブロックは Terraform 1.1 で導入された宣言型の解決策です。.tf ファイルに記述して
git commit し、terraform apply を実行するだけで、Terraform が自動的に state を移行します。| 比較項目 | terraform state mv(CLI) | moved ブロック(宣言型) |
|---|---|---|
| VCS 管理 | できない | できる(.tf ファイルに記述) |
| PR レビュー | できない | できる |
| plan 確認 | できない | できる(Moved 表示で事前確認) |
| チーム共有 | 個別実行が必要 | apply 時に全員に自動適用 |
| 必要バージョン | Terraform 0.x 以降 | Terraform 1.1 以降 |
movedブロックの書き方と基本構文
1. 基本構文
moved ブロックはfrom と to の2つの引数だけで構成されます。# aws_instance.server を aws_instance.web_server に名前変更する moved { from = aws_instance.server to = aws_instance.web_server } # 変更後のリソース定義(新名称) resource "aws_instance" "web_server" { ami = "ami-0abcdef1234567890" instance_type = "t3.micro" }
terraform plan を実行します。2. terraform plan の出力確認
plan 実行時に「Moved」表示が出たら正しく認識されています。$ terraform plan Terraform will perform the following actions: # aws_instance.server has moved to aws_instance.web_server resource "aws_instance" "web_server" { id = "i-0a1b2c3d4e5f67890" ami = "ami-0abcdef1234567890" instance_type = "t3.micro" tags = { "Name" = "web-server-prod" } # (12 unchanged attributes hidden) } Plan: 0 to add, 0 to change, 0 to destroy.
state mv にはない最大のメリットです。terraform apply で適用後、moved ブロックはそのまま残しておいても問題ありません(後から削除した場合も state は変わりません)。チームで「いつ何を移動したか」の記録として残す運用も一般的です。モジュール内リソースの移動に使うパターン
1. フラットなリソースをモジュールへ移動する
コードが肥大化してモジュール化を進める場面は多いはずです。既存リソースをモジュールに移動する場合も、moved ブロックが使えます。# ルートモジュールにあった aws_s3_bucket.logs を # module.logging 内の aws_s3_bucket.main に移動する moved { from = aws_s3_bucket.logs to = module.logging.aws_s3_bucket.main } # modules/logging/main.tf に移動後のリソース定義を記述 # module "logging" の呼び出しをルートモジュールに追加
2. モジュール間の移動
既存モジュールから別モジュールへの移動にも対応しています。# module.old_network から module.vpc への移動例 moved { from = module.old_network.aws_vpc.main to = module.vpc.aws_vpc.main }
3. count / for_each 使用リソースの移動
インデックス指定も可能です。count から for_each への変換時に役立ちます。# count から for_each へ変換する際の moved ブロック例 moved { from = aws_security_group.allow_http[0] to = aws_security_group.allow_http["web"] }
removedブロックとは:リソースを壊さず管理から外す
リファクタリングで「Terraform の管理対象から除外したいが、インフラそのものは消したくない」という要件があります。例として、以下のようなケースが挙げられます。
・別の Terraform プロジェクトや別チームに管理を移管する
・一時的に手動管理に切り替える
・既存リソースを IaC 管理から外してコストを削減するリファクタリング
従来は
terraform state rm コマンドを使っていましたが、Terraform 1.7 で導入された removed ブロックを使えば、同じ操作を宣言型で行えます。| 比較項目 | terraform state rm(CLI) | removed ブロック(宣言型) |
|---|---|---|
| VCS 管理・PR レビュー | できない | できる |
| インフラを残す設定 | できる(state rm は常にインフラ存続) | destroy = false で指定 |
| インフラを削除する設定 | 別途 destroy が必要 | destroy = true で同時実行 |
| 必要バージョン | Terraform 0.x 以降 | Terraform 1.7 以降 |
removedブロックの書き方とdestroyオプション
1. インフラを残して state から除外する(destroy = false)
最もよく使うパターンです。AWS リソースを残したまま Terraform の管理対象から外します。# aws_instance.legacy を state から除外するが、EC2 インスタンスは残す removed { from = aws_instance.legacy lifecycle { destroy = false } }
terraform plan の出力確認例:$ terraform plan Terraform will perform the following actions: # aws_instance.legacy will no longer be managed by Terraform after apply # (your real infrastructure will be left intact) resource "aws_instance" "legacy" { id = "i-0b2c3d4e5f6a7b8c9" instance_type = "t3.small" # ... } Plan: 0 to add, 0 to change, 0 to destroy.
2. state 除外と同時にインフラも削除する(destroy = true)
destroy = true はデフォルト値です。設定を省略した場合と同じ挙動になります。# aws_instance.old を state 除外と同時に実インフラも削除する removed { from = aws_instance.old lifecycle { destroy = true # デフォルト値。省略可 } }
destroy = true の場合は terraform apply で実インフラが削除されます。本番環境では必ず terraform plan で事前確認し、削除対象が意図通りかを確認してから apply してください。
>> Terraform実践セミナーの詳細はこちら
moved+removedを組み合わせたリファクタリング実例
実務では moved と removed を組み合わせる場面があります。ここでは「ルートモジュールのリソースをモジュール化し、不要になった古いリソースを state から除外する」リファクタリング例を示します。1. リファクタリング前の構成
# main.tf(リファクタリング前) resource "aws_s3_bucket" "app_data" { bucket = "my-app-data-prod" } resource "aws_s3_bucket" "logs_old" { bucket = "my-logs-legacy-prod" # このバケットは別チームに移管済みで Terraform 管理不要 }
2. リファクタリング後の構成
# main.tf(リファクタリング後) # 既存リソースをモジュールへ移動 module "storage" { source = "./modules/storage" } # aws_s3_bucket.app_data を module.storage.aws_s3_bucket.main へ移動 moved { from = aws_s3_bucket.app_data to = module.storage.aws_s3_bucket.main } # logs_old は別チーム管理になったため state から除外(バケット自体は残す) removed { from = aws_s3_bucket.logs_old lifecycle { destroy = false } }
3. modules/storage/main.tf
# modules/storage/main.tf resource "aws_s3_bucket" "main" { bucket = "my-app-data-prod" }
terraform plan を実行すると、・
aws_s3_bucket.app_data が module.storage.aws_s3_bucket.main へ移動(Moved 表示)・
aws_s3_bucket.logs_old が state 除外(削除なし)が確認できます。実インフラへの変更は発生せず、「Plan: 0 to add, 0 to change, 0 to destroy」と表示されます。
よくあるエラーと対処法
1. バージョン不一致エラー(Terraform 1.1 / 1.7 未満)
Error: Unsupported block type on main.tf line 1: 1: moved { Blocks of type "moved" are not expected here.
terraform version でバージョンを確認し、1.1 未満の場合はアップグレードしてください。removed ブロックは 1.7 以上が必要です。required_version に下限を設定しておくと、チーム内のバージョン不一致を防げます。2. from アドレスの記述ミス
Error: Invalid "moved" block target address from address "aws_instance.old_server" doesn't refer to an existing resource in the state.
terraform state list で現在の state アドレスを確認し、from の記述を一致させてください。モジュールを使っている場合は module.name.resource_type.name の形式が必要です。3. 循環参照エラー
Error: Cycle in moved block references Terraform detected a cycle in the moved block references: aws_instance.a -> aws_instance.b -> aws_instance.a
4. destroy = false なのに削除が走るケース
removed ブロックでdestroy = false を指定しているにもかかわらず、plan に削除操作が含まれる場合は、他の設定変更が副作用を引き起こしている可能性があります。対処:plan の全出力を精査し、removed ブロック以外の変更で削除が発生していないか確認してください。
まとめ
| やりたいこと | 使うブロック | 必要バージョン |
|---|---|---|
| リソース名を変更する | moved { from = ... to = ... } |
Terraform 1.1 以上 |
| ルートモジュール→モジュールへ移動 | moved { from = res to = module.name.res } |
Terraform 1.1 以上 |
| state 除外・インフラは残す | removed + lifecycle { destroy = false } |
Terraform 1.7 以上 |
| state 除外・インフラも削除 | removed + lifecycle { destroy = true } |
Terraform 1.7 以上 |
state mv・state rm)を宣言型に置き換えるものです。コードとして記述することで VCS 管理・PR レビュー・plan 事前確認が可能になり、チーム運用での安全性が大幅に高まります。・moved ブロックは Terraform 1.1 以降で利用可能。リソース名変更やモジュール移動を HCL で宣言する
・removed ブロックは Terraform 1.7 以降で利用可能。
destroy = false でインフラを残したまま state 除外できる・どちらも
terraform plan で事前確認できるため、CLI 操作のミスリスクをほぼゼロにできるstate 操作のリスクを減らしながら安全にリファクタリングを進めたい場合は、まず moved ブロックと removed ブロックの導入を検討してみてください。
>> Terraform実践セミナーの詳細はこちら
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 次のページへ:Terraformのdepends_onと依存関係設計・暗黙依存とterraform graphでリソース作成順序を制御する方法
- 前のページへ:terraform testによるコード検証の始め方|1.6以降のネイティブテスト機能でリグレッションを防ぐ
- この記事の属するカテゴリ:Terraformへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます