「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」第5部まとめブログ:Terraformライフサイクル破壊編で明らかになった「認識の不可視性」の構造
🗺️ シリーズ全体の位置づけを知りたい方はこちら
本記事は「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」シリーズ(全50回、5部構成)のうち、第5部(第41〜50回)のまとめ記事です。
シリーズ全体(第1〜5部)の関係については、以下の統合ブログで整理しています。→ 「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」シリーズ統合ブログ ※近日公開予定
📋 目次
0. このブログの使い方
1. はじめに
2. 第5部の条件マップ:不可視性はどう破壊に転じるか
3. 第41〜49回の事象と条件マップの対応関係
4. 「結果としての消失」の収束構造
5. 第47〜49回の対処アプローチの位置づけ
6. シリーズ完結、統合ブログへの接続
7. 初めて読む方へ
8. 第5部 各回リンク
0. このブログの使い方
このブログは、「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」シリーズの第5部(第41〜50回)の全体像を把握するための記事です。
- 第5部(Terraformライフサイクル破壊編)の全体像を先に把握してから各回を読みたい方、または各回を読んだ後に全体を俯瞰したい方は、このまま読み進めてください
- シリーズ全体(第1〜5部)の位置づけを把握したい方は、「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」シリーズ統合ブログ(※近日公開予定) をご覧ください
- 各回の詳細なトラブル内容、実機検証の手順を確認したい方は、第8節 の各回リンクから該当回に進んでください
1. はじめに
TerraformとAnsibleの連携は、自動化とCI/CDさえ整えれば安定すると思っていないでしょうか。
実際、疎結合なパイプライン設計、E2Eの自動テスト、定期的なドリフト検知といった仕組みを整えれば、terraform applyやansible-playbookの実行はより安全に、より効率的になります。しかし、コードをほんの少し書き換えてapplyし直しただけで、コンテナがまるごと作り直され、Ansibleが設定したはずの内容が跡形もなく消えている、という事態は、こうした仕組みを整えていても起こり得ます。
このとき起きているのは、パイプラインの不備ではありません。Terraformが管理しているのはリソースの属性だけであり、Ansibleがコンテナ内部に投じた設定やデータは、その管理範囲の外側にそのまま存在し続けます。この認識の範囲は、パイプラインをどれだけ自動化、効率化しても変わりません。そのため、リソースが破棄、再生成されるという特定の操作をきっかけに、この消失が初めて具体的な問題として表面化します。
そこで、第5部の話に入る前に、第4部 までで扱ってきた内容を振り返ります。
第4部(第31〜40回)で扱ったのは、TerraformとAnsibleの連携を自動化、効率化するための仕組みでした。terraform outputを介した疎結合なパイプライン設計、GitHub ActionsによるE2E自動テスト、triggersによるAnsible実行要否の制御、Testinfraによる独立した状態検証、モジュールとロールの共通化、キャッシュによる実行時間の短縮、定期実行によるドリフトの自動検知、仕様書の自動生成。いずれも、同じ問題を繰り返し人手で調査する状況を減らし、開発、運用のサイクルをどう改善するかを扱っており、ここでは「どう自動化し、どう改善するか」を扱ってきました。
一方、第5部(第41〜50回)では、この視点が変わります。第4部 までがパイプラインという外側の仕組みを扱っていたのに対し、第5部は、TerraformのState管理そのものが持つ構造的な限界に踏み込みます。Terraformがそもそも何を認識し、何を認識していないのかという根本の構造から出発し、その不可視性がどのような条件で「破壊」に転じるか、そしてそれにどう向き合うかを、第41回から第50回まで扱っています。
このブログは、「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」という全50回、5部構成のシリーズのうち、第5部(第41〜50回、Terraformライフサイクル破壊編)を整理するまとめ記事です。第41〜49回で扱った個別の障害と対処の詳細な実機検証手順やコード例には踏み込まず、それらが「不可視性、トリガー、結果、対処」という一つの構造のどこに位置づけられるかを整理し直し、対応関係として示します。あわせて、第44回 と 第45回 が、異なるトリガーから同じ「データ消失」という結果に収束する構造についても整理します。各回の詳細なトラブル内容、実機検証の手順には踏み込みません。詳細を確認したい方は、第8節 の各回リンクから該当回に進んでください。
- これから第5部を読み始める方にとっては全体像を先につかむための地図として
- すでに各回を読まれた方にとっては読んだ内容を俯瞰して整理し直すための記事として
どちらの立場でも読み進めていただける内容になっています。
次のセクションでは、第5部の範囲を貫く条件マップを図で整理します。
2. 第5部の条件マップ:不可視性はどう破壊に転じるか
第1部から第4部のまとめブログでは、それぞれの部の範囲を俯瞰するための独自の図解を用いてきました。第1部 の処理フロー、第2部 の運用フロー 、第3部 の原因特定プロセス、第4部 のパイプライン工程です。これに対し第5部では、これらとは性質の異なる図を使います。第5部が扱うのは、一連の処理の流れそのものではなく、第41回 で整理した「認識の不可視性」という一つの構造が、どのような条件のもとで表面化し、どう対処されるかという条件の関係です。ここでは、この関係を条件マップと呼び、図で整理します。
【条件マップ】
【起点】
Terraformの認識範囲の外にAnsibleの変更がある(不可視性)
↓
【トリガー】
├─ 属性変更、lifecycle設定
└─ 依存リソースの変更
↓
【結果】
→ Ansibleが投じた設定、データの消失
↓
【対処】
├─ データを守る
├─ 境界を動かす
└─ 侵犯を検知して止める
不可視性そのものは、Terraformが管理するリソースが存在し続けている限り、通常運用では実害を生みません。この不可視性が「破壊」に転じるのは、何らかのトリガーによってリソースが破棄、再生成される場合です。トリガーが働くと、Ansibleが投じた設定やデータは保護も引き継ぎもされずに消失し、この結果に対して、データを守る、境界そのものを動かす、侵犯を検知して止めるという、異なる段階に効く3つの対処が存在します。
第41〜49回のどの回がこの条件マップ上のどこに対応するかは、次のセクションで一覧にして整理します。ただし、第43回、第46回 の2回は、この起点、トリガー、結果、対処という主要な流れにそのまま乗るものではなく、性質の異なる回です。この2回の位置づけについても、次のセクションであわせて整理します。
3. 第41〜49回の事象と条件マップの対応関係
第2節 で示した条件マップの各ノードに、第41〜49回のどの回が対応するかを一覧にします。第50回 は個別の技術的な事象ではなく第5部の総括の回であり、この一覧には含めません。各回の詳細な検証内容には踏み込まず、各回へのリンク(第8節)で示すにとどめます。
| 回 | 事象 | 条件マップ上の位置 |
|---|---|---|
| 第41回 | Terraformの認識範囲とAnsibleの変更の乖離 | 起点(不可視性) |
| 第42回 |
create_before_destroy、replace_triggered_byによる強制再生成 |
トリガー |
| 第43回 |
prevent_destroyとAnsible再実行要求の衝突 |
トリガーではなく、対策自体が別種の障害に置き換わったケース |
| 第44回 | 再生成に伴うデータの全喪失 | 結果 |
| 第45回 | 依存リソースの変更による連鎖的再生成 | トリガー、かつ結果(詳細は 第4節) |
| 第46回 | プロビジョニング事故によるState不整合 | 不可視性は表面化するが、消失には至らなかった対照例 |
| 第47回 | Volume分離によるデータ保持 | 対処(結果への対処) |
| 第48回 | イミュータブル運用への転換 | 対処(トリガーの発生自体を減らす) |
| 第49回 | パイプラインガードによる自動ブロック | 対処(トリガー発生時点での検知) |
この対応関係のうち、第43回 と 第46回 は、起点、トリガー、結果、対処という主要な流れに単純には収まらない、性質の異なる2回です。
第43回 は、第42回 で確認した消失を防ぐためにprevent_destroyを設定した回です。この対策は、破棄を伴う変更を一律で拒否することで消失そのものは防ぎますが、意図しない誤破壊と意図した再構築の要求を区別しないため、デプロイパイプラインそのものが詰まるという、消失とは別種の障害を引き起こします。第43回 は、新たなトリガーを示す回ではなく、トリガーへの対策が別の障害に置き換わっただけであったことを示す回です。
第46回 は、Ansibleのプロビジョニング実行中に発生する接続切断やプロセス停止という事故を扱った回です。この事故によってtaintedとしてマークされるのは、local-execプロビジョナーを保持するnull_resource自体であり、プロビジョニングの対象であるコンテナは、事故の前後で一切影響を受けません。tainted化という、一見「破壊」に見える事象が起きても、それが必ずしもコンテナ側のデータ消失に直結するとは限らないことを、第46回 は示しています。
第45回 は、条件マップ上でトリガーと結果の両方に位置づけられる回です。この回が示す収束の構造については、次のセクションで改めて整理します。
4. 「結果としての消失」の収束構造
第3節 で触れた通り、第44回 と 第45回 は、いずれも条件マップ上の「結果」に位置づけられます。このセクションでは、この2回がどのような経路でその結果に至ったのかを掘り下げます。
第44回 の実機検証で再生成のきっかけとして使われたのは、第42回 から引き続き設定されていたreplace_triggered_byです。tls_private_keyをtaintし、参照元のコンテナ群を再生成させることで、Ansibleが投じたパッケージとデータベースのレコードが失われることを確認しています。
一方、第45回 で使われたトリガーは、これとはまったく別の経路です。変更したのはコンテナではなく、コンテナが接続しているネットワークの属性でした。networks_advancedがネットワークのidを参照している構成のもとでは、ネットワーク自体が再生成されてidが変わると、直接は変更していないコンテナ群まで再生成の対象になります。この、依存リソース経由という、第42回 とは異なるトリガーによっても、第44回 と同じ、Ansibleが投じたデータの全喪失が独自に発生することが、第45回 で確認されています。
第42回のトリガー(replace_triggered_by)─┐
├─→ データ消失(第44回で実機確認)
第45回のトリガー(依存リソースの再生成)─┘ (第45回でも独自に実機確認)
トリガーに至る経路は異なっていても、行き着く先が同じ結果になるのは偶然ではありません。第41回 で整理した通り、tfstateはコンテナ内部のOSレイヤーの状態を観測対象に含めていません。この観測範囲の外側にAnsibleが投じたデータが存在し続けている限り、そのデータを抱えるコンテナがどのような経路で再生成対象になったとしても、結果として起きることは変わりません。コンテナが直接の変更対象であったか、依存リソースを経由して巻き込まれただけかという違いは、tfstateの観測範囲という一点の前では意味を持たないためです。
5. 第47〜49回の対処アプローチの位置づけ
第2節 の条件マップで示した通り、不可視性そのものへの対処には、データを守る、境界を動かす、侵犯を検知して止めるという3つのアプローチがあります。第47回 から 第49回 で扱ったこの3つが、条件マップ上のどの段階に効くかを整理します。
| 対処 | 該当回 | 効く段階 | 限界 |
|---|---|---|---|
| データを守る | 第47回 | 結果(消失)そのものへの対処 | 保護されるのは明示的にマウントした範囲のみで、それ以外は従来通り失われる |
| 境界を動かす | 第48回 | トリガーの発生自体を減らす対処 | 強制再生成による設定消失は解消するが、稼働後に生成されるデータの消失には及ばず、設定変更のたびに再ビルドが必要になる運用コストを伴う |
| 侵犯を検知して止める | 第49回 | トリガーが発生した瞬間に検知する対処 |
第43回 のprevent_destroyのような恒久的な禁止ではなく、着脱可能な一時的チェック工程にとどまる |
第47回 は、docker_volumeをコンテナとは独立したリソースとしてマウントし、コンテナが再生成されてもVolume自体は影響を受けないことを実機で確認しています。ただし、保護されるのはVolumeのマウントパスとして明示的に指定した範囲に限られ、それ以外の場所に投じられたパッケージ等は、これまで通り失われます。
第48回 は、Ansibleの実行タイミングを、コンテナ起動後からイメージのビルド時へと前倒しする転換です。設定の焼き込み先がdocker_imageというコンテナとは別のリソースに移ることで、第42回 で確認した強制再生成による設定消失は実質的に解消されます。ただし、この効果が及ぶのは静的な設定に限られ、稼働後に生成されるデータの消失には及びません。設定を変更するたびにイメージの再ビルドが必要になるという、新しい運用コストも生まれます。
第49回 は、terraform planの結果をJSON形式で解析し、危険な変更が計画された時点でCI/CDパイプラインのジョブを止める仕組みです。第43回 で扱ったprevent_destroyが、コードに組み込む恒久的で無条件の制約であったのに対し、このガードはCI/CDワークフロー側に置かれた着脱可能な一時的チェック工程にとどまります。
この3つの対処は、いずれも不可視性そのものを解消しているわけではありません。データの置き場所、設定の焼き込み先、あるいは検知の仕方を変えているにすぎず、互いに排他的な選択肢でもありません。実際の運用では、複数の対処を組み合わせて使うことになります。
6. シリーズ完結、統合ブログへの接続
第2節 から 第5節 まで、第5部を条件マップという軸で俯瞰してきました。第1部から第4部までのまとめブログは、いずれもこのセクションで次の部への接続を示してきましたが、第5部(第41〜50回)はシリーズの最終部にあたるため、ここで示すのは次部への接続ではありません。
第1部(環境構築、連携編) → 第2部(運用、ライフサイクル編) → 第3部(トラブルシューティング、デバッグ編)
→ 第4部(改善、CI/CD自動化編) → 第5部(Terraformライフサイクル破壊編)
→ シリーズ完結
第41回から第50回にかけて、TerraformのState管理が持つ構造的な限界という、第4部 までとは異なる視点から連携の問題を整理してきました。このブログでは、その全体像を「不可視性、トリガー、結果、対処」という条件マップとして俯瞰しましたが、より詳細な総括、すなわち第42回から第46回それぞれがこの不可視性のどのような局面に対応するかという整理、そして第47回から第49回の対処が互いにどう補い合うかという整理は、第50回 に譲ります。
第1部から第5部までを通じて、「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」というシリーズは、全50回で完結しました。シリーズ全体を貫く構成、各部の位置づけ、冪等性シリーズ、ドリフトシリーズ、Moleculeシリーズとの関係を俯瞰したい方は、以下の統合ブログをご覧ください。
→ 「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」シリーズ統合ブログ ※近日公開予定
7. 初めて読む方へ
ここまで、第5部の条件マップと、そこで発生する事象の全体像を見てきました。実際にどの回から読み進めるか迷っている方は、関心に応じて以下を参考にしてください。
- TerraformとAnsibleが、それぞれ何を見ていて何を見ていないのか、根本から知りたい → 第41回
- HCLのコードをほんの少し書き換えただけなのに、コンテナがまるごと作り直され、Ansibleの設定が消えてしまう → 第42回
-
設定の消失を防ごうと
prevent_destroyを設定したら、今度はデプロイそのものが詰まってしまった → 第43回 - 消えるのは設定ファイル程度ではなく、稼働後に投入したデータベースの中身まで消えるのか知りたい → 第44回
- コンテナには何も変更していないのに、意図しない再生成に巻き込まれてしまう → 第45回
- Ansibleの実行中に接続が切れたり止まったりしたとき、Terraformの状態がどうなるのか不安 → 第46回
- コンテナが再生成されても、Ansibleが投入したデータだけは残したい → 第47回
- そもそも実行時にAnsibleで設定を変えるのをやめ、イメージに焼き込んでしまいたい → 第48回
- 危険な変更が計画された時点で、CI/CDパイプライン側で自動的に止めたい → 第49回
- 第41〜49回を踏まえた、シリーズ全体の結論を知りたい → 第50回
8. 第5部 各回リンク
第5部(第41〜50回)の各回タイトルと概要の一覧です。各回の詳細なトラブル内容、実機検証の手順は、リンク先の本文をご覧ください。
第5部:Terraformライフサイクル破壊編
| 回数 | テーマ、記事タイトル | 概要 |
|---|---|---|
| 第41回 | なぜTerraformはAnsibleが投じた設定を知らないのか | TerraformのState管理メカニズムと、Ansibleが変更するコンテナ内部状態の「認識の不可視性」を解剖する根本解説回。 |
| 第42回 |
create_before_destroyやreplace_triggered_byによる強制再生成 |
HCLのライフサイクル制御(lifecycleブロック)によって強制再生成(ForceNew)が発生し、Ansibleの設定が消し飛ぶ現象の再現。 |
| 第43回 |
prevent_destroy設定時におけるAnsibleプロビジョニングの詰まり |
誤破壊防止のprevent_destroyと、Ansibleの再実行(再プロビジョニング)要求がバッティングしてデプロイが詰まる障害。 |
| 第44回 | リソース再生成に伴うAnsible生成データの全喪失(データ消失障害) | Terraform側がリソースを破棄、再生成(Destroy & Create)した際、Ansibleで作成したデータベースやファイルなどの状態が全消去される問題。 |
| 第45回 | ネットワークや依存リソースの変更に引きずられる連鎖的再生成 | 依存関係にあるネットワーク(Docker Network)や上位リソースの変更により、ターゲット全体が連鎖破壊される現象。 |
| 第46回 | プロビジョニング途中の接続切断とState不整合(ハーフデプロイ状態) | Ansible実行中に接続が切断、エラー停止した際、TerraformのStateが「作成完了」と「プロビジョニング未完了」の半端な状態になる問題。 |
| 第47回 | ステートフルなデータ(Volume/DB)を保持しながらの安全な再構築 | 内部データをDocker Volumeに分離し、コンテナ(リソース)が再生成されてもAnsibleの投入データを生かす設計。 |
| 第48回 | イミュータブルインフラストラクチャにおけるAnsibleの限定的役割 | 「実行時にAnsibleで設定する」のを取りやめ、ビルド時にAnsibleを組み込んでコンテナイメージを焼き込む運用へのシフト。 |
| 第49回 | CI/CD環境において再構築事故を物理的に防ぐパイプラインガード | GitHub Actions等で誤ってDestroy & Createが走らないよう、terraform planの差分ログを解析して自動ブロックするガードレール設計。 |
| 第50回 | 連載総括:Ansible×Terraformを共存させる「ツール境界線」の設計原則 | 50回を通じた結論。「どこまでをTerraformに任せ、どこからをAnsibleに委ねるか」の境界線(アンチパターンと原則)のまとめ。 |
📑 連載の移動 前の記事:【Ansible×Terraform編】第40回
🗺️ シリーズ全体の位置づけを知りたい方はこちら
本記事は「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」シリーズ(全50回、5部構成)のうち、第5部(第41〜50回)のまとめ記事です。
シリーズ全体(第1〜5部)の関係については、以下の統合ブログで整理しています。→ 「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」シリーズ統合ブログ ※近日公開予定