Terraform状態管理とドリフト・ロックの原理
apply が壊す・固まる理由を state の仕組みから解消。宣言と実体を結ぶ state ファイル、plan の差分計算とリフレッシュ、並行実行を止める状態ロック、手動変更によるドリフトの検知と是正を原理から理解できます。
- 1.Terraform は『設定(あるべき姿)』『state(前回把握した実体)』『実クラウド(今の実体)』の三者を突き合わせる。state は実体への参照とメタデータを保持する写像であり、これが無いと宣言とリソースを対応づけられない。
- 2.plan は (1) refresh で実体を読み state を更新し (2) 設定と state の差分を計算する二段階。apply は差分どおりに API を呼ぶ。リフレッシュ結果と設定のズレがドリフトとして現れる。
- 3.並行 apply は state を破壊するため、バックエンドが state ロックで直列化する。ロックは相互排他であり、失効や強制解除を誤ると state 破損や二重操作を招く。
state がなぜ必要なのか
Terraform に代表される宣言的 IaC は「あるべき姿(設定ファイル)」を書けば、ツールが実体をそこへ収束させてくれます(/devops/iac/)。だが宣言的であることと、ツールが実体を正しく操作できることは別問題です。設定には「EC2 を1台」「このセキュリティグループ」と書いてありますが、その記述と クラウド上に実在する具体的なリソース(インスタンスID i-0abc... など) を結びつける情報がどこかに必要です。これを担うのが state ファイル です。
state は、設定上の論理アドレス(例 aws_instance.web)と、実クラウドの物理リソース(プロバイダ固有のID・属性のスナップショット)との 写像(マッピング) を保持します。さらに各リソースの最後に観測した属性値、依存関係、プロバイダ情報、リソースの作成・更新を一意に追う内部メタデータを記録します。state が無ければ、Terraform は「aws_instance.web が既に存在するのか、新規作成すべきか」すら判断できず、毎回ゼロから作ろうとしてしまいます。
state を単なる性能用キャッシュと捉えると誤ります。クラウドAPIを全件問い合わせれば実体は分かりますが、Terraform は どの実リソースが自分の管理下にあるか をAPIだけからは判別できません(同種のリソースが他チームの手で多数並んでいるため)。state は「自分が管理する実体はこれだ」という所有権の台帳であり、削除や置換の対象を確定させる根拠です。
plan の二段階:リフレッシュと差分計算
terraform plan は一見すると「設定と実体を比べて差分を出す」だけに見えますが、内部では明確に二段階を踏みます。
plan = リフレッシュ(実体観測)→ 差分計算(desired と current の比較)
段階1 リフレッシュ:
state に記録された各リソースについて、プロバイダのRead APIを呼び
クラウドの「今の属性値」を取得し、state のスナップショットを更新する
段階2 差分計算:
desired = 設定ファイル(あるべき姿)
current = リフレッシュ後の state(把握した実体)
両者を属性ごとに突き合わせ、create / update / replace / delete を決定
段階1(リフレッシュ) で、Terraform は state に載っている各リソースを実際にクラウドへ問い合わせ、属性を最新化します。ここで「state には停止中と書いてあるが実体は起動中」のように、前回 apply 以降に外部で起きた変化 が state に反映されます。段階2(差分計算) では、リフレッシュ後の state(=今の実体の把握)と、設定ファイル(=あるべき姿)を属性レベルで比較し、必要なアクションを決めます。
属性ごとの判定:
設定にあり実体に無い → create
設定に無く実体(state)にある → delete
両方にあり属性が一致 → 変更なし(no-op)
両方にあり属性が相違 → update(その場更新)または replace(破棄再作成)
update と replace の分岐は重要です。多くの属性はその場で更新(in-place)できますが、一部の属性(例:可用性ゾーンやある種の不変フィールド)は 変更不可(ForceNew) とプロバイダが宣言しており、これらを変えると Terraform は「破棄して作り直す(replace)」を計画します。意図せぬ replace はダウンタイムやIDの変化を招くため、plan の -/+(destroy and create replacement)表示は最も注意して読むべき箇所です。
大規模 state ではリフレッシュのAPI呼び出しが plan の所要時間の大半を占めます。-refresh=false で省略すると速くなりますが、その plan は state を最後に観測した時点の実体 を前提に差分を出すため、外部変更を見落とします。速度のために盲目になる取引であり、本番適用前の最終 plan では原則リフレッシュを有効にすべきです。
ドリフト:宣言と実体が乖離する構造
ドリフト(drift) とは、Terraform を介さずクラウドに加えられた変更によって、実体と state(および設定)が食い違った状態 を指します。コンソールでの手動変更、別ツールの操作、クラウド側の自動修復などが原因です。ドリフトは「設定 vs state」のズレではなく、「state が把握していた値 vs 実体の今の値」のズレ として、リフレッシュの段階で初めて顕在化します。
正常: 設定 == state == 実体 (三者一致)
ドリフト発生:
実体だけが手動変更で書き換わる
→ 設定 == state ≠ 実体
plan のリフレッシュ後:
state が実体に追従して更新される
→ 設定 ≠ state(=実体) ← この差分が「ドリフトの検知」として表示される
ここが原理の肝です。ドリフトを検知できるのは、plan がまずリフレッシュで state を実体に合わせ、その上で 設定と比較する からです。つまり Terraform から見たドリフトの是正とは「実体を設定(あるべき姿)へ戻す」ことであり、apply を流せば手動変更は 設定に書かれた値で上書き されます。設定に書いていない属性(プロバイダ既定やクラウド側付与の値)は管理対象外なので、ドリフトとして扱われません。
| 事象 | 三者の状態 | plan の挙動 | apply の結果 |
|---|---|---|---|
| 手動で属性変更 | 設定 ≠ 実体、state は追従 | リフレッシュ後に差分表示 | 設定値で実体を上書き是正 |
| 手動でリソース削除 | 実体が消失 | 再作成(create)を計画 | リソースを作り直す |
| 手動でリソース追加 | 管理外の実体 | Terraform は無関心 | import しない限り触らない |
| 設定に無い属性をクラウドが変更 | 管理対象外 | 差分に出ない | 放置(is not managed) |
検知を運用に組み込むなら、terraform plan -detailed-exitcode が有効です。終了コードで「差分なし/差分あり/エラー」を区別できるため、定期実行してドリフトを継続監視できます。手動変更そのものを根絶したいなら、設定を唯一の真実とし変更を Git 経由に限定する GitOps 的規律(/devops/gitops/)が効きます。
ドリフトや import の不整合を terraform state rm や手書きでの state 編集で「とりあえず合わせる」のは危険です。state は実体との写像であり、写像だけ書き換えても実体は変わりません。state からリソースを消せば Terraform は「管理外」とみなして二度と触らなくなり(孤児リソース)、逆に誤った物理IDを書けば次の apply が 別人のリソースを破壊 しかねません。state 操作は実体との対応を常に意識して行う必要があります。
状態ロック:並行 apply を直列化する
state は「読んで→差分を決めて→書き戻す」というリードモディファイライトの対象です。二人が同時に apply すると、互いの更新を上書きして state が壊れる、あるいは同じリソースに二重に操作を仕掛けて実体が不整合になります。これは古典的な競合(レースコンディション)であり、解決策は state ロックによる相互排他 です。
apply(および state を書き換える操作)の前に、Terraform はバックエンドに対して 排他ロック を取得します。ロックを保持している間だけ state を操作でき、他のプロセスがロックを取れなければ待つか失敗します。これは分散ロックそのものの応用で、リースや所有者識別子を伴います(/devops/distributed-locking/)。
apply のロック手順(概念):
1. バックエンドにロック取得を要求(ロックID・操作者・時刻を記録)
2. 取得成功 → state を読み、plan を確定し、API を実行
3. 結果を state に書き戻す
4. ロックを解放
取得失敗(他者が保持)→ エラーで停止(待機しない)
バックエンド別の実装:
S3 (1.10+) : S3 の条件付き書き込み(If-None-Match。無ければ作成)でロックファイルを排他取得
S3 + DynamoDB : DynamoDB の条件付き書き込みで排他(従来方式。1.11 で非推奨化)
Terraform Cloud: 実行キューで直列化
GCS / Azure : オブジェクトのリース/ロックAPI
ロックの中身は単なる「鍵」ではなく、誰が・いつ・どの操作で握っているか のメタデータを持つレコードです。これにより、ロック衝突時に「誰の実行とぶつかったか」を提示できます。ロックは 同一 state に対する相互排他 なので、別の state(別ワークスペース・別ディレクトリ)であれば並行に動けます。state を適切に分割しておくことが、ロック競合による直列化の痛みを減らす設計上の手当てになります。
プロセスがクラッシュしてロックが残ると、後続の apply が永久にブロックされます。terraform force-unlock <LOCK_ID> で強制解除できますが、これは 本当に誰も実行中でないと確信できるときだけ に限ります。実際には別プロセスが apply 継続中なのにロックを剥がすと、ロックが守っていた相互排他が崩れ、二重操作や state の上書き破損が起こります。ロックIDを必ず照合し、対象の実行が本当に死んでいるかを確認してから解除してください。
冪等性との関係:なぜ何度流しても同じになるか
宣言的 IaC の売りである 冪等性——同じ設定で何度 apply しても結果が同じ——は、ここまでの仕組みの帰結です(/devops/idempotency-exactly-once/)。一度収束した後の plan は、リフレッシュした実体と設定が一致するため差分ゼロ(no-op)となり、apply しても何も起きません。
収束後の状態:
設定 == state(=リフレッシュ後の実体)
→ 差分計算の結果は空 → apply は API を一切呼ばない(冪等)
逆に言えば、冪等性が崩れて「apply するたびに差分が出続ける(perpetual diff)」現象は、設定値と実体の正規化が一致していない ことのサインです。プロバイダがクラウド側で値を正規化する(大文字小文字・順序・既定値の補完)のに、設定にはその正規化前の値が書かれていると、差分計算が毎回「不一致」と判定し続けます。これは state の写像やリフレッシュ結果の読み方を理解していれば原因を切り分けられます——差分が出ているのは設定と実体のどの属性か、それは ForceNew か、リフレッシュで実体側がどう正規化されているか、を順に確認するのが定石です。
まとめ
- state は宣言と実体を結ぶ台帳。論理アドレスと物理リソースの写像・属性スナップショット・メタデータを保持し、これにより Terraform は新規作成か既存更新かを判断できる。キャッシュではなく所有権の根拠。
- plan は二段階。段階1のリフレッシュで実体を読み state を最新化し、段階2で設定(desired)と比較して create/update/replace/delete を決める。replace(ForceNew)は破棄再作成を伴うため最注意。
- ドリフトはリフレッシュで顕在化する。手動変更で実体だけがズレ、リフレッシュで state が追従、設定との差分として表示される。apply は設定値で実体を上書き是正する。
- 状態ロックは並行 apply を直列化する相互排他。バックエンドが排他ロックを実装し、同一 state への同時書き込みを防ぐ。force-unlock は実行が本当に死んでいると確認できる時のみ。
- 冪等性は仕組みの帰結。収束後は差分ゼロで apply が無操作になる。永続的な差分は設定値と実体の正規化不一致のサインで、写像・リフレッシュの原理から切り分けられる。
DevOps/インフラ Article
Terraform状態管理とドリフト・ロックの原理を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Terraform
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6
導入後に効く点
plan は (1) refresh で実体を読み state を更新し (2) 設定と state の差分を計算する二段階。apply は差分どおりに API を呼ぶ。リフレッシュ結果と設定のズレがドリフトとして現れる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「Terraform / IaC」に近いか確認する。
- 強みである「Terraform は『設定(あるべき姿)』『state(前回把握した実体)』『実クラウド(今の実体)』の三者を突き合わせる。state は実体への参照とメタデータを保持する写像であり、これが無いと宣言とリソースを対応づけられない。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。