こんな状況に直面したことはないでしょうか。Ansibleは
commandモジュールやshellモジュールを使う場合、終了コードが0なら「OK(成功)」、0以外なら「failed(失敗)」と判断しますが、「変更があったか否か」についてはPlaybook側で明示的に定義しない限り、常に「changed」と返します。この記事では、failed_when・changed_when・ignore_errorsの3つのキーワードを使い、タスクの成否と変更状態をPlaybook側から精密に制御する方法を解説します。
register変数を活用した条件判定からset_factによる変数の加工・再利用まで、RHEL 9.4 / Ubuntu 24.04 LTS の実環境で動作確認した出力例を交えながら、現場で即使えるパターンを紹介します。この記事のポイント
・failed_whenで「終了コード以外の失敗条件」をPlaybook側で定義できる
・changed_whenでcommandの「変更有無」の判定ロジックを上書きする
・ignore_errorsはエラー後も処理を継続するが、乱用は要注意
・registerとセットで使い、stdout/rcを条件式に組み込む設計が基本
・set_factでregister変数を加工すると、後続タスクで再利用しやすくなる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
AnsibleがタスクをOK・changed・failedに分類する仕組み
Ansibleがタスクを実行すると、結果は次の4種類のうちいずれかで報告されます。・ok:タスクが成功し、変更なし
・changed:タスクが成功し、何かが変更された
・failed:タスクが失敗した
・skipped:whenなどの条件によってスキップされた
fileモジュールやdnfモジュールのように、Ansible組み込みのモジュールを使う場合は、このOK/changed/failedの判定がモジュール内部に実装されています。たとえばファイルを作成するとき、すでに同名のファイルが存在すれば「ok」、新規作成なら「changed」と適切に判定してくれます。問題が起きやすいのは
commandモジュールやshellモジュールを使うときです。これらは汎用コマンド実行モジュールであり、実行したコマンドが何かを変更したかどうかをAnsibleは知る術がありません。そのため、デフォルトでは「終了コード(rc)が0なら成功、0以外なら失敗」「成功した場合は常にchanged」という単純なルールが適用されます。この挙動を変えるのが、failed_whenとchanged_whenです。どちらも
registerでタスクの実行結果を変数に格納し、その値を条件式で参照するのが基本パターンです。registerはデータの「入口」、failed_when/changed_whenはその値を評価する「ゲート」と捉えると、設計の意図が整理しやすくなります。failed_whenでタスクの失敗条件を自分で定義する
failed_whenを使うと、「どのような状態を失敗とみなすか」をJinja2の条件式で定義できます。1. 基本構文とregisterの組み合わせ
failed_whenは単体では動きません。まずregisterでタスクの実行結果を変数に格納し、その変数の値をfailed_whenの条件式で参照します。# サービスの稼働状態を確認するタスク例 - name: nginxの稼働状態を確認する command: systemctl is-active nginx register: nginx_status failed_when: nginx_status.rc not in [0, 3]
register: nginx_statusで実行結果を変数nginx_statusに格納します。この変数には以下のキーが含まれます。・rc:コマンドの終了コード(Return Code)
・stdout:標準出力の内容(文字列。末尾に改行を含む場合がある)
・stderr:標準エラーの内容(文字列)
・stdout_lines:標準出力を行ごとに分割したリスト
・failed:Ansibleがタスク失敗と判定したかどうかのブール値
・changed:Ansibleが変更ありと判定したかどうかのブール値
上の例では、終了コードが0(active)か3(inactive)以外の場合に失敗とみなします。
systemctl is-activeはサービスが存在しない場合に終了コード4を返すため、そのケースのみを失敗として検出する設計になっています。なお、
stdoutは文字列(str型)であるため、数値と比較する際は| intフィルタで整数に変換する必要があります。stdoutとstdout_linesの使い分けは「単一行ならstdout(末尾をtrimして使う)、複数行を一行ずつ処理するならstdout_lines」が基本です。2. 出力内容(stdout)で失敗を判断する
終了コードだけでなく、コマンドの出力内容を見て失敗を判断する場合もfailed_whenが使えます。以下は実サーバー上でのad-hoc実行結果です。# ディスク使用率が95%以上なら失敗とみなすタスク - name: ルートパーティションのディスク使用率を確認する command: df --output=pcent / register: disk_result changed_when: false failed_when: disk_result.stdout_lines[-1] | replace('%','') | trim | int >= 95
[tomohiro@ansible-ctl ~]$ ansible -m command -a "df --output=pcent /" web01 -i inventory/prod web01 | SUCCESS => { "changed": true, "cmd": ["df", "--output=pcent", "/"], "rc": 0, "stdout": "Use%\n 42%", "stdout_lines": [ "Use%", " 42%" ] }
stdout_lines[-1]でリストの最後の要素( 42%の行)を取り出し、| replace('%','')でパーセント記号を除去、| trimで前後の空白を除去したうえで| intフィルタで整数に変換して比較します。ディスク使用率が95%以上になった瞬間にタスクが失敗ステータスを返し、alertのhandlerやrescueブロックにつなげる設計が可能です。3. 複数条件をandで組み合わせる
failed_whenの条件はJinja2の論理演算子(and/or/not)で組み合わせられます。リスト形式で並べるとすべての条件がAND評価になります。# 終了コードが0以外 かつ "not installed" が出力に含まれない場合のみ失敗 - name: パッケージの存在確認 command: rpm -q mypackage register: rpm_result failed_when: - rpm_result.rc != 0 - "'not installed' not in rpm_result.stdout"
changed_whenで「変更あり」の定義を上書きする
changed_whenは「このタスクが何かを変更したか」の判定ロジックを上書きするキーワードです。commandモジュールのデフォルト動作(成功時は常にchanged)を制御するために頻繁に使います。1. changed_when: falseで常に「変更なし」にする
最もシンプルな使い方はchanged_when: falseです。確認のためだけに実行するコマンドには「変更なし」とみなすのが適切です。# 確認系コマンドにはchanged_when: falseを付ける - name: PHPのバージョンを確認する command: php -v register: php_version changed_when: false - name: バージョン情報を表示する debug: msg: "{{ php_version.stdout_lines[0] }}"
changed_when: falseを付けておくと、毎回「changed」の黄色が並んで結果が見づらくなる問題を解消できます。2. 出力内容で変更を判断する実践例
インストールスクリプトのように「すでにインストール済みの場合はスキップし、新規インストールの場合だけchangedにしたい」という場面で特に役立ちます。# カスタムスクリプトでインストール状態を検出する - name: myappをインストールする command: /opt/install-myapp.sh register: install_result changed_when: "'already installed' not in install_result.stdout" failed_when: install_result.rc != 0
failed_whenと組み合わせることで、成否と変更の両方を細かく制御できます。
Ansible実践ハンズオンの詳細を見る >>
ignore_errorsとfailed_whenの違いと使い分け
「タスクが失敗しても処理を続けたい」という場面ではignore_errorsとfailed_whenの2つの手段があります。両者の動作と使いどころは明確に異なります。ignore_errors: trueを使うと、タスクが失敗してもPlaybookの実行を継続します。ただしタスクの最終的なステータスは「failed」のままであり、Playbookのサマリーにはfailedとして記録されます。# 旧設定ファイルの削除を試みる(存在しなくてもエラーにしない) - name: 旧設定ファイルを削除する(任意) command: rm /etc/app/old-config.conf register: rm_result ignore_errors: true - name: 削除成功時のみメッセージを表示する debug: msg: "旧設定ファイルを削除しました" when: rm_result.rc == 0
failed_when: falseを使うとタスクは決して失敗ステータスにはなりません。サマリーには「ok」または「changed」として記録されます。使い分けの判断基準は以下の通りです。
・ignore_errors:「失敗しても処理を続けたい」が、失敗の記録はサマリーに残したい場面
・failed_when: false:「そもそも失敗という概念がないタスク」として扱いたい場面
・failed_whenで条件を絞る:「一部の終了コードや出力のみを本当の失敗とみなしたい」場面
【重要】ignore_errorsの乱用が招くリスク
本番環境ではignore_errorsを多用するのは禁止に近い扱いとして意識してください。私のセミナーでも「なぜPlaybookが止まるか分からないからignore_errors: trueを全タスクに付けた」という事例を見かけることがあります。こうすると本来検出すべき障害がサイレントに通過してしまい、原因特定が困難になります。ignore_errorsを使う場面は「ファイルが存在しない場合のrm」「サービスが起動していない場合の停止試行」など、失敗しても業務に影響がないことが明確な任意処理に限定してください。失敗の原因を明示的に定義できる場面では、必ずfailed_whenで条件を絞るのが鉄則です。実務でよく使うパターン集
1. サービスの状態確認を安全に行う
systemctl is-activeはサービスの状態に応じて終了コードが変わるコマンドです。ansible.builtin.service_factsモジュールが使えない環境や、特定コンテナ環境での状態確認に活用できます。# サービスの状態を取得し、後続タスクの条件分岐に使う - name: nginxの稼働状態を取得する command: systemctl is-active nginx register: nginx_status changed_when: false failed_when: false - name: nginxが停止している場合にアラートを出す debug: msg: "nginx is not running (status: {{ nginx_status.stdout }})" when: nginx_status.rc != 0
failed_when: falseとchanged_when: falseを両方指定することで、このタスクは「純粋な確認専用」の位置づけになります。後続のタスクでwhen条件としてnginx_status変数を参照できます。2. カスタムバイナリの存在確認とインストール設計
dnfやaptモジュールは冪等性を内部に持っていますが、tarball展開やカスタムインストーラーを使う場面ではcommandモジュールとの組み合わせが必要です。# インストール済みチェック→未インストール時のみインストール実行 - name: myappがインストール済みかチェックする command: which myapp register: which_result changed_when: false failed_when: false - name: myappをインストールする command: /opt/install-myapp.sh when: which_result.rc != 0 register: install_result changed_when: "'installed successfully' in install_result.stdout" failed_when: install_result.rc != 0
3. 外部スクリプトの終了コードと出力を組み合わせた判定
自社開発のオペレーションスクリプトを呼び出す場面では、終了コードだけでは成否の判定が難しいケースがあります。出力内容との組み合わせで判断します。# デプロイスクリプトを呼び出す - name: アプリをデプロイする command: /opt/deploy.sh {{ app_version }} register: deploy_result failed_when: - deploy_result.rc != 0 - "'DEPLOY_OK' not in deploy_result.stdout" changed_when: "'DEPLOY_OK' in deploy_result.stdout"
4. set_factでregister変数を加工・再利用する
failed_when/changed_whenの条件式に複雑なフィルタチェーンを直接書くと可読性が落ちます。set_factでregister変数を事前に加工・整形してから条件式で使うと、Playbookの見通しが大幅に向上します。set_factで定義した変数はPlaybook全体の後続タスクから参照できます。# register変数をset_factで加工してから判定・再利用する - name: ディスク使用率を取得する command: df --output=pcent / register: df_result changed_when: false failed_when: false # フィルタチェーンをset_factで先に処理し、意味のある変数名を付ける - name: ディスク使用率を整数値として保存する set_fact: disk_usage_pct: "{{ df_result.stdout_lines[-1] | replace('%','') | trim | int }}" - name: ディスク使用率が95%以上なら警告を出す debug: msg: "警告: ディスク使用率が {{ disk_usage_pct }}% に達しています" when: disk_usage_pct | int >= 95
failed_whenの条件式に| replace | trim | intのようなフィルタチェーンを直接書くと、後から読み返したときに意図がつかみにくくなります。set_factでdisk_usage_pctという名前を付けておくと、条件判定の意図が一目で分かります。set_factで加工した変数は後続の複数タスクで再利用できるため、同じフィルタチェーンを何度も書く必要もなくなります。5. loopとregisterを組み合わせた複数サービスの一括判定
複数のサービスや対象を一括チェックする場面では、loopとregisterを組み合わせます。loopを使った場合、register変数の構造が変わる点を把握しておく必要があります。# 複数サービスの稼働状態を一括チェックする - name: 複数サービスの稼働状態を確認する command: systemctl is-active {{ item }} loop: - nginx - mariadb - php-fpm register: service_check_results changed_when: false failed_when: false # loopを使うとresultsという配列に各アイテムの結果が格納される # item.item → 元のloopアイテム(サービス名) # item.rc → そのコマンドの終了コード - name: 停止しているサービスを表示する debug: msg: "停止中: {{ item.item }} (rc={{ item.rc }})" loop: "{{ service_check_results.results }}" when: item.rc != 0
service_check_resultsはresultsというリストを持つ辞書になります。個々の結果にはitem.item(元ループ変数)とitem.rc(終了コード)でアクセスします。よくあるミスはitem.stdoutとitem.itemを混同するケースで、item.itemが元のループ変数(ここではサービス名)、item.stdoutがそのアイテムのコマンド出力であることを意識して使い分けてください。トラブルシュート:registerとdebugで動作を確認する
failed_whenやchanged_whenの条件が想定通りに動いているか確認するときは、debugモジュールを組み合わせるのが基本です。# registerの中身をすべて表示してデバッグする - name: コマンドを実行する command: df --output=pcent / register: df_result changed_when: false failed_when: false - name: df_resultの中身を確認する(デバッグ用・運用時は削除) debug: var: df_result
debug: var: df_resultを実行すると、rc・stdout・stderr・stdout_linesの全内容がAnsible実行時に表示されます。failed_whenの条件式を作る前に、まずこの方法で実際の値を確認してから条件式を設計するのが、手戻りを防ぐコツです。また、
ansible-playbook実行時に-vオプション(詳細)または-vv(さらに詳細)を付けると、タスクの実行結果が詳しく表示されます。条件式が正しく評価されているかを確認する際に役立ちます。VARIABLE IS NOT DEFINED エラーへの対処
failed_when/changed_whenの条件式でregister変数を参照する際、「VARIABLE IS NOT DEFINED」エラーが発生することがあります。主な原因は2つです。# エラー例 TASK [変数を参照するタスク] *** fatal: [web01]: FAILED! => { "msg": "The task includes an option with an undefined variable. 'nginx_status' is undefined" }
default()フィルタでデフォルト値を指定することで対処できます。・別のPlayで定義した変数を参照しようとしている:registerで定義した変数はホストごとに保存されます。別Playから参照するにはset_factで明示的に変数を定義し直す必要があります。
# default()フィルタでundefined対策をする - name: 変数が未定義でもエラーにしない debug: msg: "{{ nginx_status.stdout | default('未実行') }}"
stdoutの末尾改行とintフィルタの型変換
よくある実務上の注意として、Jinja2変数の型を間違えるケースがあります。stdoutは文字列(str)なので、数値と比較する際は| intフィルタで整数に変換する必要があります。また、コマンドの標準出力には末尾に改行が含まれることが多く、文字列比較で期待通りの結果が得られない原因になります。trimフィルタで除去しておくのが安全です。# NG: stdout_lines[-1]は文字列のため、整数との直接比較は意図通りにならない場合がある failed_when: disk_result.stdout_lines[-1] >= 95 # OK: | replaceでパーセント記号を除去し、| trimで空白を除去、| intで整数に変換してから比較する failed_when: disk_result.stdout_lines[-1] | replace('%','') | trim | int >= 95 # stdoutの末尾改行が文字列比較に影響する場合はtrimを使う # NG: "{{ hostname_result.stdout == 'web01' }}" → 末尾の改行で一致しない # OK: "{{ hostname_result.stdout | trim == 'web01' }}" → 確実に一致する
本記事のまとめ
| キーワード | 役割 | 典型的な使い方 |
|---|---|---|
failed_when |
失敗条件をPlaybook側で定義する | failed_when: result.rc != 0 and "error" in result.stderr |
changed_when |
変更ありの条件をPlaybook側で定義する | changed_when: "'installed' in result.stdout" |
changed_when: false |
常に「変更なし」とみなす(確認系コマンド専用) | 確認・情報収集専用のタスクに必須 |
ignore_errors: true |
失敗してもPlaybookを継続させる | 任意処理・存在しても問題ないファイル削除など |
register |
タスクの実行結果を変数に格納する | register: resultでrc・stdout・stderrを保存 |
set_fact |
register変数を加工・派生させてPlaybook全体で再利用する | set_fact: disk_pct: "{{ result.stdout | trim | int }}" |
commandモジュールやshellモジュールを使うPlaybookでは、failed_whenとchanged_whenを意識的に設計することが、堅牢で読みやすい構成管理の基礎になります。まずregisterで変数に結果を格納し、debugモジュールで実際の値を確認してから条件式を組み立てる習慣を持つと、試行錯誤を最小限に抑えられます。set_factを組み合わせることで複雑なフィルタチェーンを整理し、Playbookの長期保守性も向上します。Ansible実践ハンズオンの詳細を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 次のページへ:Ansible・Puppet・Chefの違いを比較する|エージェントレス構成管理を選ぶ判断基準と導入設計
- 前のページへ:Ansibleの接続設計|コントロールノードとSSH鍵・踏み台サーバーの設定からインベントリ変数まで
- この記事の属するカテゴリ:Ansibleへ戻る

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