「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」第3部まとめブログ:Ansible×Terraform連携におけるトラブルシューティングとデバッグの体系
🗺️ シリーズ全体の位置づけを知りたい方はこちら
本記事は「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」シリーズ(全50回、5部構成)のうち、第3部(第21〜30回)のまとめ記事です。
シリーズ全体(第1〜5部)の関係については、以下の統合ブログで整理しています。→ 「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」シリーズ統合ブログ ※近日公開予定
📋 目次
0. このブログの使い方
1. はじめに
2. 第3部の原因特定プロセス図解
3. 第21〜30回の事象とプロセスの対応関係
4. 「Terraformはローカルプロセスの結果しか見ていない」という共通構造
5. 第4部への接続
6. 初めて読む方へ
7. 第3部 各回リンク
0. このブログの使い方
このブログは、「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」シリーズの第3部(第21〜30回)の全体像を把握するための記事です。
- 第3部(トラブルシューティング、デバッグ編)の全体像を先に把握してから各回を読みたい方、または各回を読んだ後に全体を俯瞰したい方は、このまま読み進めてください
- シリーズ全体(第1〜5部)の位置づけを把握したい方は、「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」シリーズ統合ブログ (※近日公開予定) をご覧ください
- 各回の詳細なトラブル内容、実機検証の手順を確認したい方は、第7節 の各回リンクから該当回に進んでください
1. はじめに
AnsibleやTerraform単体のトラブルシューティングには慣れているから、連携環境でも両者のエラーログを突き合わせれば原因を特定できると思っていないでしょうか。
しかし、実際の連携環境で起きる問題は、必ずしも一方のツールだけを見れば原因が分かるとは限りません。TerraformとAnsibleがそれぞれ異なる役割を担い、異なる状態を管理しているため、障害が発生したときには、個々のエラーだけでなく、その前後で何が起きていたのかまで追う必要があります。
そこで、第3部の話に入る前に、第2部 までで扱ってきた内容を振り返ります。
第2部(第11〜20回) で扱ったのは、構築後の継続運用の中で発生する構成ズレと状態管理の問題でした。手動変更によるドリフト、初期化処理によるOS設定の上書き、リソースの強制再生成、Playbookの冪等性、tfstateの整合性、パッケージアップデートによる非互換、IPアドレスの変動、機密情報の役割分担、OSのメジャーバージョンアップ。いずれも、問題がどう起きるかという構造を理解したうえで、それを未然に防ぐ、あるいは安全に受け止めるための設計を整理しています。
しかし、どれだけ設計や運用で予防を尽くしても、障害をゼロにすることはできません。実際にTerraformとAnsibleを連携させた環境で何かが失敗したとき、「もう一度実行してみる」だけでは、同じ場所で同じように失敗を繰り返すか、あるいは原因を特定しないまま再実行を重ねてしまうことになりかねません。第2部 までが「起きる問題をどう防ぐか」という視点だったのに対し、第3部は「起きてしまった問題を、どう特定するか」という視点に移り、第21回から第30回まで一回ずつ扱っています。
そして、このブログは、「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」という全50回、5部構成のシリーズのうち、第3部(第21〜30回、トラブルシューティング、デバッグ編)を整理するまとめ記事です。第21〜30回で扱った個別のトラブルの詳細な再現手順やコード例には踏み込まず、それらが障害発生時の原因特定プロセスのどの段階、どの原因カテゴリに対応していたかを整理し直し、対応関係として示します。あわせて、第2部 と第3部で扱ってきた問題の性質がどう異なるか、そして第3部を通じて繰り返し現れた「Terraformはローカルプロセスの結果しか見ていない」という共通構造についても、それぞれ整理します。各回の詳細なトラブル内容、実機検証の手順には踏み込みません。詳細を確認したい方は、第7節 の各回リンクから該当回に進んでください。
- これから第3部を読み始める方にとっては全体像を先につかむための地図として
- すでに各回を読まれた方にとっては読んだ内容を俯瞰して整理し直すための記事として
どちらの立場でも読み進めていただける内容になっています。
次のセクションでは、第3部の範囲で実行される、障害発生時の原因特定プロセスを図で整理します。
2. 第3部の原因特定プロセス図解
第1部 では、Terraformがリソースを生成してからAnsibleが構成管理を完了するまでの、①〜⑨という単一方向の処理フロー(構築フロー)を整理しています。
【構築フロー】
① TerraformがインフラリソースをHCLコードで定義する
↓
② terraform applyを実行する
↓
③ Docker EngineにコンテナリソースのAPIリクエストを送る
↓
④ Docker Engineがコンテナを生成する
├─ DockerネットワークがIPアドレスを払い出す
├─ ネットワークルールを適用する
└─ コンテナ内のプロセスが起動する
↓
⑤ TerraformがコンテナのAPIレスポンスを受け取る
├─ 秘密鍵をローカルに出力する
└─ tfstateにリソース情報を記録する
↓
⑥ Terraformがlocal-execでAnsibleを呼び出す
↓
⑦ AnsibleがSSH経由でコンテナに接続する
├─ SSHDの起動確認
└─ 秘密鍵、接続ユーザーの確認
↓
⑧ AnsibleがコンテナOS上でモジュールを実行する
├─ Pythonバージョンの確認
├─ become権限昇格
└─ Playbookのタスクを順に実行する
↓
⑨ 実行結果をコントロールノードに返す
続く 第2部 では、⑨より先、環境が一度構築された後の継続運用を、運用フローとして整理しています。構築フロー①〜⑨とは、⑨の直後を⑩とすることで番号を連続させています。
【運用フロー】
①〜⑨(構築フローと同一、⑨で完了)
↓
⑩ 稼働状態
↓
⑪ 手動変更、パッケージ更新、コード修正、チーム操作(変化の発生)
↓
⑫ 構成ズレ、状態変化が発生する
↓
⑬ terraform plan、ansible-playbook --checkで差分検知
↓
⑭ terraform apply、ansible-playbookで同期、再設定
↓
⑮ 稼働状態に戻る(またはリソース再生成を経由して⑩に戻る)
↓
(⑪に戻る)
構築フロー、運用フローはいずれも、リソースの生成から稼働、変化、再同期という、処理が正常に進んでいくことを前提にした流れです。しかし実際には、この①〜⑮のどの段階でも、terraform applyが失敗する、あるいは無応答のまま停止するという事態が起こり得ます。第3部が扱うのは、この「正常に進むはずの流れのどこかで失敗が起きたとき、そこからどう調査を進めるか」という、構築フロー、運用フローとは別の軸にある調査サイクルです。ここでは⑮の続きとして番号を振り、原因特定プロセスと呼びます。
【原因特定プロセス】
①〜⑮(構築フロー、運用フローのどこかで失敗が発生)
↓
⑯ 症状の観測
terraform applyの失敗、または応答なしでの停止を確認する
(この時点では原因の種類までは分からないことが多い)
↓
⑰ ログの分離、詳細化
ANSIBLE_LOG_PATH、TF_LOG_PATH等で両ツールのログを分離する
必要に応じて-vvvv、TF_LOGで詳細度を上げる
↓
⑱ 原因の切り分け
ネットワーク起因か、ホストリソース起因か、
ツール間の競合か、対話待ちによる停止か、依存関係の取得失敗か
↓
⑲ 対処
設定変更、環境調整、設計変更によって解消する
構築フロー、運用フローが「何が実行されるか」という処理の内容そのものを表しているのに対し、原因特定プロセスは「その処理のどこかが失敗したとき、何をどう調べるか」という手順を表しています。①〜⑮のどの段階で失敗が起きても、⑯〜⑲という同じ調査サイクルに合流するという点が、この原因特定プロセスの特徴です。
この流れの中で押さえておくべき点は、⑯の症状の観測の時点では、原因がどのカテゴリに属するかはまだ分からないという点です。terraform applyが失敗した、あるいは無応答のまま止まっている、という事実だけが最初に得られる情報であり、それがネットワークの問題なのか、ホストリソースの枯渇なのか、ツール間の競合なのか、対話待ちによる停止なのか、依存関係の取得失敗なのかは、⑰のログの分離、詳細化を経て初めて絞り込めます。第21回 で扱ったログ解析の手法、第30回 で扱ったデバッグフラグの組み合わせは、いずれもこの⑰の段階を担うものです。
次のセクションでは、この原因特定プロセスの中で、第21〜30回の事象がそれぞれどの段階、どのカテゴリに対応していたかを対応づけます。
3. 第21〜30回の事象とプロセスの対応関係
第2節 で整理した原因特定プロセスの中で、第21〜30回の事象がそれぞれどの段階、どのカテゴリに対応していたかを対応づけます。各回の詳細な検証内容には踏み込まず、各回へのリンク(第7節)で示すにとどめます。
| 回 | 事象 | プロセス上の対応 | 原因カテゴリ |
|---|---|---|---|
| 第21回 | ログ解析手法(両ツールのログ分離、突き合わせ) | ⑰そのもの | ログの遮蔽、観測手法そのもの |
| 第22回 | マルチネットワーク環境におけるインターフェース競合 | ⑱ | ネットワーク起因(コンテナ内部の経路) |
| 第23回 | 大容量ファイル転送、重いタスク実行時のSSHタイムアウト | ⑱ | ネットワーク起因(速度、継続性) |
| 第24回 | 並列実行時のホストリソース枯渇 | ⑱ | ホストリソース起因 |
| 第25回 | HCL構文、ansible-lintの競合 | ⑯以前(実行前の静的解析) | 静的解析ツール間の競合 |
| 第26回 | sudoパスワードプロンプトの停止 | ⑯〜⑱ | 対話待ちによる無応答停止 |
| 第27回 | 再起動、再生成に伴うプロビジョニング断絶 | ⑱ | Terraformの受動的な観測構造 |
| 第28回 | 異種OS(Windowsターゲット)混在環境における接続プロトコルの制約 | ①〜⑨相当(接続前提そのもの) | 対象OSの違いに起因する構造 |
| 第29回 | Ansible Galaxy取得失敗 | ⑯ | 依存関係の取得失敗 |
| 第30回 | デバッグフラグの組み合わせ | ⑰の体系化、総括 | 観測手法の体系化 |
この対応関係から、いくつかの傾向が見えてきます。
第21回 は、原因特定プロセスのうち⑰そのものを扱う回です。local-exec経由のAnsible出力がTerraformのエラーブロックに埋没する構造を整理し、ANSIBLE_LOG_PATHによるログ分離という、⑰の基本となる手段を確立しています。第3部の起点として、以降の回すべてがこの⑰の土台の上に成り立っています。
第22回 は、⑱の原因の切り分けにあたる回です。原因の特定には、ip addr、ip route、journalctlのsbJoinログ、terraform state showという複数の手段を組み合わせています。
第23回、第24回 は、いずれも⑱の原因カテゴリに位置づけられます。第23回 はSSHタイムアウトの発生層(接続確立時、転送中の無応答)とTCP、MTUといったネットワーク層の要因を、第24回 はTerraformの並列実行とAnsibleのforksが重なった際のホスト側リソース圧迫のパターンを、それぞれ整理しています。
第25回 は、⑯の症状の観測より前、実行前の静的解析の段階に位置づけられます。terraform applyが失敗する前、コードを書いている時点で発生する競合であり、原因特定プロセスそのものの外側にある予防的なチェックという性質を持ちます。
第26回 は、⑯の症状の観測から⑱の切り分けまでを、この回単体でひとつなぎに示した事例です。エラーメッセージが一切出力されないという症状の特殊性から、psコマンドによるプロセスツリーの確認という、通常の⑰とは異なる観測手段を用いています。
第27回 は、⑱の原因カテゴリ(Terraformの受動的な観測構造)を扱います。この構造は 第21回、第26回 とも共通しており、詳しくは 第4節 で改めて整理します。
第28回 は、他の回のように障害発生後の調査手順に位置づけられるものではなく、構築フローの①〜⑨、つまり接続そのものの前提が、対象OSの違いによってどう変わるかという回です。
第29回 は、⑯の症状の観測の時点で、原因の所在(Collectionの取得失敗)がすでにlocal-execの標準出力にそのまま表示されていた事例です。Ansibleの-vvvvが効力を持つ範囲(ansible-playbook実行時のタスク処理)に到達する前の段階で失敗しているため、⑰でデバッグフラグを上げても得られる情報は増えません。
第30回 は、第3部最終回として、⑰のログの分離、詳細化という段階そのものを体系的に総括する回です。TF_LOGと-vvvvという2つのフラグが実際に何を記録し何を記録しないかを実機検証したうえで、第21回、第22回、第27回、第29回 への当てはめを通じて、両フラグが原因特定における万能の手段ではないことを示しています。
4. 「Terraformはローカルプロセスの結果しか見ていない」という共通構造
第3節 で触れた通り、第21回、第26回、第27回 の3回は、いずれも「local-execは子プロセス(Ansible実行)の内部で何が起きているかを認識せず、終了コードや応答の有無という結果だけを受動的に観測している」という共通の構造から派生しています。このセクションでは、3回それぞれで、この構造がどう異なる症状として現れているかを掘り下げます。
第21回:子プロセスの失敗内容が、正常時と同じ形の出力に埋もれて見えなくなる
第26回:子プロセスがBECOMEパスワードの入力待ちで停止しても、正常な待機か異常な停止か区別できない
第27回:子プロセス内のOS再起動に伴う接続断絶が、正常な過程か異常な停止か区別できない
第21回 では、local-execは失敗したコマンドの出力を握りつぶしているわけではなく、Ansibleの実行結果は通常のログとして一度流れたのち、Error: local-exec provisioner errorブロックのOutput:以降にも同じ内容が再掲されています。情報自体は失われていないものの、この重複と罫線装飾によって、実際の失敗箇所が見えにくくなるという形で、この構造が現れています。
第26回 では、--ask-become-passを指定した状態でlocal-exec経由の実行を行うと、ansible-playbookの子プロセスがBECOMEパスワードの入力待ちのまま残り続け、local-exec側もその終了を待ち続けることで、terraform apply全体が無応答のまま停止しています。エラーメッセージは一切出力されないため、local-execの視点からは、この停止が正常な待機なのか異常な停止なのかを区別する手段がありません。
第27回 では、Ansibleのrebootモジュールが接続断絶を再起動に伴う正常な過程として扱うよう設計されている一方、local-execはその「正常な過程である」という情報を受け取れず、子プロセスが応答を返さない状態が続いているという事実だけを観測しています。この受動性は、local-execの実装上の欠陥ではなく、シェルコマンドを起動してその終了を待つという、local-exec本来の役割から必然的に導かれる性質です。
症状の現れ方は回ごとに異なりますが、根底にある「local-execは子プロセスの内部で何が起きているかを認識しない」という一点は共通しています。第30回 で確認した通り、TF_LOGとANSIBLE_LOG_PATHは互いの詳細を補い合う関係ではなく、local-execが実行されている区間そのものがTF_LOGの記録範囲の外にあるという構造も、この受動性の延長線上にあります。この構造を理解しておくことが、第3部全体の見通しを良くします。
次のセクションでは、第3部で整理してきた原因特定、デバッグの手法を踏まえ、第4部 が何を扱うのかを整理します。
5. 第4部への接続
第4部 では、この視点が変わります。第3部までが「起きた問題をどう特定するか」を扱っていたのに対し、第4部 は「問題が起きる前にどう防ぐか、どう自動化するか」に焦点を移します。第4部 で扱う内容の概要として、以下を挙げます。
- Terraformの
outputを中間データとして抽出し、Ansibleの動的インベントリや変数として渡す、疎結合なパイプライン設計 - GitHub Actionsによるコンテナ起動を伴う自動テスト(E2E)の組み込み
-
triggersを用いたAnsible再実行の最適化 - Testinfraによる状態検証のパイプライン統合
- 定期実行による構成ドリフトの自動検知、収束
第3部で身につけた原因特定、デバッグの手法は、第4部 で自動化を進めるうえでも土台になります。パイプラインが失敗したとき、その失敗をどう切り分けるかという場面では、第3部で整理している考え方がそのまま生きてきます。
次回:「AnsibleとTerraformの連携が壊れる理由はライフサイクルにあった」第31回:状態出力を介した疎結合なパイプライン設計
第4部:改善、CI/CD自動化編
| 回数 | テーマ、記事タイトル | 概要 |
|---|---|---|
| 第31回 | 状態出力を介した疎結合なパイプライン設計 | Terraformのoutputを中間データ(JSON/Environment)として抽出し、Ansibleの動的インベントリや変数として渡すパイプラインの分離設計。 |
| 第32回 | GitHub Actionsを用いたプロビジョニングコードの自動テスト環境構築 | CI/CD(GitHub Actions)上でTerraform実行によるコンテナ起動からAnsible適用までの自動テスト(E2E)を組み込む手法。 |
| 第33回 |
triggersを用いたAnsible再実行の最適化設計 |
null_resourceやterraform_dataのtriggers/triggers_replaceを使い、設定ファイル変更時のみAnsibleを発火させる設計。 |
| 第34回 | Testinfraによる状態検証を組込んだCI/CDパイプライン | Ansible適用後の状態(ポート、ファイル、プロセス)をTestinfra(Python)でテストし、パイプラインの成否を判定する自動化設計。 |
| 第35回 | 共通パーツのモジュール化(Terraformモジュール/Ansibleロール) | Terraformのモジュール設計とAnsibleのロール(Role/Collection)の粒度を揃え、再利用性を高める設計パターン。 |
| 第36回 | プラグインおよびパッケージのキャッシュによるCI実行時間の高速化 | Terraform ProviderやAnsible Galaxy、aptパッケージのキャッシュ戦略により、CI/CDパイプラインの実行時間を短縮する手法。 |
| 第37回 | 定期実行による構成ドリフトの自動検知と収束パイプライン | スケジュール実行(Cron/GitHub Actions)でansible-playbook --checkを流し、実際の構成差分(ドリフト)を検知し、差分があった場合のみ本実行して自動収束させる設計。 |
| 第38回 | インフラコード(HCL/Playbook)からの仕様書、構成図の自動生成 |
terraform-docsやansible-autodoc等を活用し、コード更新と同時に仕様書や依存関係図を自動更新するパイプライン構築。 |
| 第39回 | 既存インフラ運用知識とInfrastructure as Code(IaC)のシナジー | 手動運用(CLI/Shell)のノウハウを、TerraformとAnsibleという2大ツールにどう分解、再構築していくかの比較考察。 |
| 第40回 | 改善、CI/CD編まとめ:プロビジョニング自動化の成熟度モデル | 手動実行から状態連携、CI/CD化、自動収束(Level 1〜4)に至るまでのインフラ自動化の成熟度の整理。 |
6. 初めて読む方へ
ここまで、第3部の原因特定プロセスと、そこで発生する事象の全体像を見てきました。実際にどの回から読み進めるか迷っている方は、関心に応じて以下を参考にしてください。
-
terraform applyのエラーメッセージを見ても、Ansible側のどこが失敗したのか分からない → 第21回 - 複数のネットワークをアタッチしたら、Ansibleが意図しないIPアドレスに接続してしまう → 第22回
- 大きなファイルを転送する、あるいは重いタスクを実行するとSSHがタイムアウトする → 第23回
- 複数のリソースを同時に構築すると、ホストのCPU、メモリが逼迫する → 第24回
-
terraform fmtとansible-lintを両方導入したら、CIが通らなくなった → 第25回 -
sudoのパスワード入力が絡む処理で、
terraform applyが無応答のまま固まる → 第26回 - AnsibleでOS再起動を伴うタスクを実行すると、Terraform側がエラー扱いにしてしまう → 第27回
- WindowsサーバーをAnsibleで管理する際の接続、認証の勘所を知りたい → 第28回
-
プロキシ環境で
ansible-galaxy collection installが失敗する → 第29回 -
TF_LOGや-vvvvを、どのタイミングでどう使い分ければいいか知りたい → 第30回
7. 第3部 各回リンク
第3部(第21〜30回)の各回タイトルと概要の一覧です。各回の詳細なトラブル内容、実機検証の手順は、リンク先の本文をご覧ください。
第3部:トラブルシューティング、デバッグ編
| 回数 | テーマ、記事タイトル | 概要 |
|---|---|---|
| 第21回 | 統合実行時におけるネストされたエラーログの解析手法 | Terraformのlocal-exec経由で実行されたAnsibleのエラーログから、原因がTerraform側(HCL、State)かAnsible側(Playbook、タスク)かを特定する手法。 |
| 第22回 | マルチネットワーク環境におけるインターフェース競合 | 複数ネットワーク(Dockerネットワーク等)をアタッチした際、Ansibleのansible_default_ipv4や接続IPの自動検出が意図しないインターフェースに引きずられる問題。 |
| 第23回 | 大容量ファイル転送、重いタスク実行時におけるSSHタイムアウト | Ansibleで大きなアセットや大量のパッケージを転送、適用する際、Terraform側のプロビジョナータイムアウトに引っかかる可能性がある問題を扱う。 |
| 第24回 | 並列実行時における実行ホストのシステムリソース枯渇対策 |
parallelismやforks設定により、大量のリソース構築とAnsibleプロビジョニングが同時に走った場合、ホストのCPU、メモリ、ファイルディスクリプタが枯渇しうる問題を扱う。 |
| 第25回 | 構文チェックツール(HCL構文、ansible-lint)の競合緩和 | 両ツールの静的解析ツール(terraform fmt、ansible-lint)を導入した際、コード記述ルールや命名規則の不一致でCIが通らなくなる問題。 |
| 第26回 | 管理者権限(sudo)実行時におけるパスワード入力のプロンプト停止 | Terraformのlocal-exec経由でAnsibleのbecomeを実行する際、パスワード入力を要求するオプションを指定した場合に限り、非対話実行環境で処理が無応答のまま停止する問題。 |
| 第27回 | 再起動、再生成に伴うプロビジョニング断絶 | AnsibleのrebootモジュールによるOS再起動時、接続断絶が正常な過程か異常な停止かをTerraformのlocal-execが区別できず、外側のタイムアウトと競合する問題。 |
| 第28回 | 異種OS(Windowsターゲット)混在環境における接続プロトコルの制約 | SSHではなくWinRM等を用いる特殊プロトコル環境での接続、権限エラー。実務でWindows ServerをAnsible×Terraformで管理する際の構造的注意点を扱う概念解説回。 |
| 第29回 | プロキシ環境等における外部コレクション(Ansible Galaxy)の取得失敗 | インフラ構築処理の途中で外部ネットワーク(Ansible Galaxy等)への依存が切れ、Terraformの処理全体が失敗する問題。 |
| 第30回 | デバッグフラグの組み合わせによるログ解析の高度化 | TerraformのTF_LOGとAnsibleの-vvvvを組み合わせ、接続遅延や処理遅延のボトルネックを特定、解消する手法。 |