moved ブロックは、既存インフラの作り直しを避けつつ、アドレス(resource/module アドレス)の変更を宣言的に state に反映させる仕組みです。
チーム開発や CI/CD でのリファクタリング時に、手順ミスや環境差異を防げる点が最大の利点です。
リファクタリングでリソース名やモジュール構成を変更すると、Terraform のアドレスが変わり、従来のままでは plan に「古いリソースの destroy」と「新しいリソースの create」が並びます。既存リソースを壊したくない、ダウンタイムを出せない場合に、moved ブロックで旧→新アドレスの対応を宣言すると、apply 時に state 上の紐づけだけを移し替え、物理リソースはそのまま保持できます。
実務では、命名規則変更、モジュール化・モノレポ再編、count から for_each への移行などで頻出します。コマンドで個別に state を触るより、コードに変更履歴として残せるため、再現性・レビュー適性・CI 実行時の安全性が高いのが特徴です。
最小例: リソース名のリネーム(state のみ移動、実体は不変)
resource "aws_s3_bucket" "app_primary" {
# 旧: aws_s3_bucket.app
# 設定内容は従来どおり
}
moved {
from = aws_s3_bucket.app
to = aws_s3_bucket.app_primary
}moved ブロックは Terraform 1.1 以降で利用可能です。書式は moved { from = <旧アドレス> to = <新アドレス> }。from/to は同一種類の state オブジェクト(同じリソースタイプ、同じ管理対象の粒度)を指す必要があります。タイプやプロバイダが異なる移動はできません。
moved ブロックは、from と to の両方のアドレスを参照できるモジュールに置きます。一般に「両者の最小共通の親モジュール」に配置すると覚えると安全です。例えば、ルート直下のリソースを子モジュールへ移す場合は、ルートモジュールに moved を書き、from はルートのリソース、to は module.<name>.<resource> の形式で指定します。
適用条件として、to 側のリソース(またはモジュール)は新しい構成としてコード上に存在し、state には未登録であること、from 側のオブジェクトは state に存在するがコード上には存在しない(あるいは rename 後で参照されていない)ことが必要です。plan には moved エントリとして表示され、apply 時に一度だけ消化されます。
モジュール化に伴う移動(ルート→子モジュール)
# root module
module "storage" {
source = "./modules/storage"
}
# 旧: aws_s3_bucket.app(root に存在していた)
# 新: module.storage.aws_s3_bucket.app(子モジュール配下に定義)
moved {
from = aws_s3_bucket.app
to = module.storage.aws_s3_bucket.app
}よくある 3 パターンを示します。いずれも、先に新しい定義(to 側のリソース/モジュール)をコードに用意し、同時に moved ブロックを追加して plan で moved のみになることを確認します。
count→for_each では、インスタンス単位のアドレスが変わるため、必要な数だけ moved を並べます。インデックス番号からキー名への対応を一意に決めるのがポイントです。
モジュール移動の視覚化
ケース別 moved の記述例
# 1) リソース名のリネーム(同一モジュール内)
resource "aws_s3_bucket" "app_primary" {}
moved {
from = aws_s3_bucket.app
to = aws_s3_bucket.app_primary
}
# 2) ルート→子モジュールへの移動(ルートに記述)
module "storage" { source = "./modules/storage" }
moved {
from = aws_iam_role.app
to = module.storage.aws_iam_role.app
}
# 3) count→for_each(index→key の対応を個別に指定)
# 旧: aws_instance.web[count = 2]
# 新: aws_instance.web[for_each = {"a" = ..., "b" = ...}]
moved {
from = aws_instance.web[0]
to = aws_instance.web["a"]
}
moved {
from = aws_instance.web[1]
to = aws_instance.web["b"]
}アドレス移動に近い操作はいくつかあります。どれを使うかは目的で決めます。既存リソースを壊さず、チームで再現したいなら moved が第一選択です。単発の緊急対応や過去のズレ補正には state コマンドを検討します。新規の外部リソース取り込みは import 系が適切です。
| 手段 | 操作の性質 | 共有性/再現性 | 影響範囲 |
|---|---|---|---|
| moved ブロック | 宣言的・apply 時に一度だけ state を移動 | 高い(コードに残る) | 非破壊(物理リソースは維持) |
| terraform state mv | 命令的・即時に state を編集 | 低い(手操作依存) | 非破壊(ただし人為ミスの余地) |
| import(import ブロック/terraform import) | 既存リソースを state に取り込み | 中(ブロックはコード化可能) | 非破壊(取り込みのみ) |
| replace(-replace や lifecycle) | 作り直しを明示 | 中(コマンド/コード併用) | 破壊的(再作成) |
移動は「新定義の追加」と「moved の追加」を同一変更として扱い、plan に create/destroy が出ないことを確認してから適用します。リモートバックエンドのロックを有効にし、環境ごとに段階適用します。
複数の moved を束ねる場合は、plan で期待どおりに全て moved と表示されるかを確認します。to 側が既に state に存在する場合はエラーになるため、重複を先に解消(state rm か destroy)してから進めます。
期待される plan 出力(概念例)
Terraform will perform the following actions:
# aws_s3_bucket.app_primary has moved to aws_s3_bucket.app_primary
moved from aws_s3_bucket.app
to aws_s3_bucket.app_primary
Plan: 0 to add, 0 to change, 0 to destroy. 2 to move.試験では、moved と import/replace/state mv の使い分け、モジュールどこに置くか、count→for_each の個別移動、to/from の前提条件、非対応対象(data)などが問われます。plan での挙動(moved が表示され、create/destroy は出ない)が読み取れることも重要です。
また、moved は型やプロバイダを跨いだ移動に使えない点、to 側が state に既にあると apply が失敗する点、apply 後は一度消化されるので全環境で確実に適用してから削除する運用が推奨される点も押さえておきましょう。
Pro
問題 1
ルートモジュールに aws_s3_bucket.web があり、これを子モジュール storage 内の aws_s3_bucket.web_main に移したい。再作成は避けたい。正しい moved ブロックの配置と記述はどれか。
正解: A
moved は from/to の両方を参照できる最小共通親モジュールに置く。今回はルートがそれに該当する。data は対象外であり、state mv は宣言的ではなく再現性が低い。
moved ブロックはいつ削除してよいですか?
全ての適用対象環境(開発/検証/本番など)で moved が消化され、state の移動が完了した後に削除します。長く残しすぎるとノイズになるため、移行完了のタイミングでクリーンアップするのがよい運用です。
to 側が既に state に存在し、apply でエラーになります。どうすればよいですか?
重複状態です。先に重複を解消してください。具体的には、誤って作成された to 側オブジェクトを destroy するか、terraform state rm で不要な state エントリを除去した上で、改めて moved を適用します。
大量の count→for_each 変換で moved が何十個も必要です。効率的な方法は?
旧 index と新 key の対応表を用意し、テンプレートやスクリプトで moved ブロックを生成すると安全です。段階的に小さな PR/変更単位で適用し、各ステップで plan に moved のみが出ることを確認して進めてください。
NicheeLab編集部
データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。
Terraform HCL 構文の基礎:Block / Attribute / Expression を正しく使い分ける
Terraform Associate で頻出の HCL 構文を、ブロック・属性・式の3視点で整理。実務で迷いがちな書き...
Terraform Authoring & Ops Pro: 上位資格の範囲と対策
上位レベルを想定したTerraformの設計・運用ドメインを整理し、実務で通用する対策を提示。モジュール設計、ステート運...
Terraform Providers の基本: プラグイン型アーキテクチャを正しく使いこなす
Associate レベルで押さえるべき Provider の基礎、インストール、バージョニング、認証、エイリアス運用を...
Terraform Resourceブロック徹底ガイド: 最小単位のリソース定義
Associateレベルで押さえるべきResourceブロックの構造、依存関係、メタ引数、ライフサイクル制御を実務目線で...
Terraform Data Source徹底理解:既存リソースの参照で壊さず足す
Terraform Associate向けに、Data Sourceを用いた既存リソース参照の基本、選択基準、評価順序、...