terraform planの差分の読み方|init・apply・destroyの安全手順
terraform planの出力を上から順に読んでいると、200行を超えたあたりで集中力が切れ、本当に見るべき1行を見落とします。planのレビューで事故が起きるのは、差分が読めないからではなく、読む順番と止める基準が決まっていないから。この記事では記号とサマリ行から危険な差分だけを先に拾う読み順を示し、差分の種類ごとに止めるか通すかを決める判定基準を表にまとめ、destroyと-targetと-auto-approveの運用ルールまで踏み込みます。バージョン依存の記述は2026年8月13日にHashiCorp公式ドキュメントとGitHub Releasesで確認した内容です。
まとめ:destroyとreplaceを先に探しCIで機械的に止める
結論を先に置きます。plan出力は上から読みません。最初に見るのは末尾のサマリ行「Plan: X to add, Y to change, Z to destroy.」で、Zがゼロでなければ該当箇所へ飛んで削除の内訳だけを確認する。この順序にすると、レビューにかける時間が差分の量ではなく危険度に比例します。次に探すのが「must be replaced」で、削除と再作成が同時に起きるため停止時間とデータ消失の両方が絡みます。
目視だけに頼る運用は遅かれ早かれ破綻します。terraform plan -out=tfplanでプランを保存し、-detailed-exitcodeの戻り値でCIを分岐させ、削除件数がゼロでないときは承認を挟む。ここまで仕組みに落として初めて、レビューは再現性を持ちます。-targetは公式が常用を推奨しておらず障害復旧などの例外に限る。-auto-approveは保存済みプランをapplyする場合を除き、本番では使いません。
terraform initからapplyまでの実行ライフサイクルと役割分担
役割分担が曖昧だと、planで検出できるはずのない問題をplanに期待してしまいます。
terraform initが担う3つの初期化とlockファイルの扱い
terraform initが担うのは、バックエンドの初期化、module blockが参照するモジュールのソース取得、providerのプラグイン導入の3つ。公式は「This command is always safe to run multiple times」と述べており、何度実行しても壊れません。
provider導入の後、Terraformは選択したバージョンを依存ロックファイルに書き出します。公式はこれをバージョン管理へコミットするよう求めており、理由は「Terraform will select exactly the same provider versions」の保証。コミットしていないと、CIとローカルで別バージョンのproviderが入り、planの差分が環境によって食い違います。バックエンドの差し替えでは-reconfigureが既存設定を無視し、-migrate-stateが既存stateを新しいバックエンドへ複製します。
fmtとvalidateをplanの前に置く分担と検出できる範囲
terraform validateは構文と内部整合性を検査しますが、クラウド側の実体には触れません。存在しないインスタンスタイプを書いてもvalidateは通り、planでも通り、applyで初めて落ちる場合があります。期待してよいのは、参照先の変数が未定義といった構成内部で閉じた誤りまで。
terraform fmtは整形のみで意味を検査しません。命名規約や非推奨な書き方、セキュリティ上まずい設定値まで拾うなら静的解析ツールを別に置きます。導入と設定はTFLintとは?Terraform静的解析の導入・設定・CI組み込み【v0.64.0対応】で扱いました。CIではfmtとvalidateと静的解析を先に並べ、すべて通ってからplanを実行する順序に。planはクラウドAPIを叩くため、前段で落とせる誤りを持ち込まないほうが待ち時間も短くなります。
planからapplyでstateが更新されるまでの実行の流れ
planは3つの入力を突き合わせます。設定ファイルの記述、stateに記録された前回の姿、クラウドAPIから取得した現在の実体。この3者の差から組み立てたものが実行計画です。宣言的な構成管理の考え方はIaCとは?Infrastructure as Codeの仕組み・メリットと導入判断を解説で整理しています。
applyはこの計画を実行し、成功したリソースの新しい姿をstateへ書き戻します。途中で失敗した場合、すでに作成が完了したリソースはstateへ記録され、その時点で処理が止まる。つまりstateは「途中まで進んだ状態」を正しく保持します。再実行すれば残りだけが処理されるので、慌てて手作業で消しに行かないでください。stateの構造と書き換え手順はTerraform stateとは?tfstateの構造とS3バックエンド・移動削除の安全手順にまとめています。
terraform planの出力記号とサマリ行から危険な差分を読む
読む順序を決めれば、出力が何行あっても判断にかかる時間は一定になります。
plan出力のサマリ行と記号から4種類のリソース操作を読み分ける
公式チュートリアルは「Resource actions are indicated with the following symbols: + create」と記し、サマリ行の例として「Plan: 4 to add, 0 to change, 0 to destroy.」を挙げています。覚える記号は4つ。+が作成、-が削除、~がその場での更新、-/+が削除してからの再作成、つまり置き換えです。
読む順序は次のとおりです。
- 末尾のサマリ行を見て、to destroy の件数を確認する
- ゼロでなければ「will be destroyed」で検索して内訳を確認する
- 続いて「must be replaced」を検索し、置き換え対象を洗い出す
- 残った
~の更新を、リソース種別ごとにまとめて確認する - 最後に
+の新規作成を、命名規約と課金単位の観点で確認する
理由は単純で、取り返しがつかないものから先に見るという一点。新規作成は誤っていても消せますが、削除と置き換えは戻せません。
must be replacedを見つけたときに確認する属性と回避策
置き換えが提案されたとき、Terraformは理由を出力に添えます。「forces replacement」という注記が付いた属性がそれで、変更できない属性を書き換えようとしたという合図。EC2インスタンスのAMI、RDSのエンジンバージョンの一部変更などが典型例です。
確認すべきは3点。第一に、その置き換えでデータが失われるか。データベースやEBSが対象なら、スナップショットと復元手順を先に固めます。第二に停止時間の長さ、第三に依存する下流リソースが連鎖して置き換わらないかです。該当ブロックの前後に、同じく置き換え対象のリソースが並んでいないか確認してください。
回避策として効くのがprevent_destroyです。lifecycleブロックにこれを付けると、削除や置き換えを含むplanがエラーで止まる。本番のデータベースやS3バケットには先に付けておき、意図的に置き換えるときだけ外す運用にします。
known after applyとsensitiveで差分が読めないときの扱い
(known after apply)は、applyしてみないと値が決まらない属性に付きます。新規作成されるリソースのIDやARNを他のリソースが参照している場合、参照側の属性がこの表示。数個なら無視して構いませんが、更新のはずのリソースに大量に出ているときは上流で置き換えが起きている可能性が高いので、上流を先に確認してください。
(sensitive value)は値が伏せられた表示で、変数やoutputにsensitiveを付けた場合に出ます。伏せられていても差分の有無は分かる。中身はterraform show -jsonでJSON化すれば読めますが、これは伏せた意味を消す操作です。公式は「Terraform plan files can contain sensitive data. Never commit a plan file to version control.」と警告しており、JSON化した出力の置き場と保持期間を先に決めてください。sensitiveでは値がstateに残る点と、その先の対処はterraform variableとlocals・tfvarsの使い分けでまとめています。
planを保存してCIで危険な差分を機械的に止める仕組みの作り方
目視のレビューは疲労と時間帯で精度が変わるため、判断の一部を機械へ移します。
-outで保存したplanをapplyへ渡す運用と平文で残る危険
公式は「you can apply the saved plan, and Terraform will only perform the changes listed in the plan」と説明しています。terraform plan -out=tfplanで保存しterraform apply tfplanで適用する形にすると、レビューした計画とapplyされる内容が確実に一致する。planとapplyの間に他人のapplyが挟まっても、保存済みプランは自分が見たものだけを実行します。
注意点が2つ。1つは、保存プランをapplyに渡すと確認プロンプトが出ないこと。公式は「when using a saved plan, you cannot specify any additional planning modes or options」とも記しており、後から-targetなどを足せません。もう1つが機密の扱いで、プランファイルには変数値やstateの内容が含まれます。CIのアーティファクトとして残す場合は保持期間と閲覧権限を絞り、リポジトリにはコミットしないでください。
-detailed-exitcodeの0と1と2でCIの分岐を組み立てる
CIで分岐を作る公式な入口が-detailed-exitcodeです。戻り値は0が変更なしの成功、1がエラー、2が変更ありの成功。通常のplanは変更の有無にかかわらず0を返すため、差分の有無でジョブを分けたいならこのオプションが要ります。
組み立て方はこうです。0ならapplyのジョブをスキップし、レビュー依頼も飛ばさない。1なら即座に失敗させる。2のときだけ保存したプランを成果物として残し、承認待ちへ進める。差分の中身で止めたい場合はterraform show -json tfplanの出力を機械的に検査する層をもう1段足す。JSONにはリソースごとのactionsが配列で入っているため、deleteを含むかどうかで判定できます。
plan差分の種類ごとに止めるか通すかを決める判定基準の一覧表
差分の種類ごとに既定の扱いを先に決めます。個別案件ごとに議論すると、急いでいる日ほど緩い側へ倒れるためです。
| 差分の種類 | 記号や表示 | 既定の扱い | 追加で確認する点 |
|---|---|---|---|
| リソースの新規作成 | + create | 通す | 命名と課金の単位 |
| 属性のその場更新 | ~ update in-place | 通す | sensitive属性の有無 |
| リソースの削除 | – destroy | 止める | 実体とデータの退避 |
| 削除して再作成 | -/+ must be replaced | 止める | 停止時間と依存先 |
| IAM権限の拡張 | ~ で statement 追加 | 止める | 付与範囲と信頼関係 |
| countやfor_eachの変化 | 複数行の + と – | 止める | 添字ずれの連鎖 |
| 値が未確定の属性 | known after apply | 条件付きで通す | 上流の置き換え有無 |
「止める」は却下ではなく、自動承認から外して人間の承認を要求するという意味です。運用に乗せるなら、各行をCIの検査条件へ翻訳する。削除と置き換えはJSONのactionsで判定でき、IAMの権限拡張はリソース種別による絞り込みで拾えます。countやfor_eachの添字ずれは、リストの途中に要素を挿入したときに後続すべてが置き換わる現象。キーを持つfor_eachへ寄せておくと事故自体が起きにくくなります。
destroyとtargetとauto-approveの危険操作を止める運用ルール
曖昧にすると、例外のはずの操作が既定の手順として定着します。ここは言い切ります。
destroyはapply -destroyの別名でplanで先に見る手順
公式はterraform destroyについて「This command is just a convenience alias for the following command: terraform apply -destroy」と記しています。特別なコマンドではなく、すべてのリソースを削除する計画を立てて適用するだけの別名。事前に影響範囲を見るにはterraform plan -destroyを使います。
運用ルールは、本番の作業ディレクトリでdestroyを実行できる経路そのものを塞ぐことです。CIのジョブに含めず、実行権限を持つロールを日常の作業用ロールと分ける。加えて、消えると困るリソースにはprevent_destroyを付けます。検証環境で使い捨てる場合は話が別で、そこはdestroyを前提にした運用が成立する。環境ごとの分離方式の選び方はterraform workspaceの使い方と環境分離の判断基準|分割方式の比較で比較しました。
-targetを例外に限定する運用条件と常用してはいけない理由
公式の表現は明確です。「It is not recommended to use -target for routine operations, since this can lead to undetected configuration drift and confusion about how the true state of resources relates to configuration.」とし、この機能は「provided for exceptional circumstances」と位置づけられています。
-targetを付けたplanは指定したリソースとその依存だけを対象にするため、他の差分が評価されません。結果として構成ファイルとクラウドの実体がずれたまま放置され、次に全体planを回した誰かが大量の差分に出くわす。しかも、そのずれがいつ生まれたのかを追う手掛かりが残りません。
許容する条件は3つ。applyが途中で失敗して特定リソースだけ再試行する場合、依存順を一時的に強制する場合、誤った変更を局所的に巻き戻す場合です。いずれも作業後に対象を外した全体planを回し、差分が空であることを確認するまでを一続きの手順にします。CIのジョブに-targetが書かれていたら例外の常用化なので、設計を戻してください。
-auto-approveを許してよい条件と許してはいけない条件
公式は-auto-approveについて「we recommend making sure that no one can change your infrastructure outside of your Terraform workflow.」と条件を添えています。裏を返すと、手作業でのコンソール操作が残っている環境では前提が崩れます。
許してよいのは2つ。使い捨ての検証環境で、消えても業務影響がないとき。もう1つが保存済みプランをapplyへ渡す運用で、この形なら承認はプラン作成の時点で完了しており、-auto-approveを明示する必要すらありません。
許さないのは、本番を含む環境で構成ファイルから直接applyする経路です。とくにmainブランチへのマージをトリガーにterraform apply -auto-approveを走らせる構成は、レビュー時点のplanと実行時のplanが一致する保証がない。マージからapplyまでの間に、別のPRのマージやクラウド側の変更が挟まりうるためです。こうしたCIとインフラの設計を含む構築や見直しは、インフラ構築(AWS・Google Cloud・Azure)として請け負っています。
refreshの非推奨とドリフト検知をplanで扱うときの手順
コマンド一覧で見かけるterraform refreshは、現在では新規に使う対象ではありません。位置づけを整理します。
terraform refreshが非推奨になった理由と置き換え先
公式は「This command is deprecated. Instead, add the -refresh-only flag to terraform apply and terraform plan commands.」と明記しています。terraform refreshは実質的にterraform apply -refresh-only -auto-approveと同等で、確認なしにstateを書き換える。設定に誤りがあると、管理対象がstateから消えて実体だけが取り残されます。
置き換え先は2つ。差分だけ見たいならterraform plan -refresh-onlyを実行する。stateを実体へ合わせたいならterraform apply -refresh-onlyを使い、対話的な確認を経てから反映します。手でコンソールから変更されたリソースを検出する定期ジョブなら、前者を日次で回してdriftの有無だけを通知する形が扱いやすい。実体はあるがstateに無いリソースを管理下へ入れる工程はterraform importで既存リソースをコード化する手順にまとめました。
-refresh=falseで速度を取ってよい場面と取れない場面
planが遅い原因の多くは、リソース1件ごとにクラウドAPIへ現在の姿を問い合わせる工程にあります。-refresh=falseを付けるとこの同期を省けるため、数百リソースの構成では体感が変わる。並列度は-parallelism=nで調整でき、既定値は10です。
使ってよいのは、直前にplanを回したばかりで構成ファイル側だけを触った場合と、変更前後を見比べる目的で短時間に何度も回す場合だけ。逆に使ってはいけないのが、レビュー対象として提出するplanとapplyの直前に回すplanです。stateが古いまま組み立てられた計画は、実体とかけ離れた内容になりえます。速度が問題なら分割粒度を見直してリソース数を減らすほうが筋がよく、判断基準はTerraform moduleの分割粒度とoutput設計|自作とRegistryの判断で扱っています。
よくある質問
terraform planについて、実務で問い合わせの多い5点をまとめました。
terraform planを実行するとクラウド側に何か変更が起きますか?
リソースの作成や変更は起きませんが、完全な読み取り専用でもありません。planは既定で各リソースの現在の姿をAPIから取得し、その結果は通常のplanではstateへ永続化されない。一方でstateロックは取得するため、同じstateに対する他の実行とは競合します。CIで複数のジョブが同時にplanを回す構成では、ロック待ちのタイムアウトを見込んでください。
planでは差分が出なかったのにapplyで失敗するのはなぜですか?
planが評価できるのは、設定とstateとAPIから取得した属性の3者までです。クォータ超過、IAM権限の不足、リージョンでの提供状況、他システムが同時に加えた変更は計画段階で判定されません。失敗しても途中まで進んだstateは残るため、原因を潰して再実行する手順が基本です。
plan出力が長すぎてレビューできないときはどうすればよいですか?
まず作業ディレクトリあたりのリソース数を疑ってください。1回のplanで数百件が動く構成は分割の粒度が粗すぎます。当面の対処は、terraform show -jsonでJSON化し、actionsにdeleteやreplaceを含むリソースだけを抽出して人間に見せる仕組みを挟むこと。-targetで出力を絞るのは、レビューを楽にする目的では使わないでください。
terraform validateとplanはどちらもCIに入れる必要がありますか?
両方入れます。validateはクラウドの認証情報なしで数秒で終わるため、PR作成の直後に回して構文と参照の誤りを弾く役割。planはクラウドAPIへの到達と認証が要るので後段に置きます。前段でvalidateを通していれば、planのジョブが単純な記述ミスで落ちる回数が減り、CI全体の実行時間も短くなります。
OpenTofuでもplanのオプションや出力の見方は同じですか?
基本的な体系は共通で、記号やサマリ行の形式、-outや-detailed-exitcodeといったオプションも同様に使えます。ただし両者は分岐後に独自の機能追加を続けており、細部の挙動が一致しない箇所が生まれている。採用判断の材料はOpenTofuとは?Terraformとのライセンス・機能差と移行判断を実装視点で解説で整理しました。
関連記事
- IaCとは?Infrastructure as Codeの仕組み・メリットと導入判断を解説:宣言的な構成管理の考え方と導入判断。
- Terraform stateとは?tfstateの構造とS3バックエンド・移動削除の安全手順:stateの構造と書き換え手順。
- TFLintとは?Terraform静的解析の導入・設定・CI組み込み【v0.64.0対応】:planの前段に置く静的解析層の導入。
- terraform workspaceの使い方と環境分離の判断基準|分割方式の比較:環境分離方式の比較と選び方。
- Terraform moduleの分割粒度とoutput設計|自作とRegistryの判断:分割粒度の見直し方の判断基準。