原因の多くは、Playbook設計の段階で「冪等性(べきとうせい)」の考え方が抜けていることにあります。冪等性とは「何度実行しても同じ結果が得られる性質」のことで、Ansibleの設計思想の核心です。
この記事では、Ansibleが冪等性を必要とする理由から、冪等性を破壊してしまうアンチパターン、そして現場で機能する4つの設計原則まで体系的に解説します。さらに、冪等性の高いPlaybookを本番環境へ安全に展開するためのロールアウト設計(serial・max_fail_percentage)も合わせて押さえます。
RHEL 10 / Rocky Linux 9 の実環境で確認した実行例つきで説明しているので、自分のPlaybookにすぐ応用できます。
この記事のポイント
・Ansibleの冪等性とは「何度実行しても同じ状態に収束する」設計の性質
・command/shell乱用とstate未指定が冪等性を破壊する2大アンチパターン
・4原則:モジュール優先・creates条件付け・state明示・handler活用
・--check/--diffで事前確認後、serialとmax_fail_percentageで本番への安全な段階展開を設計する
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
冪等性とは何か|「手順実行」から「状態収束」へ
Ansibleを初めて触った人が感じる「シェルスクリプトと何が違うのか」という疑問。この答えが冪等性の理解につながります。1. シェルスクリプトとAnsibleの根本的な違い
シェルスクリプトは「手順を上から順に実行する」ツールです。次のスクリプトを2回実行すると何が起きるか考えてみてください。# シェルスクリプト:冪等性なし mkdir /var/app/uploads useradd appuser echo "server_name=web01" >> /etc/app/config.conf
一方、AnsibleのPlaybookを見てみましょう。
# Ansible Playbook:冪等性あり - name: uploadsディレクトリを作成する ansible.builtin.file: path: /var/app/uploads state: directory mode: '0755' - name: appuserを作成する ansible.builtin.user: name: appuser state: present
2. 「状態収束」とはどういうことか
Ansibleが実現するのは「状態収束型の自動化」です。Playbookに書くのは「手順(何をするか)」ではなく「あるべき状態(どうなっているべきか)」です。・シェルスクリプトの発想:「httpをインストールする」「設定ファイルを書き換える」「サービスを再起動する」
・Ansibleの発想:「httpdはinstalled状態であること」「設定ファイルはこの内容であること」「httpdはrunning状態であること」
この発想の違いが冪等性を生みます。Ansibleはタスクを実行するたびに「現在の状態」と「あるべき状態」を比較し、差分があるときだけ変更を加えます。サーバーが既に正しい状態なら、Ansibleは何もしません。
構成管理ツールとしての信頼性はこの性質に依存しています。夜中にcronでAnsibleを自動実行しても安全な理由も、複数台のサーバーに同じPlaybookを当てられる理由も、すべて冪等性があるからです。
冪等性を壊す3つのアンチパターン
Ansibleが設計として冪等性をサポートしていても、Playbookの書き方によっては冪等性が失われます。現場でよく見かける3つのアンチパターンを押さえてください。1. commandとshellモジュールの乱用
最も多いアンチパターンです。Ansibleには `command` と `shell` という汎用モジュールがあり、任意のシェルコマンドを実行できます。便利な反面、これらのモジュールはAnsibleが「実行前に状態を確認する」しくみを持っていません。# NG:commandモジュールで冪等性なし - name: ログディレクトリを作成する ansible.builtin.command: mkdir /var/log/myapp - name: 設定を追加する ansible.builtin.shell: echo "MAX_CONN=100" >> /etc/myapp/app.conf
2. stateパラメーターの指定漏れ
AnsibleのモジュールにはほぼすべてにStateパラメーターがあります。省略可能なケースもありますが、明示しないと将来の挙動変化やAnsibleのバージョンアップで予期しない結果になることがあります。# NG:stateを省略(デフォルト挙動に依存) - name: httpdをインストールする ansible.builtin.dnf: name: httpd # OK:stateを明示 - name: httpdをインストールする ansible.builtin.dnf: name: httpd state: present
3. 副作用のある処理の繰り返し実行
処理そのものは正常に完了しても、実行するたびに「変化」が起きてしまう設計です。# NG:毎回新しいアーカイブを作成してしまう - name: バックアップを作成する ansible.builtin.command: > tar czf /backup/conf_{{ ansible_date_time.epoch }}.tar.gz /etc/myapp/ # NG:ログをローテートするスクリプトを毎回実行してしまう - name: ログをローテートする ansible.builtin.shell: /usr/local/bin/rotate_logs.sh
なお、 `ignore_errors: true` を付けたタスクはAnsibleが失敗を無視するため、本来は検知すべき問題が素通りします。後述する `max_fail_percentage` の計算にも影響しないため、致命的な処理に安易に使うことは避けてください。
冪等性を保つPlaybookの4原則
アンチパターンの逆を押さえれば、冪等性の高いPlaybookが自然に書けるようになります。1. ビルトインモジュールを最初の選択肢にする
Ansibleには公式のビルトインモジュール(ansible.builtin.*)とAnsible Collectionsが提供するモジュールが数百以上あります。これらはAnsibleが「現在の状態を確認してから変更を加える」しくみを内部に持っているため、基本的に冪等性が保証されます。・ファイル操作:`ansible.builtin.copy`、`ansible.builtin.template`、`ansible.builtin.file`
・パッケージ管理:`ansible.builtin.dnf`(RHEL/Rocky)、`ansible.builtin.apt`(Debian系)
・サービス制御:`ansible.builtin.service`、`ansible.builtin.systemd`
・ユーザー管理:`ansible.builtin.user`、`ansible.builtin.group`
・設定ファイル:`ansible.builtin.lineinfile`、`ansible.builtin.blockinfile`
特定の行だけを冪等に書き換えたい場合は `lineinfile` が便利です。
# lineinfileで特定行を冪等に書き換える - name: MaxConnectionsを設定する ansible.builtin.lineinfile: path: /etc/myapp/app.conf regexp: '^MAX_CONN=' line: 'MAX_CONN=100' create: yes
`template` や `copy` モジュールには `validate:` パラメーターを指定でき、ファイルをコピーする前に構文チェックコマンドを走らせられます。サービスが設定エラーで再起動不能になる事故をデプロイ前に防ぐ、実務上の重要な安全弁です。
# validateで構文チェックを組み込む(Nginx設定の例) - name: Nginxの設定ファイルを配置する ansible.builtin.template: src: templates/nginx.conf.j2 dest: /etc/nginx/nginx.conf validate: nginx -t -c %s notify: Nginxをリロードする
2. command/shellモジュールにはcreates/removesで条件付けする
どうしても `command` や `shell` を使う必要があるときは、`creates` または `removes` パラメーターで条件を付けます。これはAnsibleが提供する「冪等性を後付けする」しくみです。# createsで冪等性を付与する - name: ログディレクトリを作成する ansible.builtin.command: mkdir -p /var/log/myapp args: creates: /var/log/myapp # このパスが存在する場合はスキップ - name: 設定ファイルを初期生成する ansible.builtin.command: /usr/local/bin/init_config.sh args: creates: /etc/myapp/app.conf # ファイルが既にあればスキップ
ただし `creates` はあくまで「パスの有無」しか確認できません。処理の正確な冪等性確認が必要な場合は、`command` ではなく適切なビルトインモジュールを探すことを優先してください。
3. stateを常に明示する
Ansibleのモジュールでは `state` を明示することが設計の基本です。「今この記事を書いた時点の挙動」ではなく「Ansibleのバージョンが上がっても意図が伝わる」Playbookを書くために、省略可能でも必ず書く習慣を持ちましょう。# stateを常に明示する例 - name: httpdをインストール ansible.builtin.dnf: name: httpd state: present # インストール済みなら何もしない - name: httpdを起動・自動起動有効化 ansible.builtin.systemd: name: httpd state: started # 既に起動していれば何もしない enabled: yes # 自動起動が有効なら何もしない - name: 不要なパッケージを削除 ansible.builtin.dnf: name: telnet state: absent # インストールされていなければ何もしない
4. handlerで「変化があった時だけ」実行する
サービスの再起動や設定のリロードは、変化があった時だけ行う必要があります。Ansibleの `handler` はまさにこのために設計された機能です。# handler設計のパターン tasks: - name: httpdの設定ファイルを配置する ansible.builtin.template: src: templates/httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf owner: root group: root mode: '0644' notify: httpdを再起動する # 変化があった時だけhandlerを呼ぶ handlers: - name: httpdを再起動する ansible.builtin.systemd: name: httpd state: restarted
「設定を変えた時だけhttpdが再起動される」という挙動は、冪等性と組み合わせることで「何も変わっていないのにhttpdが落とされる」という事故を防ぎます。Ansibleのhandlerが冪等性設計の重要な柱になっている理由です。
Ansibleの設計原則をハンズオン形式で体系的に学びたい方は、以下もご参照ください。
Ansible実践ハンズオンの詳細を見る >>
check modeとdiffで冪等性を事前に検証する
Playbookを本番サーバーへ流す前に、「このPlaybookを実行したら何が変わるか」を確認するのが現場の鉄則です。Ansibleの `--check` と `--diff` はそのための機能です。1. --checkで変更予定タスクを洗い出す
`--check` オプション(dry-run)を付けると、Ansibleは実際には何も変更せずに「変更が発生するかどうか」だけを確認します。# dry-runで変更予定を確認する ansible-playbook -i inventories/prod site.yml --check
PLAY [web servers] ************************************************************* TASK [Gathering Facts] ********************************************************* ok: [web01.example.local] TASK [httpd : httpd パッケージをインストールする] ************************************ ok: [web01.example.local] # 既にインストール済みのため変更なし TASK [httpd : 設定ファイルを配置する] ************************************************ changed: [web01.example.local] # 内容が変わるため変更対象 TASK [httpd : httpdを起動・自動起動有効化] ****************************************** ok: [web01.example.local] # 既に起動中のため変更なし PLAY RECAP ********************************************************************* web01.example.local : ok=3 changed=1 unreachable=0 failed=0 skipped=0
2. --diffで具体的な変更内容を確認する
`--diff` オプションを追加すると、ファイルの変更前・変更後の差分がdiff形式で表示されます。# diffつきdry-runで変更内容を詳細確認する ansible-playbook -i inventories/prod site.yml --check --diff
TASK [httpd : 設定ファイルを配置する] ************************************************ --- before: /etc/httpd/conf/httpd.conf +++ after: /tmp/ansible-local-xxxxx/tmpyyyy/httpd.conf @@ -95,7 +95,7 @@ # # Timeout: The number of seconds before receives and sends time out. # -Timeout 60 +Timeout 120 changed: [web01.example.local]
3. CI/CDパイプラインへの組み込み設計
`--check` と `--diff` の組み合わせは、GitHub ActionsなどのCI/CDパイプラインに組み込むことで「Playbookのマージ前に変更内容をレビューする」設計が実現できます。・PRのdiffレビュー:コードの変更に加えて `--check --diff` の出力をPRコメントに貼り付ける
・ステージング環境での自動適用:mergeをトリガーにステージング環境へ自動流し、本番はmanual approvalで制御する
・本番適用ゲート:ステージングで `changed=0` の確認後のみ本番を許可する
このフローは冪等性の高いPlaybookがあってこそ実現できます。`changed` が毎回発生するPlaybookではCI結果が意味をなしません。
冪等性を活かした本番ロールアウト設計|serialとmax_fail_percentageの基本
冪等性の高いPlaybookが完成したら、次は「本番への安全な適用設計」に進みます。Ansibleのデフォルト動作は、インベントリに書いた全ホストへ並列に処理を配布することです。テスト環境では問題ありませんが、本番で数十台に一気に適用すると、設定ミスが全台に一瞬で広がるリスクがあります。1. serialで段階的に適用範囲を広げる
`serial` はPlayレベルのパラメーターで、「一度に何台ずつ処理するか」のバッチサイズを指定します。`serial` を使うと、Ansibleは全タスクを1バッチ分のホストで完結させてから次のバッチへ進みます。冪等性が保証されているからこそ、失敗後の再実行が安全にできる点も重要です。# 本番Webサーバーへの段階的デプロイ - name: httpdの設定変更を段階的に適用する hosts: webservers serial: - 1 # 第1バッチ:1台で動作確認 - "10%" # 第2バッチ:全体の10%に拡大 - "100%" # 第3バッチ:残り全台 max_fail_percentage: 0 # 1台でも失敗したら即停止 tasks: - name: httpdの設定ファイルを配置する ansible.builtin.template: src: templates/httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf validate: httpd -t -f %s notify: httpdをリロードする handlers: - name: httpdをリロードする ansible.builtin.systemd: name: httpd state: reloaded
2. max_fail_percentageで失敗時の自動停止を設計する
`max_fail_percentage` は「何パーセントのホストが失敗したらPlayを強制停止するか」を指定します。本番環境でサービス継続が必須の場合の鉄則は `max_fail_percentage: 0`(1台でも失敗したら即停止)です。・`max_fail_percentage: 0`:1台でも失敗したら即停止(本番重要サービスの基本設定)
・`max_fail_percentage: 20`:20%未満の失敗は許容して継続(軽微な変更向け)
・`max_fail_percentage: 100`:デフォルト値。全台失敗でも継続するため実質無効
3. validateとヘルスチェックで二重安全設計を実現する
`validate:` パラメーターと `max_fail_percentage: 0` を組み合わせることで、「構文エラーをデプロイ前にブロック→万が一エラーが起きたら即停止」という二重安全設計が実現できます。さらに `ansible.builtin.uri` でHTTPヘルスチェックを組み込むと、デプロイ後のサービス疎通まで自動で確認できます。# validate + ヘルスチェック + max_fail_percentage: 0 の二重安全設計 - name: Nginx設定を段階的に適用する hosts: webservers serial: [1, "50%", "100%"] max_fail_percentage: 0 tasks: - name: Nginx設定ファイルを配置する ansible.builtin.template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf validate: nginx -t -c %s # 構文エラーで即ブロック notify: Nginxをリロードする - name: サービス疎通確認 ansible.builtin.uri: url: "http://{{ inventory_hostname }}/health" status_code: 200 retries: 3 delay: 5 failed_when: false # 後続の register で判定する handlers: - name: Nginxをリロードする ansible.builtin.service: name: nginx state: reloaded
冪等性が崩れた時のトラブルシュート
「設計したつもりなのに、なぜか毎回 `changed` になる」——そういった場合の切り分け手順を押さえておきます。1. changedが毎回発生するタスクの確認手順
`--check --diff` で「実際に何が変わっているか」を確認するのが最初のステップです。# 毎回changedになるタスクをdiffで詳細確認する # PLAY RECAPでchangedになっているタスク名をtagで絞って実行する ansible-playbook -i inventories/dev site.yml --check --diff --tags "確認したいタグ"
・templateの末尾改行ゆれ:テンプレートファイルの末尾改行がサーバー側ファイルと一致しないと毎回差分として検出される。`trim_blocks: true` やファイル末尾の改行を統一して対処する
・lineinfile の regexp が複数行にマッチ:`regexp` が複数行に一致すると毎回 `changed` を返すことがある。正規表現をより厳密にして1行だけにマッチするよう修正する
・fileモジュールのパーミッション表記ゆれ:`mode: '755'` と `mode: '0755'` は同義だが4桁形式で統一すると差分検出が安定する
2. 途中失敗後の安全な再実行方法
冪等性の高いPlaybookは途中失敗後の再実行が安全です。`failed=1` になったタスク名を確認して `--start-at-task` で途中から再開できます。# 失敗したタスク名を確認して、そこから再実行する ansible-playbook -i inventories/dev site.yml --start-at-task "設定ファイルを配置する" # またはタスクにtagを付けて絞り込んで再実行する ansible-playbook -i inventories/dev site.yml --tags "nginx-config"
serial を使った本番ロールアウトで途中失敗した場合は、`--limit` で失敗したホストのみを指定して個別に再実行するのが安全な手順です。
# 失敗ホストのみを指定して再実行 ansible-playbook -i inventories/prod site.yml --limit web03.example.local
本記事のまとめ
Ansibleの冪等性設計と本番ロールアウトの要点を整理します。| 設計ポイント | 推奨(冪等性・安全設計あり) | アンチパターン |
|---|---|---|
| ファイル作成 | ansible.builtin.file: state: directory |
ansible.builtin.command: mkdir /path |
| 設定行の追加 | ansible.builtin.lineinfile: regexp: '^KEY=' line: 'KEY=VAL' |
ansible.builtin.shell: echo "KEY=VAL" >> /etc/conf |
| パッケージ管理 | ansible.builtin.dnf: name: httpd state: present |
ansible.builtin.command: dnf install httpd |
| サービス再起動 | handlerで変化があった時だけ呼ぶ | 毎タスク実行後に無条件で再起動する |
| command使用時 | args: creates: /path/to/file で条件付け |
条件なしでcommandを実行する |
| 構文チェック | validate: nginx -t -c %s でデプロイ前に検証 |
構文チェックなしでテンプレートを配置する |
| 本番ロールアウト | serial: [1, "10%", "100%"] max_fail_percentage: 0 |
全台一括適用・失敗しても継続する |
| 事前確認 | ansible-playbook --check --diff でdry-run |
いきなり本番サーバーへ投入する |
ビルトインモジュールを優先し、どうしてもcommand/shellを使う場合はcreates条件で補強する。stateを明示し、副作用のある処理はhandlerに任せる。さらに `validate:` で事前の構文チェックを組み込み、`serial` と `max_fail_percentage: 0` で本番への段階的ロールアウトを設計する。これらの積み上げがPlaybookを構成管理ツールとして信頼できる基盤に育てます。
Ansibleの設計原則をハンズオン環境で体系的に習得したい方は、ぜひ詳細をご覧ください。
Ansible実践ハンズオンの詳細を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます