terraform lifecycleの使い分け|ignore_changes・depends_on・movedの判断
applyしたはずなのに翌日のplanでまた同じ差分が出る、あるいはignore_changesを付けたのに差分が消えない。この2つはTerraformの運用で最も相談の多い症状で、原因の切り分けを飛ばしてlifecycleブロックを足すと、後から本当に必要な変更まで無視され続けます。この記事ではlifecycleの7つの引数がどこまで効くのかを仕様から整理し、差分が収束しない原因を3つに切り分ける手順、depends_onを最後の手段に留める条件、movedブロックでstateを壊さずに構成を移動させる方法まで扱います。バージョン依存の記述は2026年8月13日にHashiCorp公式ドキュメントとGitHub Releasesで確認した内容です。
まとめ:差分の原因を切り分けてから引数を1つだけ選ぶ
結論から置きます。lifecycleブロックは差分を消すための道具ではなく、Terraformの依存グラフの組み立て方を変える宣言です。だからこそ変数も式も書けず、値はリテラルに限られます。差分が毎回出るときに最初にやることはignore_changesの追記ではなく、その差分がクラウド側の外部変更なのか、APIが値を正規化した結果なのか、providerが既定値を埋め戻しているのかを分けること。ここを飛ばすと、正規化が原因の差分にignore_changesを貼って、本当の設定変更まで見えなくします。
引数の選び方は症状で決まります。外部の自動化がタグやスケール値を書き換えるならignore_changesを属性単位で指定する。停止時間を作らずに置き換えたいならcreate_before_destroy。消えると困る実体にはprevent_destroyを先に付ける。上流の変更に連動させたいときだけreplace_triggered_byを使います。depends_onは参照で依存を表現できない場合の最後の手段で、moduleに付けると必要以上の置き換えを招きます。リソース名やmodule構成を変える場面では、lifecycleではなくmovedブロックが正解です。
lifecycleブロックの仕様とリテラル値しか書けない理由
挙動を変える範囲を誤解したまま使うと、効くはずのない場面で引数を足すことになります。
lifecycleに変数や式を書けない理由と評価される順番の確認
環境ごとに削除保護を切り替えたい、という要望は必ず出ます。ところがprevent_destroy = var.is_productionは書けません。公式は理由をこう説明しています。「All lifecycle settings affect how Terraform constructs and traverses the dependency graph. As a result, only literal values can be used because the processing happens too early for arbitrary expression evaluation.」
依存グラフの構築は、変数の値が確定するより前の段階で行われます。lifecycleの内容はそのグラフの形そのものを変えるため、式の評価を待てない。この制約は仕様であって回避策の対象ではないので、環境差を付けたい場合はモジュール自体を分けるか、本番用と検証用でリソースブロックを分岐させる設計に寄せてください。宣言的な構成管理でなぜこうした前提が置かれるのかはIaCとは?Infrastructure as Codeの仕組み・メリットと導入判断を解説で整理しています。
例外的に式が使えるのがpreconditionとpostcondition、そしてreplace_triggered_byです。これらは検証や参照を目的とするため、グラフ構築後の評価に載ります。precondition の評価順について公式は「Terraform evaluates precondition blocks after evaluating count and for_each meta-arguments.」と明記しており、繰り返しの各インスタンスに対して個別に判定されます。
lifecycleで指定できる7つの引数と書ける場所の一覧と配置条件
2026年8月時点の公式リファレンスに載る引数は7つ。用途と書ける場所を先に押さえます。
| 引数 | 担当する範囲 | 書けるブロック |
|---|---|---|
| create_before_destroy | 置き換えの順序を反転 | resource |
| prevent_destroy | 破棄を含む計画を却下 | resource |
| ignore_changes | 更新時の属性を無視 | resource |
| replace_triggered_by | 参照先の変化で置き換え | resource |
| precondition | 作成前の前提を検証 | data・resource等 |
| postcondition | 作成後の結果を検証 | data・resource等 |
| action_trigger | イベントで処理を起動 | resource |
preconditionとpostconditionはresourceだけでなくdataブロックとephemeralブロックでも使えます。postconditionの失敗について公式は「Postcondition failures prevent changes to other resources that depend on the failing resource.」と記しており、検証の失敗が下流へ波及して止まる設計。dataブロック側での検証の使い方はterraform dataの使い方|data sourceの参照設計と依存の暗黙化で扱いました。
action_triggerは比較的新しい引数です。Actions本体はv1.14.0(2025年11月19日公開)で「New top-level Actions block」として導入され、providerが定義した処理をリソースのライフサイクルに紐付けて起動します。対応イベントはbefore_createとafter_create、before_updateとafter_update。v1.16系ではbefore_destroyとafter_destroyが加わり、失敗時の挙動をhaltとtaintとcontinueから選べるようになりました。1.16系は候補版の段階なので、本番の設計に織り込むかは提供状況を確認してから判断してください。
毎回同じ差分が出る事象をignore_changesの前に切り分ける
差分が収束しない状態は、コードとstateとクラウド実体のどこかがずれているという信号です。信号を消す前に発信源を特定します。
毎回差分が出る原因を外部変更と正規化と既定値の3つに切り分ける
同じ差分が繰り返し現れる原因は、実務ではほぼ3種類に収まります。第一が外部変更。Auto Scalingの希望台数、コンソールから追加されたタグ、別システムが付与するラベルなど、Terraform以外の主体が書き換えている場合です。第二がAPI側の正規化で、IAMポリシーのJSONがキー順を並べ替えられる、証明書のPEMが末尾の改行を落とされる、といった書き換えが該当します。第三がprovider既定値の埋め戻しで、こちらが書いていない属性にクラウド側の既定値が入り、空との差分として毎回提示される形です。どの既定値が埋め戻されるかはproviderの版で変わるため、制約の桁とlockファイルの運用はterraform providerのバージョン制約とalias設計|lockファイルの運用判断で整理しました。
切り分けはterraform plan -refresh-onlyから始めます。ここで差分が出るなら、stateと実体がずれている、つまり外部変更が起きている側。実体は変わっていないのに通常のplanだけ差分が出るなら、コードとstateの表現差、すなわち正規化か既定値の側です。この2つは対処がまるで違います。何度実行しても同じ結果へ収束する性質そのものについては冪等性とは?読み方・意味からAPI・IaCでの担保方法まで実装者向けに解説で整理しました。
外部変更が原因なら、まず「その変更を誰が行っているか」を止められるかを検討します。止められるならコンソール権限を絞るほうが健全で、ignore_changesは不要。止められない場合、たとえばアプリ側のデプロイツールがタスク定義のリビジョンを進める構成なら、そこで初めてignore_changesの出番になります。
ignore_changesはリストで属性名を並べます。公式は要素単位の指定について「You can reference map and list elements using index notation, such as tags[“Name”] and list[0].」と記載しており、マップ全体ではなく特定のキーだけを無視できる。運用チームが付けるコスト按分タグだけを対象から外し、環境名や用途のタグは管理下に残す、といった書き分けが可能です。
押さえておきたいのが、ignore_changesが効くのは更新の場面に限られる点。作成と破棄は通常どおり実行されます。つまり無視した属性は、リソースを新規に作るときには設定値がそのまま使われ、その後の変更だけが提案されなくなる。ここを取り違えると「無視しているのに初回applyで値が入った」と混乱します。
指定する属性は、そのリソースの最上位の引数名で書きます。ネストしたブロックの内側にある値を狙う場合、ブロック単位で指定するか、索引記法で辿れる形かをproviderのスキーマで確認してください。狙いどおり指定できているかは、planを一度回して該当属性が差分から消えたかで判定するのが確実です。plan出力の記号とサマリ行の読み方はterraform planの差分の読み方|init・apply・destroyの安全手順にまとめています。
ignore_changesを付けても差分が消えないときの調べ方
指定したのに差分が残る場合、疑う順序があります。まず属性名の綴りとネストの深さ。planの出力に現れている属性パスと、ignore_changesに書いた名前が一致しているかを文字単位で照合します。次に、差分が更新ではなく置き換えとして提案されていないか。「must be replaced」が付いているなら、それは更新ではないためignore_changesの管轄外です。
それでも解けないときはterraform show -jsonで計画をJSON化し、該当リソースのbeforeとafterを直接比較してください。目視のplan出力では省略されている属性や、空文字とnullの違いが見えます。正規化が原因の差分は、この比較で「値は同じだが表現が違う」形として現れる。JSONを扱う際は変数値やstateの内容が含まれるため、出力先と保持期間を先に決めておきます。
実体はあるがstateに載っていないリソースが原因で、毎回作成の差分が出ているケースも珍しくありません。この場合はignore_changesではなく取り込みが正解で、手順はterraform importで既存リソースをコード化する手順で扱いました。
ignore_changes = allを使ってよい場面と避ける場面
公式は「use the all keyword to instruct Terraform to ignore all attributes」として全属性の無視を用意しています。allを指定すると、そのリソースは作成された後は一切更新されず、コード側を書き換えても差分が出ません。
許容できるのは2つだけ。1つは、実体の設定を別の仕組みが全面的に管理していて、Terraformは存在の管理だけを担うと役割分担が決まっている場合。もう1つが、移行期間に既存リソースを取り込んだ直後、コードを実体へ寄せ切るまでの一時措置として使う場合で、この用法では期限と外す条件を先に決めます。
避けるべきなのは、差分が読み解けないという理由でallを貼る運用です。以後そのリソースはコードが実体を表さなくなり、レビューでも検知できません。誰かが半年後に設定を書き換え、applyしても何も起きず、原因究明に半日かかる。この事故は実際に起きます。allは、無視しているという事実をコメントで残し、棚卸しの対象に載せてください。
置き換えと削除の抑止をlifecycleの3つの引数で制御する
ここから先は、差分を消す話ではなく、変更が起きる順序と可否を設計する話です。
create_before_destroyが依存先へ伝播する挙動と名前の衝突
変更できない属性を書き換えると、Terraformは既定で「破棄してから作成」の順で置き換えます。停止時間が発生するため、これを反転させるのがcreate_before_destroy = true。新しい実体を先に作り、下流の参照を切り替えてから古い実体を破棄します。
見落とされやすいのが伝播です。公式は「Terraform propagates and applies create_before_destroy behavior to all resource dependencies」と明記しています。1つのリソースに付けると、それが依存するリソース群にも同じ順序が適用される。想定より広い範囲で先行作成が起き、同時に存在できないリソースがあると失敗します。
典型的な失敗が名前の衝突です。IAMロールやセキュリティグループのように名前の一意性が要求される実体では、先行作成の時点で同名の実体が2つ必要になり、作成そのものが弾かれる。対処はname_prefix系の引数へ寄せてproviderに一意な名前を生成させるか、名前にランダムなサフィックスを付けることです。もう1点、この引数を付けたリソースではdestroy provisionerが使えなくなるため、破棄時の後処理を持たせている場合は先に移設してください。
prevent_destroyで止まる操作と止まらない操作の境目
prevent_destroy = trueを付けると、そのリソースの破棄を含む計画がエラーで却下されます。置き換えも破棄を含むため同様に止まる。本番のデータベース、S3バケット、KMSキーのように失うと復旧できない実体には、作成と同時に付けておく運用が有効です。
ただし止まらない経路があります。公式は「This rule doesn’t prevent Terraform from destroying a resource if you remove its configuration.」と明記しています。リソースブロックごと削除した場合、prevent_destroyの記述も一緒に消えるため、保護は働きません。誤ってブロックを消したPRがマージされれば実体は消えます。
この穴を塞ぐには、Terraform側だけに頼らない層を重ねます。クラウド側の削除保護(RDSのdeletion protection、S3のバケットポリシーやオブジェクトロック)を併用し、CI側ではplanのJSONを検査してdeleteアクションを含む計画に承認を要求する。3層のうち1層が抜けても止まる形にしておくのが実務的な落としどころです。なお、実体を残したままTerraformの管理から外したい場合はremovedブロックを使い、その中のlifecycleでdestroy = falseを指定します。
replace_triggered_byで意図した置き換えを起こす書き方
ここまでと逆に、置き換えを意図的に起こしたい場面もあります。設定ファイルの内容が変わったらコンテナを作り直す、暗号鍵をローテーションしたら依存する実体を再作成する、といった連動です。replace_triggered_byには、変化を監視したいリソースや属性の参照をリストで渡します。
制約が1つ。公式は「You can only reference managed resources in replace_triggered_by expressions.」と記しており、変数やローカル値、dataソースは指定できません。任意の値の変化を引き金にしたい場合は、terraform_dataリソースのinputへその値を渡し、そのリソースを参照する形に組み替えます。
使いどころは限定してください。この引数は依存関係を暗黙に増やすため、参照先が置き換わるたびに連鎖して置き換えが走ります。連鎖の範囲が読めない構成では、planで毎回どこまで巻き込まれるかを確認する運用が要る。逆に、明示的な再作成をCIから制御したいだけならterraform apply -replace=ADDRESSで足ります。
depends_onを最後の手段に留める判断基準と書き換え方
依存が正しく解決されないという相談の多くは、depends_onで解決すべきではない問題です。
参照で表せる依存とdepends_onでしか表せない依存の差
Terraformはリソース間の参照から依存グラフを自動で作ります。BのarnをAの引数で参照していれば、BがAより先に作られる。この形で表せる依存にdepends_onを書く必要はなく、書けば冗長な宣言が増えるだけです。
公式が用途として挙げるのは「a resource or module relies on another resource’s behavior but does not access any of that resource’s data in its arguments」という状況。値は参照しないが、相手が存在していないと動かないという依存です。IAMロールのポリシーがアタッチされる前にEC2が起動してAPIを叩いて失敗する、VPCのインターネットゲートウェイが接続される前にプロビジョニングが走る、といったケースが該当します。
指定できるのは、同じ呼び出しモジュール内のリソースと子モジュールへの参照リストのみ。任意の式は書けません。判断の順序としては、まず引数で参照できる値がないかを探し、無い場合に限ってdepends_onへ進んでください。
moduleへのdepends_onが過剰な置き換えを招く仕組み
公式はdepends_onを「as a last resort」と位置づけ、理由も明示しています。この宣言があると、Terraformは上流の変化が下流へ及ぼす影響を特定できず、下流の値を保守的に「(known after apply)」として扱う。結果として公式の表現どおり「replace more resources than necessary」、つまり必要以上の置き換えが提案されます。
影響が最も大きいのがmoduleへのdepends_onです。モジュール全体が対象になるため、内部のリソースとdataソースのすべてが上流の変化を待つ関係になり、上流に軽微な変更が入っただけでモジュール内の広範囲が未確定になります。数十リソースを含むモジュールでこれをやると、planの差分が読めなくなる。
設計として避ける方向は2つ。1つはモジュールの入出力を見直し、依存を値の受け渡しで表現し直すこと。もう1つが分割粒度の変更で、そもそも待ち合わせが必要な単位が同じモジュールに同居していないかを疑います。粒度とoutputの設計はTerraform moduleの分割粒度とoutput設計|自作とRegistryの判断で判断基準を示しました。
depends_onを消すために依存を参照へ書き換える手順と確認点
既存の構成からdepends_onを外す作業は、次の順で進めると安全です。まず対象のdepends_onが何を待たせているかを1件ずつ書き出し、待つ理由が「値が必要」なのか「相手の存在が必要」なのかを分類する。前者はその値を引数で参照する形に置き換えれば宣言を削除できます。
後者のうち、モジュール単位で待たせているものは、モジュールのoutputに待ち合わせ対象のリソース属性を1つ足し、呼び出し側でその値を参照する形へ組み替えます。参照する値はidやarnで構いません。これで依存グラフは同じ順序を保ちながら、未確定値の伝播だけが止まります。
書き換えのたびにplanを回し、置き換えが提案されていないことを確認してください。宣言を外した瞬間に順序が変わり、初回だけ作成順の失敗が起きる構成もありえます。検証環境で一度applyまで通してから本番へ持ち込む、という手順を省かないこと。こうした既存構成の棚卸しと書き換えを含むインフラの設計見直しは、インフラ構築(AWS・Google Cloud・Azure)として請け負っています。
movedブロックでstateを壊さずに構成を移動させる手順
リソース名の変更やモジュールへの切り出しは、何も宣言しなければ破棄と再作成になります。
movedのfromとtoで扱えるリネームとmodule移動の範囲
公式はリネーム時の既定の挙動を「By default, Terraform interprets a change as an instruction to destroy the existing resource and create a new resource at the new address.」と説明しています。Terraformにとってアドレスが変わることは、別物になったことと区別が付きません。
これを移動として伝えるのがmovedブロックで、fromに旧アドレス、toに新アドレスを書きます。扱えるのはリソースのリネーム、モジュール内外への移動、モジュール自体のリネーム。ブロックを追加してplanを回すと、破棄と作成ではなく移動としてサマリに現れます。
stateを直接触るterraform state mvでも同じ結果を作れますが、こちらは手作業でありレビューにも残りません。movedブロックはコードとしてPRに載り、CIで同じ移動が再現される点で運用に向きます。stateそのものの構造と、やむを得ず直接操作する場合の手順はTerraform stateとは?tfstateの構造とS3バックエンド・移動削除の安全手順で扱いました。
countからfor_eachへ変換するときの索引の書き分け方
繰り返しの方式を変える作業は、movedブロックの真価が出る場面です。countで作った3つの実体は[0]から[2]の索引を持ち、for_eachへ移すとキー付きのアドレスへ変わります。宣言しなければ、3つとも破棄されて作り直されます。
公式は索引を含む移動について「When at least one of the two addresses includes an instance key, such as [“small”]」と記し、両側のアドレスを個別インスタンスとして解釈すると説明しています。fromに旧索引、toに新しいキーを書いたmovedブロックを、実体の数だけ並べれば対応付けが完了する。
逆に、単一のリソースをcountやfor_eachへ変える場合は、索引を持たないアドレスから[0]や特定のキーへの移動を1本書きます。ここで対応付けを誤ると、生きている実体が破棄対象になる。planのサマリで「to destroy」がゼロであることを、applyの前に必ず確認してください。
movedブロックを消してよい条件と連鎖させて残す判断基準の確認
適用が終わればブロックを消したくなりますが、公式の警告は明確です。「Removing a moved block is a breaking change because any configurations that refer to the old address will plan to delete the existing object instead of move it.」
判断は配布範囲で分かれます。単一の構成で、その構成を持つ全員が新しいアドレスでapply済みだと確認できるなら、削除して構いません。公開モジュールや社内で広く再利用されているモジュールでは残します。古い版から一気に上げてくる利用者がいる限り、履歴としてのmovedブロックが移行経路そのものだからです。
同じ実体を二度動かした場合の扱いも決まっています。公式は「we recommend chaining moved blocks to document the full change history」として、移動の連鎖を書き残すよう推奨。1本目のtoが2本目のfromになる形で並べておけば、どの版からでも最終アドレスへ到達できます。棚卸しでまとめて消したくなったときは、この経路を断つ操作だと理解したうえで判断してください。
lifecycleとdepends_onとmovedの使い分け判定基準
ここまでの内容を、現場で引ける形に畳みます。
毎回出る差分の症状から使うべき引数を引くための判定基準の一覧
症状を入口に、既定の対処を先に決めておきます。案件ごとに議論すると、急ぐ日ほど無視する側へ倒れるためです。
| 症状 | まず疑う原因 | 既定の対処 | 使わない選択肢 |
|---|---|---|---|
| 外部の自動化が値を変える | 実体側の書き換え | ignore_changesで属性指定 | allでの一括無視 |
| JSONやPEMが毎回差分 | APIによる正規化 | 正規化した形で記述 | ignore_changes |
| 書いていない属性に差分 | provider既定値の埋戻し | 既定値を明示的に記述 | allでの一括無視 |
| 毎回作成が提案される | stateに未登録 | importで取り込む | ignore_changes |
| 置き換えで停止時間が出る | 不変属性の変更 | create_before_destroy | 手作業での事前削除 |
| 消えると困る実体がある | 誤操作と設定削除 | prevent_destroyと多層防御 | 単独での削除保護 |
| 上流に連動させたい | 暗黙の依存 | replace_triggered_by | depends_on |
| 順序だけ担保したい | 値を参照しない依存 | 参照へ書き換える | moduleへのdepends_on |
| 名前や構成を変えたい | アドレスの変更 | movedブロックを追加 | 宣言なしのリネーム |
この表の「使わない選択肢」は、実際に選ばれがちで後から効いてくる手を並べています。とくに正規化と既定値の2行は、ignore_changesで消すと以後その属性の変更が永久に見えなくなるため、記述側を実体の表現へ寄せるほうが安全です。
lifecycleを使わずに設計側で直すべき場面の見分け方と判断条件
lifecycleの記述が増えている構成は、たいてい別の問題を抱えています。見分け方は単純で、1つのリソースに2つ以上のlifecycle引数が付き始めたら設計を疑ってください。
よくあるのが、Terraformと他のツールの責任分界が決まっていないケース。デプロイツールがタスク定義を更新し、オートスケーラが台数を動かし、運用チームがコンソールでタグを付ける構成では、ignore_changesが増え続けます。この場合に効くのは引数の追加ではなく、どの属性を誰が持つかを決めて分界線を引くこと。Terraformが持たない属性はコード上も書かない形へ寄せます。
もう1つが、環境差をlifecycleで吸収しようとして行き詰まる場面です。リテラル値しか書けない以上、この方向は成立しません。環境ごとの差はモジュールの引数として渡すか、構成そのものを分けて表現する。差分が読めないほど1回のplanが膨らんでいるなら、分割の粒度から見直すのが先です。
よくある質問
terraform lifecycleについて、実務で問い合わせの多い5点をまとめました。
prevent_destroyを付けたリソースをどうしても削除したいときは?
手順は、まずlifecycleからprevent_destroyの記述を外してapplyし、その後に破棄を実行します。保護の解除自体が1回のPRとして残るため、削除の意思が記録に残る点でも都合がよい。stateから外すだけで実体を残したい場合は破棄ではなくremovedブロックを使い、内側のlifecycleでdestroy = falseを指定してください。
ignore_changesを付けた属性は初回のapplyでも無視されますか?
いいえ、無視されるのは更新の場面だけです。作成時にはコードに書いた値がそのまま使われ、破棄も通常どおり実行されます。したがって「初回は意図した値で作り、その後の外部からの変更は追わない」という挙動になる。作成時の値まで制御したくない属性は、そもそもコードに書かない選択も検討してください。
lifecycleブロックはモジュール呼び出しにも書けますか?
create_before_destroyやignore_changesはresourceブロック専用で、module blockには書けません。preconditionとpostconditionはdataブロックとephemeralブロックでも使えますが、これもモジュール呼び出しへの記述とは別です。モジュール内のリソースに挙動を付けたい場合は、モジュール側の該当リソースへ記述するか、モジュールの引数で切り替えられる設計にします。
movedブロックを書いたのに破棄と作成の計画が出るのはなぜですか?
アドレスの綴りが実際のものと一致していない可能性が高いので、terraform state listで現在のアドレスを出力し、fromと文字単位で照合してください。countやfor_eachが絡む場合は索引やキーの表記も含めて一致が要ります。モジュール内のリソースはmodule.name.resource_type.nameの形で、入れ子であればその分だけ前置きが伸びます。
depends_onとlifecycleのreplace_triggered_byはどう使い分けますか?
目的が違います。depends_onは処理の順序だけを保証し、置き換えを起こしません。replace_triggered_byは参照先が変化したときに対象を作り直すためのもの。「相手より後に作りたい」なら前者、「相手が変わったら作り直したい」なら後者です。前者は可能な限り引数での参照に置き換え、後者は連鎖の範囲をplanで確認してから採用してください。
関連記事
- IaCとは?Infrastructure as Codeの仕組み・メリットと導入判断を解説:宣言的な構成管理の考え方と導入判断。
- terraform planの差分の読み方|init・apply・destroyの安全手順:差分の記号とサマリ行の読み順。
- Terraform stateとは?tfstateの構造とS3バックエンド・移動削除の安全手順:stateの構造と直接操作の手順。
- Terraform moduleの分割粒度とoutput設計|自作とRegistryの判断:依存を値で表す分割の設計。
- 冪等性とは?読み方・意味からAPI・IaCでの担保方法まで実装者向けに解説:同じ結果へ収束する性質の整理。