Ansibleを導入したプロジェクトのうち、初回構築後に「再実行すると壊れる」という事態に遭遇するケースは珍しくありません。冪等性とは「何度実行しても同じ結果になる性質」ですが、これを実現するには単にモジュールを並べるだけでは足りません。どこまで冪等性を作り込むか、どこから別の仕組みに委ねるかという設計判断が構成管理の品質を決定します。本記事ではその線引きを体系的に整理します。
なぜ「動くPlaybook」と「壊れないPlaybook」は別物か
初回実行で成功しても2回目が安全とは限らない
Ansibleのモジュールには宣言的なものと命令的なものが混在しています。copyやtemplate、packageといったモジュールは冪等性を内部に持ちますが、shellやcommandモジュールはそれ自体に冪等性がなく、実行するたびに同じ操作を繰り返します。初回構築時に「Playbookが通った」という事実は、2回目以降の安全な実行を保証しません。
50台規模のWebサーバーへの初期構成をAnsibleで自動化したあるチームでは、セットアップ後の定期実行で設定ファイルが意図せず上書きされ続けるという問題に気づくまで数週間かかりました。原因はshellモジュールで設定値を書き換える処理を「もし存在しなければ」のチェックなしに書いていたことでした。
「状態」を記述しているか「操作」を記述しているかを区別する
Playbookを書く際の根本的な問いは「この記述は最終的な状態を定義しているか、それとも操作手順を並べているか」という点に尽きます。前者が宣言的アプローチで冪等性を持ちやすく、後者は逐次実行に近く再実行のたびに副作用を生みます。
「動くかどうか」は初回実行で確認できますが、「壊れないかどうか」は2回目・3回目の実行、あるいは途中失敗後の再実行で初めて問われます。この視点がなければ、構築担当者が引き継いだ後に再実行して環境を壊してしまう事故につながります。
冪等性が崩れやすいタイミング
特に危険なのは「既存環境への適用」場面です。ゼロから構築する場合はPlaybookが全てを作るため問題が見えにくいですが、既に稼働しているサーバーに対して同じPlaybookを実行すると、想定外の差分が発生します。ユーザー作成処理でホームディレクトリを再初期化してしまう、state: latestによってパッケージが更新されてしまうといったケースが現場では多く報告されています。稼働中の環境に適用する前提がある場合、初期設計の段階から「2回目の実行で何が起きるか」を明示的に検証する工程を組み込む必要があります。
冪等性を守る設計と崩す書き方のアンチパターン
shellモジュールへの依存が招く落とし穴
冪等性を最も壊しやすい要因はshellおよびcommandモジュールの無条件使用です。これらは宣言的なモジュールで表現できない処理を補うために存在しますが、安易に多用すると「Playbook全体が手順書と同義」になってしまいます。
対策の基本はcreatesパラメータやwhen条件による実行制御ですが、それだけでは「いつ実行すべきか」の判断をPlaybook側が持つことになり、条件漏れが即座にアンチパターンになります。設計上の優先順位は「専用モジュールで表現できないか再確認 → 無理なら冪等性のある外部スクリプト+changed制御 → 最後の手段としてshell+creates」の順序で検討します。
宣言的モジュールでも崩れるパターン
lineinfileは一見安全に見えますが、regexpパラメータで正確なマッチングを定義していないと、複数回の実行で同じ行が重複挿入されます。blockinfileのマーカー設計が不十分な場合も、ブロックが二重に挿入される事故が起きます。Ansibleを使い始めたチームが最初に踏む落とし穴のひとつがこれらのファイル操作系モジュールの不完全な設計であり、複数の担当者が同じPlaybookを編集すると、マーカー文字列の不統一やregexpの設計意図のズレが蓄積しやすくなります。
冪等性設計のチェックリスト
- 全ての
shell/commandモジュールにcreatesまたはwhenによる実行条件を付与しているか lineinfileのregexpは「存在確認」と「挿入内容」が一致しているかpackageモジュールのstateはpresentかlatestか意図的に選択しているか(latestは再実行のたびにアップデートを実行する)userモジュールでホームディレクトリの初期化(create_home)が冪等に動くか確認しているかtemplateモジュールの差分が意図しない設定変更を引き起こしていないか、ドライランで確認しているか- ロールの2回目実行後に
failed=0、changed=0が期待通りか自動テストで検証しているか
どこまでAnsibleで持ち、どこから別の仕組みに委ねるか
Ansibleが得意な領域と苦手な領域
Ansibleはパッケージ管理・設定ファイルの配置・サービス起動・ユーザー管理といった「OSレベルの状態管理」に強みを持ちます。一方で、データベースのスキーマ変更、アプリケーションのデータ移行、複雑な条件分岐が絡むオーケストレーションには向きません。
「Ansibleで全て解決しようとする」設計は、コードが肥大化するだけでなく、本来別ツールに委ねるべき処理をPlaybookに押し込むことで可読性と保守性を著しく低下させます。データベースのインデックス再構築とアプリのビルドをAnsibleで管理しようとした結果、PlaybookのTask数が300を超えてしまい、再実行のたびに30分以上かかるようになったという話を現場ではよく聞きます。
データとスキーマ管理をAnsibleに任せてはいけない理由
データベースのスキーマ変更は本質的に「差分適用」の問題であり、FlywayやLiquibaseのようなマイグレーションツールが担う領域です。Ansibleのモジュールでもmysql_queryやpostgresql_queryなどが存在しますが、これらでスキーマ変更を管理すると「どのバージョンまで適用済みか」という状態管理が別途必要になります。スキーマ管理は専用ツールに委ね、AnsibleはそのツールのインストールとCI/CDからの呼び出し設定だけを担う分担が現実的です。
設定ドリフトの検出をどこで担うか
Ansibleは適用は得意ですが、継続的な状態監視には別の仕組みが向いています。サーバーが手動変更によってPlaybookの定義からずれる「設定ドリフト」を検出するには、定期的な--checkモードでの差分検出やコンプライアンスチェッカーを組み合わせる設計が有効です。Ansibleだけでドリフト管理を完結させようとすると、cronでのPlaybook定期実行という構成になりがちで、予期しない変更適用のリスクを生みます。「ドリフトは検出するが自動適用はしない」という方針を明示的に設計に組み込むことが重要です。
ロール分割の粒度をどこで切るか
機能別とレイヤー別、どちらで分割するか
ロールの分割方針として代表的なのは「機能別(nginx・mysql・appといったコンポーネント単位)」と「レイヤー別(common・os-base・securityといった横断的な関心事単位)」の2軸です。小規模な構成であれば機能別が直感的でわかりやすいですが、管理台数が20台を超え始めると「共通のOSセキュリティ設定」「NTPやrsyslog設定」など複数のロールをまたがる共通処理が増え、レイヤー別の抽象化が必要になります。
機能別ロールを先に設計して後からcommonロールを追加しようとしたとき、依存関係の整理に想定外の工数がかかるケースは珍しくありません。初期設計の段階で「横断的な関心事」と「コンポーネント固有の処理」を分けるポリシーを決めておくことが、後の分割コストを大きく下げます。
再利用性と独立性のトレードオフ
ロールの再利用性を高めようとすると、変数による制御が増えてロール内部の複雑度が上がります。独立性を重視してロールごとに完結させると、似た処理が複数ロールに散在します。この二律背反は「どのチームが、どの頻度でロールを再利用するか」という運用実態から判断します。
再利用頻度が低いのに汎用化しようとするのは過剰設計です。10台程度の単一用途の構成なら、シンプルなタスクファイルの集合で十分なケースも多くあります。Ansible Galaxyのコミュニティロールは汎用化の設計思想を学ぶ参考になりますが、そのまま取り込むと不要な変数と条件分岐が増えて逆効果になることも多いです。
ロール間の依存が複雑化する前に引く線
meta/main.ymlのdependenciesでロール依存を定義できますが、ここを多用すると実行順序の制御が難しくなります。依存関係の深さが3階層を超えると、どのロールがなぜ先に実行されるのかを把握しにくくなり、デバッグコストが急増します。依存関係はPlaybook側のroles:定義で明示的に並べる方式を基本とし、metaの依存は「技術的に切り離せない結合」に限定するのが保守性の観点から有効です。
変更の適用範囲を安全に絞る設計
タグ設計とlimitの組み合わせ戦略
Ansibleのタグはタスク単位で適用範囲を絞るための仕組みですが、設計なしに付与すると「どのタグを実行すべきか」の判断が属人化します。推奨は「インストール系・設定系・再起動系」という処理フェーズでタグを分け、--tagsと--limitを組み合わせて「このロールの設定変更だけを、このホストグループに」という粒度で実行できる設計です。
特に本番環境への適用では、タグと対象ホストの絞り込みを明示的に行うルールを運用フローに組み込むことが重要です。タグなし全実行を本番で行う運用は、意図しない変更の適用リスクを常に抱えることになります。
チェックモードとドライランの正しい位置づけ
--checkモードは差分確認の手段として有効ですが、shellモジュールの処理や条件分岐によっては実際の実行結果と異なるdiffを返すことがあります。チェックモードを「本番適用前の完全な安全確認」と位置づけると、その不完全性を見落とすリスクがあります。
実務的な位置づけとしては「チェックモードで大きな意図しない変更がないかを確認し、さらにステージング環境での実際の実行で挙動を検証する」という二段階の設計が現実的です。影響が広いテンプレートの変更や、パッケージバージョンの変更を含む場合は、この二段階を必ず踏む運用フローを設計段階から組み込む必要があります。
ステージング環境での検証フロー設計
本番と同等の構成を持つステージング環境がない場合、コンテナや仮想マシンで「冪等性テスト専用の環境」を構築する方法があります。MoleculeはAnsibleロールのテストフレームワークとして広く使われており、ロール単体での冪等性テストを自動化できます。CI/CDパイプラインにMoleculeを組み込み、Playbook変更のたびに冪等性確認が自動実行される設計は、5人以上のチームが共同でPlaybookを編集するケースから投資対効果が出やすいです。手を動かす検証フローの詳細はハンズオン教材に委ね、ここでは「どの段階でどの検証を行うか」という設計方針の整理を優先します。
TerraformとAnsibleの責務分担の引き方
インフラの「存在」と「状態」を分けて考える
TerraformとAnsibleの最も明確な責務の分け方は「リソースの存在管理」と「リソースの状態管理」です。EC2インスタンスが「存在するかどうか」はTerraformが管理し、そのインスタンスの上で「Nginxが設定済みで起動しているかどうか」はAnsibleが管理する。この境界を最初に決めることで、ツール間の役割の重複と衝突を防げます。
| 管理対象 | Terraform | Ansible |
|---|---|---|
| VPC・サブネット・セキュリティグループの作成 | ○ 担当 | × 不向き |
| EC2インスタンスの起動・削除 | ○ 担当 | △ 可能だが非推奨 |
| OSパッケージのインストール・更新 | × 不向き | ○ 担当 |
| 設定ファイルの配置・テンプレート展開 | × 不向き | ○ 担当 |
| OSユーザー・グループ管理 | × 不向き | ○ 担当 |
| IAMロール・ポリシーの定義 | ○ 担当 | × 不向き |
| サービスの起動・自動起動設定 | × 不向き | ○ 担当 |
| RDSパラメータグループ・サブネットグループ | ○ 担当 | × 不向き |
境界を曖昧にしたときに起きる問題
TerraformのprovisionerブロックでAnsibleを呼び出す構成は、一見便利ですが境界が曖昧になりやすいです。Terraformのライフサイクルとは独立してAnsibleを再実行したい場面(設定変更のみ・パッケージ更新のみ)で、Terraformのapplyを再実行しないとAnsibleが走らないという制約が生まれます。
境界が曖昧な構成を採用すると、「この変更はTerraformで変えるのかAnsibleで変えるのか」という判断が毎回発生し、チームの認知負荷が高まります。責務の境界は文書化するだけでなく、インベントリの管理方法(TerraformのOutputからAnsibleのインベントリを動的生成するか、静的に管理するか)にも反映させて初めて実効性を持ちます。
動的インベントリとの接続設計
Terraformで作成したインスタンス情報をAnsibleのインベントリに反映する方法として、動的インベントリプラグインやterraform outputを使ったJSON形式のインベントリ生成が一般的です。この接続部分の設計が不十分だと、インフラのスケールアウト時にAnsibleが新規インスタンスを認識できないという問題が起きます。動的インベントリを採用する場合は、タグベースのフィルタリングとグループ定義を最初に設計しておくことが後の拡張性を左右します。インスタンス数が数十台を超える環境では、この接続設計の精度が構成管理全体の信頼性に直結します。
構成管理を「重すぎない」ラインで止める判断
全てをコード化しようとする過剰設計の落とし穴
「Infrastructure as Codeの原則に従い、全ての設定をAnsibleで管理する」という方針は一見正しいですが、実装コストと維持コストのバランスを無視すると機能不全に陥ります。変更頻度が極めて低い設定、環境固有の特殊な設定、手動でしか調整できないハードウェア依存のパラメータまで全てPlaybook化しようとすると、コードの量が管理できる規模を超えます。
「Playbookが複雑になりすぎて誰も触れなくなった」という状況は構築現場で生まれやすいです。特に引き継ぎで受け取ったPlaybookが数千行に及ぶ場合、全体を把握するだけで数週間かかることもあります。管理対象の規模が100台を超えるような環境でも、「このPlaybookを初見の担当者が1時間で理解できるか」を設計の上限として意識することが有効です。
何をAnsibleに書かず、どこで止めるか
Ansibleで管理しないことを明示的に決める判断が、構成管理の重量感を適切にコントロールします。変更頻度が年1回未満の設定は、手順書+手動対応の組み合わせでも十分なケースが多いです。緊急対応や一時的な変更はPlaybookを経由せず直接適用し、後から定期的な--checkモードで差分として検出する方針を取るチームもあります。この場合、「ドリフトは許容するが定期的に検出して解消する」という設計思想をチーム全体で共有することが前提となります。
過剰なPlaybook化を防ぐもうひとつの観点は「このタスクはAnsibleよりも適したツールがあるか」という問いを毎回立てることです。スクリプト1本で十分な処理、専用CLIツールで完結する処理をPlaybookに取り込むことは、保守性の観点から得るものが少ないです。
チームの維持コストから逆算する設計判断
構成管理の設計は「現在のチーム規模と今後2年の運用体制」から逆算するのが現実的です。3人以下のチームで複雑なロール構成とCI/CDパイプラインを構築しても、メンバーが変わった瞬間に維持できなくなります。一方で10人以上のチームが同じPlaybookを編集するなら、ロールの分割とテスト自動化への投資は回収できます。
設計の重さは「今のチームが無理なく維持できるか」を基準に決めます。過剰な自動化は、自動化そのものの管理コストが本来の構築作業を上回る逆転現象を生みます。Playbookは「書いたら終わり」ではなく、環境が変わるたびにメンテナンスが必要な資産であるという認識が、適切な「重さ」の設計判断につながります。冪等性をどこまで作り込むかという問いへの答えは、最終的には「そのコードをチームが長期的に維持できるか」という問いと同じです。
▶ 手を動かして本番まで到達する
設計の型は掴めても、本番でTerraformを通しきる経験は別物です
ここまでの設計判断は、読めば腹に落ちます。ですが「自分の手で本番まで通しきる自信」は、それとは別の話になります。
2日間の集中ハンズオンで、AWS上にIaCで構築しきる経験を持ち帰れます。設計判断を、動くコードと本番環境の手応えに変える場です。
AnsibleとTerraformの併用を手で確かめる2日間へ →
Terraformマスターセミナー|2日間ハンズオンでAWS上にIaCで本番構築