Ansibleの冪等性設計|command・shellモジュールの罠と安全なPlaybookの4原則

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Ansible > Ansibleの冪等性設計|command・shellモジュールの罠と安全なPlaybookの4原則
「Ansibleを使い始めたのに、同じPlaybookを2回実行したらエラーになった」「手動でサーバーを触った後にAnsibleを流すと設定が壊れる」——Ansibleを導入した現場でこうした問いに直面することがあります。
原因の多くは、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で本番への安全な段階展開を設計する


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

冪等性とは何か|「手順実行」から「状態収束」へ

Ansibleを初めて触った人が感じる「シェルスクリプトと何が違うのか」という疑問。この答えが冪等性の理解につながります。

1. シェルスクリプトとAnsibleの根本的な違い

シェルスクリプトは「手順を上から順に実行する」ツールです。次のスクリプトを2回実行すると何が起きるか考えてみてください。

# シェルスクリプト:冪等性なし mkdir /var/app/uploads useradd appuser echo "server_name=web01" >> /etc/app/config.conf

1回目は成功しますが、2回目は「mkdir: /var/app/uploads: ファイルが存在します」「useradd: ユーザー 'appuser' は既に存在します」とエラーになります。スクリプトは「今の状態がどうなっているか」を確認せず、ただ手順を実行するだけだからです。

一方、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

このPlaybookは何度実行しても安全です。ディレクトリが既に存在すれば「ok(変更なし)」と報告し、ユーザーが既に存在すれば「ok」とだけ返します。Ansibleは実行前に「現在の状態」を確認し、目標状態と一致していれば何もしないのです。

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

1行目は2回目の実行で「mkdir: /var/log/myapp: ファイルが存在します」と失敗します。2行目はさらに悪く、実行するたびに同じ行が追記され続けます。

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

`state: present` は「インストールされていること」を意味し、既にインストール済みであれば何もしません。`state: latest` にするとバージョンアップが毎回走り、意図しない更新が発生することもあるため、本番環境では通常 `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

上のバックアップタスクは実行するたびに新しいファイルを作成します。ディスクを圧迫するうえ、「変化があった時だけ実行」という条件制御ができません。このような副作用のある処理はそのままcronや別の仕組みで管理し、Ansibleのタスクから分離するのが設計のセオリーです。

なお、 `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

`regexp` に一致する行があれば上書きし、なければ追加します。何度実行しても同じ結果になります。

`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をリロードする

`%s` は一時ファイルのパスに展開されます。`nginx -t` が失敗するとタスクはエラー終了し、壊れた設定ファイルがサーバーに配置されません。

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: /var/log/myapp` は「このパスが既に存在する場合はこのタスクをスキップ」を意味します。2回目以降の実行ではスキップされるため、冪等性が確保できます。

ただし `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

設定ファイルの内容が変わった場合(= `changed`)のみ `notify` でhandlerが呼ばれます。設定ファイルが既に正しい内容であれば(= `ok`)handlerは呼ばれず、サービスの無駄な再起動が起きません。

「設定を変えた時だけhttpdが再起動される」という挙動は、冪等性と組み合わせることで「何も変わっていないのにhttpdが落とされる」という事故を防ぎます。Ansibleのhandlerが冪等性設計の重要な柱になっている理由です。

Ansibleの設計原則をハンズオン形式で体系的に学びたい方は、以下もご参照ください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
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

実際に本番環境(Rocky Linux 9)でcheck modeを実行した出力例です。

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

`changed=1` の行が変更予定のタスクです。本番適用前にこのサマリーを確認することで、意図しない変更が含まれていないかチェックできます。冪等性の高いPlaybookは「何も変えていないのに `changed` が増える」ことがないため、`changed=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

第1バッチの1台で問題が発生した場合、Ansibleは後続バッチへ進まず自動で停止します。「1台→10%→全台」という段階的な展開パターンは、多くの本番運用で定番の設計です。パーセント指定を使うと、ホスト台数が増減しても自動でスケールします。

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

ヘルスチェックが失敗した場合も `max_fail_percentage: 0` が機能し、後続バッチへ進まずに即停止します。「デプロイ→リロード→疎通確認→問題なければ次の台へ」という安全なロールアウトフローが完成します。

冪等性が崩れた時のトラブルシュート

「設計したつもりなのに、なぜか毎回 `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"

冪等性がないPlaybookで途中再開すると、完了済みの処理が2重実行されてトラブルになります。冪等性が確保されていれば再実行しても安全です。これが冪等性設計の実運用上の最大のメリットです。

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 いきなり本番サーバーへ投入する
冪等性の高いPlaybookは「何度実行しても安全」という性質を持ちます。この性質があってはじめて、深夜のcron自動実行・複数台への一括適用・CI/CDへの組み込みが現実的になります。

ビルトインモジュールを優先し、どうしてもcommand/shellを使う場合はcreates条件で補強する。stateを明示し、副作用のある処理はhandlerに任せる。さらに `validate:` で事前の構文チェックを組み込み、`serial` と `max_fail_percentage: 0` で本番への段階的ロールアウトを設計する。これらの積み上げがPlaybookを構成管理ツールとして信頼できる基盤に育てます。

Ansibleの設計原則をハンズオン環境で体系的に習得したい方は、ぜひ詳細をご覧ください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
Ansible実践ハンズオンの詳細を見る >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。