Terraform

Terraform moved ブロック徹底解説: リファクタリング時の安全な移動

2026-04-19
NicheeLab編集部

moved ブロックは、既存インフラの作り直しを避けつつ、アドレス(resource/module アドレス)の変更を宣言的に state に反映させる仕組みです。

チーム開発や CI/CD でのリファクタリング時に、手順ミスや環境差異を防げる点が最大の利点です。

moved ブロックの位置づけと使い所

リファクタリングでリソース名やモジュール構成を変更すると、Terraform のアドレスが変わり、従来のままでは plan に「古いリソースの destroy」と「新しいリソースの create」が並びます。既存リソースを壊したくない、ダウンタイムを出せない場合に、moved ブロックで旧→新アドレスの対応を宣言すると、apply 時に state 上の紐づけだけを移し替え、物理リソースはそのまま保持できます。

実務では、命名規則変更、モジュール化・モノレポ再編、count から for_each への移行などで頻出します。コマンドで個別に state を触るより、コードに変更履歴として残せるため、再現性・レビュー適性・CI 実行時の安全性が高いのが特徴です。

  • 主な利用局面: リソース名のリネーム、モジュール間の移動、インスタンス index/key の変更(count→for_each など)
  • 非対応: データソースの移動(data は state の管理対象ではない)
  • メリット: 破壊的変更の回避、チームでの再現性、plan/apply による自動適用

最小例: リソース名のリネーム(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 時に一度だけ消化されます。

  • 配置原則: 両アドレスを同時に参照できる最小共通親モジュールに置く
  • 型制約: リソースタイプ・プロバイダは不変(aws_s3_bucket→aws_s3_bucket は可、aws_s3_bucket→aws_s3_object は不可)
  • 非対象: data ブロック、出力値、変数などの非 state オブジェクト
  • 状態前提: to は state に未登録、from は state に登録済み
  • 出力: 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 を置く
  • count→for_each: 旧 index と新 key のマッピングを moved で列挙

モジュール移動の視覚化

紐付けのみ更新aws_s3_bucket.approot module (旧)moved { from, to }宣言的に state を移動module.storage.aws_s3_bucket.appchild module (新)Terraform state物理リソースは維持moved ブロックで state を root → 子モジュールへ移動(実体は不変)

ケース別 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 と他手段の使い分け(比較表)

アドレス移動に近い操作はいくつかあります。どれを使うかは目的で決めます。既存リソースを壊さず、チームで再現したいなら moved が第一選択です。単発の緊急対応や過去のズレ補正には state コマンドを検討します。新規の外部リソース取り込みは import 系が適切です。

  • moved: 宣言的・履歴に残る。plan/apply に統合され安全。
  • terraform state mv: 即時・手動・一回限り。履歴に残らず、共同作業では再現しづらい。
  • import(import ブロック/terraform import): 既存実体を state に登録するための手段で、アドレス移動とは目的が異なる。
  • replace(-replace フラグや lifecycle): 作り直しを意図的に行う。移動ではない。
手段操作の性質共有性/再現性影響範囲
moved ブロック宣言的・apply 時に一度だけ state を移動高い(コードに残る)非破壊(物理リソースは維持)
terraform state mv命令的・即時に state を編集低い(手操作依存)非破壊(ただし人為ミスの余地)
import(import ブロック/terraform import)既存リソースを state に取り込み中(ブロックはコード化可能)非破壊(取り込みのみ)
replace(-replace や lifecycle)作り直しを明示中(コマンド/コード併用)破壊的(再作成)

安全な移動の実務フロー(チーム/CI 向け)

移動は「新定義の追加」と「moved の追加」を同一変更として扱い、plan に create/destroy が出ないことを確認してから適用します。リモートバックエンドのロックを有効にし、環境ごとに段階適用します。

複数の moved を束ねる場合は、plan で期待どおりに全て moved と表示されるかを確認します。to 側が既に state に存在する場合はエラーになるため、重複を先に解消(state rm か destroy)してから進めます。

  • ブランチで新しい定義と moved を同時に追加
  • terraform plan を取り、moved のみが表示されることを確認
  • 検証環境→ステージング→本番の順に apply(バックエンドロック必須)
  • 全環境で適用完了後、moved ブロックを削除(将来のノイズ回避)
  • CI では plan 出力に destroy/create が混ざっていないことをゲートにする

期待される 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 後は一度消化されるので全環境で確実に適用してから削除する運用が推奨される点も押さえておきましょう。

  • 配置ルール: 両アドレスを参照できる最小共通親モジュールに置く
  • 非対応: data ブロックの移動は不要/不可
  • count→for_each: インスタンスごとに moved を書く(自動対応はされない)
  • 型/プロバイダは不変であることが必要
  • to はコードに存在し state には未登録、from は state に存在しコードには存在しないのが基本
  • state mv は命令的、moved は宣言的(チームでの再現性は moved が優位)

問題で確認

Pro

問題 1

ルートモジュールに aws_s3_bucket.web があり、これを子モジュール storage 内の aws_s3_bucket.web_main に移したい。再作成は避けたい。正しい moved ブロックの配置と記述はどれか。

  1. ルートモジュールに moved を置き、from = aws_s3_bucket.web、to = module.storage.aws_s3_bucket.web_main とする
  2. 子モジュール storage に moved を置き、from = aws_s3_bucket.web、to = aws_s3_bucket.web_main とする
  3. moved は使わず terraform state mv を本番環境で直接実行する
  4. 任意のモジュールに moved を置き、from = data.aws_s3_bucket.web、to = module.storage.aws_s3_bucket.web_main とする

正解: 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 のみが出ることを確認して進めてください。

この記事で学んだ内容を問題で確認しましょう

16,000問以上の問題で実力チェック

無料で問題を解いてみる
この記事の著者

NicheeLab編集部

データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。


関連記事
Terraform

Terraform HCL 構文の基礎:Block / Attribute / Expression を正しく使い分ける

Terraform Associate で頻出の HCL 構文を、ブロック・属性・式の3視点で整理。実務で迷いがちな書き...

Terraform

Terraform Authoring & Ops Pro: 上位資格の範囲と対策

上位レベルを想定したTerraformの設計・運用ドメインを整理し、実務で通用する対策を提示。モジュール設計、ステート運...

Terraform

Terraform Providers の基本: プラグイン型アーキテクチャを正しく使いこなす

Associate レベルで押さえるべき Provider の基礎、インストール、バージョニング、認証、エイリアス運用を...

Terraform

Terraform Resourceブロック徹底ガイド: 最小単位のリソース定義

Associateレベルで押さえるべきResourceブロックの構造、依存関係、メタ引数、ライフサイクル制御を実務目線で...

Terraform

Terraform Data Source徹底理解:既存リソースの参照で壊さず足す

Terraform Associate向けに、Data Sourceを用いた既存リソース参照の基本、選択基準、評価順序、...

Terraformの記事一覧 (101件)
© 2026 NicheeLab All rights reserved.