0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ansibleが理解できない理由はLinuxにあった【Ansible編】第4回:なぜ条件分岐が壊れるのか

0
Last updated at Posted at 2026-06-29

📚 Ansibleが理解できない理由はLinuxにあった【Ansible編】第4回:なぜ条件分岐が壊れるのか(Ansible編)

⓪ なぜShellの知識がないとAnsibleは壊れるのか
① なぜshellモジュールは危険なのか
② なぜmoduleを使うと安全になるのか
③ なぜ毎回changedになるのか
④ なぜ条件分岐が壊れるのか(Ansible編)
⑤ なぜ変数が意図通りに扱えないのか
⑥ なぜタスクの順序で壊れるのか
⑦ なぜエラー制御が破綻するのか
⑧ なぜ非同期・待機で失敗するのか
⑨ なぜPlaybookが読めないのか
⑩ なぜAnsibleを正しく設計できるようになるのか


🗺️ 初めての方・シリーズの全体像を知りたい方はこちら

本記事は、OSの仕組みからAnsible設計までを繋ぐ連載シリーズの一部です。
「どこから読み始めればいいか」あるいは、「OS/Shell/Ansible編の関係性」 について把握されたい場合は、以下の統合ガイドで整理しています。

→ Ansibleが理解できない理由はLinuxにあった|統合ガイド


📑 【Ansible編】全体のまとめはこちら
「Ansibleが理解できない理由はLinuxにあった【Ansible編】まとめ」


📑 連載の移動
前の記事:【Ansible編】第3回 | 次の記事:【Ansible編】第5回


📋 目次

  1. 前回の振り返り(第3回)
  2. 逆引き辞典との連動
  3. はじめに(この記事のスタンス)
  4. 問題定義:「成功したはずのタスク」でwhen句が失敗する要因
  5. 構造解剖:Ansibleにおける条件評価のプロセス
  6. 原因分析:Shellの終了ステータスとJinja2の評価ロジックの乖離
  7. 設計:register変数とテスト(is success / is failed)の適切な運用
  8. 本質:データ型(Type)の不一致による判定ミスとregister変数の構造
  9. まとめ
  10. 次回予告
  11. 連載一覧:Ansibleが理解できない理由はLinuxにあった【Ansible編】

※本記事の位置とシリーズ全体の関係を先に確認します。


:map: シリーズ全体構造(学習 × 問題解決)


本シリーズは
「理解(各記事)」と「問題解決(逆引き辞典)」を組み合わせて
スキルを身につける構成になっています。

この図は、どこから学び、どこに進めばよいかを示した“ロードマップ”です。


📍 現在の位置


現在はこの図の「Ansible編の第4回」になります。


1. 前回の振り返り(第3回)

前回 は、Ansibleの「変更(changed)」判定のメカニズムと冪等性(Idempotency)の設計原則について整理しました。専用モジュールが内部で行っている「現状調査とあるべき状態の比較」を理解し、shell モジュール等では changed_when を用いて明示的に状態を定義する必要があることを解説しました


↑ 目次に戻る


2. 逆引き辞典との連動

「設計上の原因」を特定したい場合は、以下の逆引き辞典を活用してください。

Ansibleが理解できない理由はLinuxにあった【Ansible編】:トラブル逆引き辞典(設計パターン版)

  • まず「逆引き辞典」で該当ステップを特定する
  • 次に「本編(各連載記事)」で仕組みを理解する

↑ 目次に戻る


3. はじめに(この記事のスタンス)

Ansibleにおける条件分岐(when 句)は、Playbookの論理制御を担う重要な要素です。しかし、Shellスクリプトにおける条件判定の感覚で記述すると、意図しない挙動やエラーに直面することがあります。

本記事では、Ansibleが条件を判定する際の「データの持ち方」と「評価のタイミング」を整理します。Shellの終了ステータス(rc)がどのようにAnsibleの変数へと変換され、Jinja2エンジンによって評価されるのか。そのプロセスを理解することで、意図通りに動作する論理制御の設計手法を解説します。


↑ 目次に戻る


4. 問題定義:「成功したはずのタスク」でwhen句が失敗する要因

実務において、以下のような「条件分岐の不具合」が起きることがあります。

  • 期待値の誤認: コマンドが正常終了(rc=0)したにもかかわらず、when 句で「失敗」と判定される、あるいはその逆が発生する。
  • データ型の混同: 標準出力(stdout)から得られた数値を比較しようとして、型不一致(Type Error)によりタスクが停止する。
  • 未定義変数の参照: 前のタスクがスキップされたことにより、register 変数が期待した構造を持たず、後続の when 句が評価不能になる。

これらの問題は、Ansibleが内部で保持するデータ構造と、Jinja2テンプレートエンジンの評価仕様を正しく把握していないことが原因です。


↑ 目次に戻る


5. 構造解剖:Ansibleにおける条件評価のプロセス

Ansibleのタスク実行と条件評価は、以下の物理的なステップで行われます。

🔄 処理フローの視覚化


📌 構造のポイント

  1. タスク実行と結果の保持: リモートノードでモジュールが実行され、結果がJSON形式で制御ノードに返される。
  2. 変数の登録(register): 返されたJSONデータが、指定した変数名に格納される。この際、rc(戻り値)、stdout(標準出力)、changed(変更有無)などの属性が保持される。
  3. 条件評価(Evaluation): 後続タスクの when 句において、Jinja2エンジンが登録された変数の内容を評価し、真(True)か偽(False)を判定する。

重要なのは、「直前のコマンドの終了ステータスを直接見ているのではなく、JSON構造の中の特定の値を評価している」 という点です。


↑ 目次に戻る


6. 原因分析:Shellの終了ステータスとJinja2の評価ロジックの乖離

LinuxのShell(bash等)において、終了ステータス 0 は「正常終了(成功)」を意味し、論理的な「真」として扱われます。一方、PythonやJinja2などのテンプレートエンジンにおける論理評価では、数値の 0 は「偽(False)」、0 以外(1など)が「真(True)」として評価されるのが原則です。

Ansibleの when 句に when: result.rc とだけ記述した場合、この仕様の違いが原因で問題が発生します。Jinja2はShellの文脈(0=成功)を解釈せず、純粋な数値の 0 を「偽」とみなすため、コマンドが正常終了したにもかかわらずタスクがスキップされます。

この挙動を実証するため、コマンドの実行結果からデータを分離し、when 句の評価だけを単体テストします。

🛠️ 挙動検証:when句の評価ロジック

  1. データ生成(1-1): コントロールノード(localhost)でコマンドを実行し、返された register 変数の内容をJSONファイルへ保存する。
  2. 判定評価(1-2): 保存されたJSONファイルを読み込み、when 句の論理評価の挙動を確認する。

【検証1-1】正常終了(rc=0)データの生成

ファイル名: generate_rc0_result.yml

---
- name: 1-1. コマンドを実行してrc=0を保存する
  hosts: localhost
  gather_facts: false

  tasks:
    - name: コマンドを実行 (rc=0)
      ansible.builtin.shell: "exit 0"
      register: result_success

    - name: 結果を一時ファイルに保存
      ansible.builtin.copy:
        content: "{{ result_success | to_json }}"
        dest: /tmp/ansible_result_rc0.json

【コントローラーノード側】

実行コマンド

ansible-playbook -i inventory.ini generate_rc0_result.yml

▼ 実行結果

(ansible) [ansible@localhost workspace]$ ansible-playbook -i inventory.ini generate_rc0_result.yml

PLAY [1-1. コマンドを実行してrc=0を保存する] ************************************************************************************************************************************************

TASK [コマンドを実行 (rc=0)] ****************************************************************************************************************************************************************
changed: [localhost]

TASK [結果を一時ファイルに保存] *************************************************************************************************************************************************************
changed: [localhost]

PLAY RECAP **********************************************************************************************************************************************************************************
localhost                  : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

▼ 保存されたJSONデータ(/tmp/ansible_result_rc0.json)

(ansible) [ansible@localhost workspace]$ cat /tmp/ansible_result_rc0.json
{"changed": true, "stdout": "", "stderr": "", "rc": 0, "cmd": "exit 0", "start": "2026-05-24 12:21:36.132128", "end": "2026-05-24 12:21:36.187365", "delta": "0:00:00.055237", "msg": "", "std

※検証用として、意図的に rc が 0 となる正常終了のデータを事前に作成・保存しています。

【検証1-2】正常終了(rc=0)データの判定評価

ファイル名: check_ansible_when_rc0.yml

---
- name: 1-2. rc=0 のときの when 句の挙動だけを検証する
  hosts: localhost
  gather_facts: false

  tasks:
    - name: 保存した結果を読み込む
      ansible.builtin.slurp:
        src: /tmp/ansible_result_rc0.json
      register: saved_data

    - name: 変数として展開
      ansible.builtin.set_fact:
        result: "{{ saved_data.content | b64decode | from_json }}"

    - name: [判定チェック] when: result.rc
      ansible.builtin.debug:
        msg: " rc=0 は【真 (True)】と評価され、このタスクが実行されました!"
      when: result.rc

【コントローラーノード側】

実行コマンド

ansible-playbook -i inventory.ini check_ansible_when_rc0.yml

▼ 実行結果

(ansible) [ansible@localhost workspace]$ ansible-playbook -i inventory.ini check_ansible_when_rc0.yml

PLAY [1-2. rc=0 のときの when 句の挙動だけを検証する] ***************************************************************************************************************************************

TASK [保存した結果を読み込む] ***************************************************************************************************************************************************************
ok: [localhost]

TASK [変数として展開] ***********************************************************************************************************************************************************************
ok: [localhost]

TASK [[判定チェック] when: result.rc] *******************************************************************************************************************************************************
skipping: [localhost]

PLAY RECAP **********************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

結論

検証1-2の結果が示す通り、[判定チェック] タスクは skipping(スキップ)となり、skipped=1 として記録されました。

Ansibleの when 句において result.rc(値は数値の 0)を直接評価させた場合、Jinja2はそれを「偽(False)」と判定することが確認できます。

Shellスクリプトにおける「0は正常終了(成功)」という前提のまま記述すると、意図に反して後続タスクがスキップされるため、明示的な比較演算やテスト構文の使用が必要です。


🛠️ 挙動検証:when句の評価ロジック(異常終了の場合)

  1. データ生成(2-1): コントロールノード(localhost)でコマンドを実行し、異常終了(rc=1)した際の register 変数の内容をJSONファイルへ保存する。

  2. 判定評価(2-2): 保存されたJSONファイルを読み込み、when 句の論理評価の挙動を確認する。

【検証2-1】異常終了(rc=1)データの生成

ファイル名: check_nginx_syntax_fail.yml

---
- name: 2-1. コマンドを実行してrc=1を保存する
  hosts: localhost
  gather_facts: false

  tasks:
    - name: コマンドを実行 (rc=1)
      ansible.builtin.shell: "exit 1"
      register: result_fail
      ignore_errors: true  # 終了ステータス1でもPlaybookを強制停止させないため

    - name: 結果を一時ファイルに保存
      ansible.builtin.copy:
        content: "{{ result_fail | to_json }}"
        dest: /tmp/ansible_result_rc1.json

【コントローラーノード側】

実行コマンド

ansible-playbook -i inventory.ini check_nginx_syntax_fail.yml

▼ 実行結果

(ansible) [ansible@localhost workspace]$ ansible-playbook -i inventory.ini check_nginx_syntax_fail.yml

PLAY [2-1. コマンドを実行してrc=1を保存する] ************************************************************************************************************************************************

TASK [コマンドを実行 (rc=1)] ****************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": "exit 1", "delta": "0:00:00.018940", "end": "2026-05-24 14:34:06.314263", "msg": "non-zero return code", "rc": 1, "start": "2026-05-24 14:34:06.295323", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
...ignoring

TASK [結果を一時ファイルに保存] *************************************************************************************************************************************************************
changed: [localhost]

PLAY RECAP **********************************************************************************************************************************************************************************
localhost                  : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=1

▼ 保存されたJSONデータ(/tmp/ansible_result_rc1.json)

(ansible) [ansible@localhost workspace]$ cat /tmp/ansible_result_rc1.json
{"changed": true, "stdout": "", "stderr": "", "rc": 1, "cmd": "exit 1", "start": "2026-05-24 14:34:06.295323", "end": "2026-05-24 14:34:06.314263", "delta": "0:00:00.018940", "failed": true, "msg": "non-zero return code", "stdout_lines": [], "stderr_lines": []}

※検証用として、意図的に rc が 1 となる「異常終了」のデータを事前に作成・保存しています。

【検証2-2】異常終了(rc=1)データの判定評価

ファイル名: check_ansible_when_rc1.yml

---
- name: 2-2. rc=1 のときの when 句の挙動だけを検証する
  hosts: localhost
  gather_facts: false

  tasks:
    - name: 保存した結果を読み込む
      ansible.builtin.slurp:
        src: /tmp/ansible_result_rc1.json
      register: saved_data

    - name: 変数として展開
      ansible.builtin.set_fact:
        result: "{{ saved_data.content | b64decode | from_json }}"

    - name: "[判定チェック] when: result.rc"
      ansible.builtin.debug:
        msg: " rc=1 は【真 (True)】と評価され、このタスクが実行されました!"
      when: result.rc

【コントローラーノード側】

実行コマンド

ansible-playbook -i inventory.ini check_ansible_when_rc1.yml

▼ 実行結果

(ansible) [ansible@localhost workspace]$ ansible-playbook -i inventory.ini check_ansible_when_rc1.yml

PLAY [2-2. rc=1 のときの when 句の挙動だけを検証する] ***************************************************************************************************************************************

TASK [保存した結果を読み込む] ***************************************************************************************************************************************************************
ok: [localhost]

TASK [変数として展開] ***********************************************************************************************************************************************************************
ok: [localhost]

TASK [[判定チェック] when: result.rc] *******************************************************************************************************************************************************
ok: [localhost] => {
    "msg": " rc=1 は【真 (True)】と評価され、このタスクが実行されました!"
}

PLAY RECAP **********************************************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

結論

検証2-2の結果が示す通り、[判定チェック] タスクはスキップされることなく正常に実行され、skipped=0 として記録されました。

Ansibleの when 句において result.rc(値は数値の 1)を評価させた場合、Jinja2はそれを「真(True)」と判定することが確認できます。


↑ 目次に戻る


7. 設計:register変数とテスト(is success / is failed)の適切な運用

論理的な誤解を避けるため、Ansibleでは「テスト(tests)」と呼ばれる構文を使用することが推奨されています。

あまり推奨されない書き方(結果の直接比較)

- name: 後続タスク
  command: echo "run"
  when: result.rc == 0

推奨:Ansibleテスト(is success)を使用する

- name: 前のタスク
  command: /usr/bin/some_command
  register: result
  ignore_errors: true  # 失敗しても処理を継続し、後続のwhenで判定するため

- name: 成功時のみ実行
  command: echo "task success"
  when: result is success
  • 設計意図: is success を使用することで、Jinja2による単一の数値評価(0か否か)ではなく、Ansibleがfailedフラグを参照して判定します。

🛠️ 挙動検証:テスト構文(is success)による論理評価の正常化

第6セクションの検証で確認した「Shell(0=成功)とJinja2(0=False)の乖離」が、この推奨構文によってどのように解決されるのかを検証します。

検証にあたり、前章で作成・保存したコマンド正常終了時の結果オブジェクト(JSONデータ)を再利用します。中身を確認します。

(ansible) [ansible@localhost workspace]$ cat /tmp/ansible_result_rc0.json
{"changed": true, "stdout": "", "stderr": "", "rc": 0, "cmd": "exit 0", "start": "2026-05-24 12:21:36.132128", "end": "2026-05-24 12:21:36.187365", "delta": "0:00:00.055237", "msg": "", "stdout_lines": [], "stderr_lines": [], "failed": false}(ansible)

※このデータを読み込み、when 句の評価単体をテストします。

【検証3-1】正常終了(rc=0)データに対する is success の評価

ファイル名: check_ansible_test_success.yml

---
- name: 3-1. rc=0 のときの is success の挙動を検証する
  hosts: localhost
  gather_facts: false

  tasks:
    - name: 保存した結果(rc=0)を読み込む
      ansible.builtin.slurp:
        src: /tmp/ansible_result_rc0.json
      register: saved_data

    - name: 変数として展開
      ansible.builtin.set_fact:
        result: "{{ saved_data.content | b64decode | from_json }}"

    - name: "[判定チェック] when: result is success"
      ansible.builtin.debug:
        msg: "is success により、rc=0 は【真 (True)】と評価され、このタスクが実行されました!"
      when: result is success

【コントローラーノード側】

実行コマンド

ansible-playbook -i inventory.ini check_ansible_test_success.yml

▼ 実行結果

(ansible) [ansible@localhost workspace]$ ansible-playbook -i inventory.ini check_ansible_test_success.yml

PLAY [3-1. rc=0 のときの is success の挙動を検証する] ***************************************************************************************************************************************

TASK [保存した結果(rc=0)を読み込む] *******************************************************************************************************************************************************
ok: [localhost]

TASK [変数として展開] ***********************************************************************************************************************************************************************
ok: [localhost]

TASK [[判定チェック] when: result is success] ***********************************************************************************************************************************************
ok: [localhost] => {
    "msg": "is success により、rc=0 は【真 (True)】と評価され、このタスクが実行されました!"
}

PLAY RECAP **********************************************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

```plaintext

結論

検証3-1の結果が示す通り、[判定チェック] タスクはスキップされることなく正常に実行され、skipped=0 として記録されました。

第6セクションの検証では、when: result.rc と直接数値を評価させたために、Jinja2の「数値の 0 = 偽(False)」というルールがそのまま適用され、タスクがスキップされました。

しかし、when: result is success というAnsibleテスト構文に変えたことで、「真(True)」として評価されました。内部では以下の判定が行われています。

  1. JSONデータの確認: Ansibleは "rc": 0 や "failed": false というステータス値を確認します。
  2. ルールの翻訳: is success 構文は、「Jinja2の世界では 0 は偽(False)だが、Linuxシェルの世界では rc=0 は成功を意味する」というズレを吸収するため、failedフラグを参照して判定します。
  3. 論理評価のシールド: failed: false をもとに、Jinja2本来の「0=偽」という判定を打ち消し、Ansible側で「このタスクの結果は成功、つまり真(True)である」と判定して条件を満たします。

このように、is success は failedフラグを参照することで、シェル側とJinja2側の真偽値ルールのズレを吸収する役割を果たしています。


【検証3-2】異常終了(rc=1)データに対する is success / is failed の評価

まずは、前章で保存した異常終了時の結果オブジェクト(JSONデータ)の中身を確認します。

※注: 本検証では、エラー時の挙動(stderrの出力など)をより分かりやすく視覚化するため、検証2-1の exit 1 ではなく、存在しないファイルを指定した ls not_exist_file コマンドの実行結果データを別途保存して使用しています。

(ansible) [ansible@localhost workspace]$ cat /tmp/ansible_result_rc1.json
{"changed": true, "stdout": "", "stderr": "ls: cannot access 'not_exist_file': No such file or directory", "rc": 1, "cmd": "ls not_exist_file", "start": "2026-05-24 12:21:40.410291", "end": "2026-05-24 12:21:40.465103", "delta": "0:00:00.054812", "msg": "non-zero return code", "stdout_lines": [], "stderr_lines": ["ls: cannot access 'not_exist_file': No such file or directory"], "failed": true}(ansible)

ご覧の通り、rc: 1(戻り値1)および "failed": true(失敗フラグ)が記録されています。

このデータを読み込み、is success(成功判定)と is failed(失敗判定)の2つのタスクにぶつけて、どちらが実行され、どちらがスキップされるかを検証します。

ファイル名: check_ansible_test_failed.yml

---
- name: 3-2. rc=1 のときのテスト構文の挙動を検証する
  hosts: localhost
  gather_facts: false

  tasks:
    - name: 保存した結果(rc=1)を読み込む
      ansible.builtin.slurp:
        src: /tmp/ansible_result_rc1.json
      register: saved_data

    - name: 変数として展開
      ansible.builtin.set_fact:
        result: "{{ saved_data.content | b64decode | from_json }}"

    - name: "[判定チェック1] when: result is success"
      ansible.builtin.debug:
        msg: "【成功判定】このタスクは実行されました。"
      when: result is success

    - name: "[判定チェック2] when: result is failed"
      ansible.builtin.debug:
        msg: "【失敗判定】is failed により、rc=1 は正常に【真 (True)】と評価され、このタスクが実行されました!"
      when: result is failed

【コントローラーノード側】

実行コマンド

ansible-playbook -i inventory.ini check_ansible_test_failed.yml

▼ 実行結果

(ansible) [root@localhost workspace]# ansible-playbook -i inventory.ini check_ansible_test_failed.yml

PLAY [3-2. rc=1 のときのテスト構文の挙動を検証する] *****************************************************************************************************************************************

TASK [保存した結果(rc=1)を読み込む] *******************************************************************************************************************************************************
ok: [localhost]

TASK [変数として展開] ***********************************************************************************************************************************************************************
ok: [localhost]

TASK [[判定チェック1] when: result is success] **********************************************************************************************************************************************
skipping: [localhost]

TASK [[判定チェック2] when: result is failed] ***********************************************************************************************************************************************
ok: [localhost] => {
    "msg": "【失敗判定】is failed により、rc=1 は正常に【真 (True)】と評価され、このタスクが実行されました!"
}

PLAY RECAP **********************************************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

結論

検証3-2の結果から、期待通りのタスク制御が行われたことが確認できました。

実行ログの通り、以下のように制御されています。

  • when: result is success(成功判定)
    rc=1(異常終了)のデータに対して「これは正常終了ではない(偽)」と判断され、タスクが skipping になりました。
  • when: result is failed(失敗判定)
    「これは異常終了(失敗)である(真)」と判断され、when 句が ok と評価されてデバッグメッセージが出力されました。

内部では、検証3-1と同様に、JSONデータの値をもとにした判定が行われています。

  1. JSONデータの確認: Ansibleは "rc": 1 や "failed": true というステータス値を確認します。
  2. ルールの翻訳: テスト構文を使わずに when: result.rc と直接数値を評価させてしまうと、Jinja2のルールにより「1以上の数値は真(True)」と判定され、エラーなのにタスクが実行されてしまいます。
  3. 論理評価の適合: is failed を使うことで、Ansibleは「Jinja2の世界では 1 は真(True)になってしまうが、シェル側では rc=1 は異常終了(失敗)を意味する」というズレを吸収します。そして failedフラグを参照することで、「このタスクの結果は失敗、つまり『失敗した状態』という条件が真(True)である」と判定してタスクを実行します。

「Linuxシェル」と「Ansible(Jinja2環境)」の間にある真偽値ルールの違いを整理した一覧が以下です。

コマンドの実態(Shell側の視点) rc の値 is success の評価 is failed の評価 実際の挙動(人間の直感)
正常終了(エラーなし=真) 0 真(True) 偽(False) 成功タスクだけが動く(検証3-1)
異常終了(エラーあり=偽) 1(以上) 偽(False) 真(True) 失敗タスクだけが動く(検証3-2)

このように、シェル側が返す「数値」を、Ansibleが扱いやすい「状態(成功か、失敗か)」へと適合させることが、Ansibleテスト構文を使用する最大のメリットです。


💡【参考】なぜテスト構文だと「ひっくり返る」のか?(一次情報)
公式ドキュメントには「状態をチェックする構文」としか書かれていませんが、Ansibleの内部ソースコード(GitHub: core.py)を確認すると、この判定を制御しているPythonコードが記述されています。

# is failed の判定を行う関数定義
def failed(result):
    """ Test if task result yields failed """
    if not isinstance(result, MutableMapping):
        raise errors.AnsibleFilterError("The 'failed' test expects a dictionary")

    # 結果オブジェクト(JSON)の中から 'failed' フラグの値を取得して真偽値に変換
    return bool(result.get('failed'))


# is success の判定を行う本物の関数定義
def success(result):
    """ Test if task result yields success """
    # 失敗(failed)の判定結果をそっくりそのまま「反転(not)」させて返す
    return not bool(failed(result))

(ソースコード引用元: https://github.com/ansible/ansible/blob/10186793b31b9d2cfc6b3edbdc2e0a1fb6606101/lib/ansible/plugins/test/core.py)
※ 上記URLは執筆時点(2026年5月)のコミットに固定しています。最新コードはdevelブランチでご確認ください。

■ソースコードが示す「ひっくり返し」の正体

このロジックを見れば、Ansibleが内部でどのように「2つの世界のルールのズレ」を吸収しているのか分かります。

Ansibleは、Jinja2に対して数値(rc の 0 や 1)をそのまま渡して評価させているわけではありません。内部のPythonプログラムがデータを処理し、「状態を表すフラグ(failed)」を取り出してから反転させるという処理をかけています。

  • is failed の裏側
    検証3-2の異常終了データには "failed": true が記録されていました。プログラムは result.get('failed') でこの True を取り出し、そのまま True(失敗状態である=真)としてJinja2に返します。これにより、人間の直感に合わせた「失敗=真」という判定になります。
  • is success の裏側
    検証3-1の正常終了データには "failed": false が記録されています。そのため、内部で呼び出された failed(result) は False を返します。
    ここで return not bool(failed(result)) の処理が効いてきます。False(失敗していない)を not で反転させることで、最終的な結果を True(成功状態である=真)としてJinja2に返します。

Jinja2の基本ルールである「数値の 0 は偽(False)とみなす」という評価を、success 関数内の not が打ち消しているため、rc=0 のときに直感通りタスクが実行されます。


🔍 裏付けとなる公式ドキュメント


↑ 目次に戻る


8. 本質:データ型(Type)の不一致による判定ミスとregister変数の構造

shell などのモジュールで取得した値を when 句で比較する際、最も注意すべきは「データ型(Type)」です。

一見すると数値の比較に見えるコードですが、データ型の特性によって「意図しない判定」を招く、あるいは「実行エラーによる強制停止」を引き起こすことがあります。まずは、型変換を行わずに比較した際の実際の挙動(失敗パターン)から確認していきます。

🛠️ 挙動検証:データ型の不一致による評価のズレ

検証の前提条件(ターゲットノード側の状態)

本検証では、リモートノード(ターゲットノード)側に、現在のバージョンが 9 であることを示す管理ファイルがすでに配置されている環境を前提とします。

# ターゲットノード側で実行済みの状態
echo -n "9" > /etc/version_code

この状態で cat を実行し、結果を register: version_out に格納した際、Ansibleの内部では以下のようなJSON構造のデータが生成されています。

{
  "changed": true,
  "cmd": "cat /etc/version_code",
  "rc": 0,
  "stdout": "9",
  "stderr": "",
  "stdout_lines": ["9"]
}

このように、register 変数の stdout から取り出した値は、元のファイルの中身が数字であっても、文字列型の "9" として保持されます。このデータ型を踏まえた上で、型変換を行わずに比較処理を実行した際の挙動を確認します。


【検証4-1】失敗パターン(型変換なしで "9" を評価)

型変換を行わずに、ファイルから読み取った値と数値の 10 を直接比較させます。設計上の意図としては「9は10未満なのでスキップされる」必要がありますが、データ型の不一致によってプレイブックの実行にどのような影響が出るかを確認します。

ファイル名: check_type_mismatch_failed.yml

---
- name: 4-1. 型変換なしでファイルの値と数値の 10 を比較する(挙動確認)
  hosts: test_servers
  gather_facts: false
  tasks:
    - name: ファイルからバージョン(9)を読み込む
      ansible.builtin.shell: cat /etc/version_code
      register: version_out

    - name: "[判定チェック] when: version_out.stdout >= 10"
      debug:
        msg: "文字コード評価により、9 >= 10 が【真 (True)】と判定されました。"
      when: version_out.stdout >= 10

【コントローラーノード側】

実行コマンド

ansible-playbook -i inventory.ini check_type_mismatch_failed.yml

▼ 実行結果

PLAY [4-1. 型変換なしでファイルの値と数値の 10 を比較する(挙動確認)] **********************************************************************************************************************

TASK [ファイルからバージョン(9)を読み込む] ************************************************************************************************************************************************
changed: [192.168.1.21]

TASK [[判定チェック] when: version_out.stdout >= 10] ****************************************************************************************************************************************
fatal: [192.168.1.21]: FAILED! => {"msg": "The conditional check 'version_out.stdout >= 10' failed. The error was: Unexpected templating type error occurred on ({% if version_out.stdout >= 10 %} True {% else %} False {% endif %}): '>=' not supported between instances of 'AnsibleUnsafeText' and 'int'. '>=' not supported between instances of 'AnsibleUnsafeText' and 'int'\n\nThe error appears to be in '/opt/ansible/workspace/check_type_mismatch_failed.yml': line 10, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n    - name: \"[判定チェック] when: version_out.stdout >= 10\"\n      ^ here\nThis one looks easy to fix. It seems that there is a value started\nwith a quote, and the YAML parser is expecting to see the line ended\nwith the same kind of quote. For instance:\n\n    when: \"ok\" in result.stdout\n\nCould be written as:\n\n   when: '\"ok\" in result.stdout'\n\nOr equivalently:\n\n   when: \"'ok' in result.stdout\"\n"}

PLAY RECAP **********************************************************************************************************************************************************************************
192.168.1.21               : ok=1    changed=1    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

結論(検証4-1)

実行ログの通り、タスクがスキップされるどころか、FAILED! としてプレイブック全体の処理が強制停止しました。

エラーメッセージにある '>=' not supported between instances of 'AnsibleUnsafeText' and 'int' という一文が、その原因を示しています。version_out.stdout から取り出された値(AnsibleUnsafeText = 文字列型)と、比較対象である 10(int = 数値型)という異なるデータ型の間で不等号演算(>=)を行おうとしたため、Jinja2の型チェックによってエラーになったことを示しています。

このような型不一致は、誤判定だけでなくプレイブックの強制停止を引き起こします。


【検証4-2】成功パターン(| int フィルターを適用)

全く同じターゲットノードのデータに対し、今度は | int フィルターを通して数値に変換してから比較させ、挙動が修正されるかを検証します。

ファイル名: check_type_mismatch_success.yml

---
- name: 4-2. | int フィルターを適用して数値として比較する(正常化)
  hosts: test_servers
  gather_facts: false

  tasks:
    - name: ファイルからバージョン(9)を読み込む
      ansible.builtin.shell: cat /etc/version_code
      register: version_out

    - name: "[判定チェック] when: version_out.stdout | int >= 10"
      debug:
        msg: "数値として正しく比較されているため、このメッセージは見えません(スキップされます)。"
      when: version_out.stdout | int >= 10

【コントローラーノード側】

実行コマンド

ansible-playbook -i inventory.ini check_type_mismatch_success.yml

▼ 実行結果

(ansible) [ansible@localhost workspace]$ ansible-playbook -i inventory.ini check_type_mismatch_success.yml

PLAY [4-2. | int フィルターを適用して数値として比較する(正常化)] **************************************************************************************************************************

TASK [ファイルからバージョン(9)を読み込む] ************************************************************************************************************************************************
changed: [192.168.1.21]

TASK [[判定チェック] when: version_out.stdout | int >= 10] **********************************************************************************************************************************
skipping: [192.168.1.21]

PLAY RECAP **********************************************************************************************************************************************************************************
192.168.1.21               : ok=1    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

結論(検証4-2)

| int フィルターを適用したことで、実行ログは強制停止することなく、意図通り skipping: [192.168.1.21](skipped=1)へと変化しました。

内部で "9" という文字列が数値の 9 に変換されたことで、Jinja2の評価が 「9 >= 10 → 偽(False)」 という数値比較のルールに従い、タスクがスキップされました。

シェル経由で取得した動的な値(バージョン番号、ファイルサイズ、プロセス数など)を when 句で数値比較する場合は、「register 変数の stdout から取り出した値は文字列である」というデータ型の特性を前提とし、| int などの型変換フィルターを適用することが堅牢なコード設計の基本です。


↑ 目次に戻る


9. まとめ

  • when 句は、直前の終了ステータスではなく、register 変数に格納されたJSONデータの各要素(キー)を評価している。

  • シェルの「戻り値 0(正常終了)」とJinja2の「数値 0(偽/False)」における評価ルールの相違を解消するため、条件判定には is success などのAnsibleテストを活用すべきである。

  • register 変数の stdout から取り出した値は常に文字列(AnsibleUnsafeText)として扱われる。そのため、数値との不等号比較を行う際は、型不一致による実行エラー(強制停止)や意図しない判定を防ぐために、int フィルターによる明示的な型変換(キャスト)が必要である。


↑ 目次に戻る


10. 次回予告

第4回では、条件分岐における「データ構造」や「データ型の特性」に起因する、判定の相違や実行エラーの原因について解説しました。

論理制御の次に確実な管理が求められるのが、「変数」のスコープと制御です。Ansibleには vars、host_vars、group_vars、register など、多種多様な変数の定義場所が存在します。

  • 「どのスコープで定義した変数が優先して適用されるのか」

  • 「なぜ特定のタイミングで変数が上書きされない挙動を示すのか」

次回は、設計時に考慮すべき「変数の優先順位(Precedence)」と「評価タイミング」の具体的な仕様について解説します。

次回:
【Ansible編】第5回:変数の優先順位と評価タイミングにおける制御仕様


↑ 目次に戻る


📑 連載の移動
前の記事:【Ansible編】第3回 | 次の記事:【Ansible編】第5回


📑 【Ansible編】全体のまとめはこちら
「Ansibleが理解できない理由はLinuxにあった【Ansible編】まとめ」


🗺️ 初めての方・シリーズの全体像を知りたい方はこちら

本記事は、OSの仕組みからAnsible設計までを繋ぐ連載シリーズの一部です。
「どこから読み始めればいいか」あるいは、「OS/Shell/Ansible編の関係性」 について把握されたい場合は、以下の統合ガイドで整理しています。

→ Ansibleが理解できない理由はLinuxにあった|統合ガイド


📚11. 連載一覧:Ansibleが理解できない理由はLinuxにあった【Ansible編】

回数とタイトル 内容(概要)
【Ansible編】第0回:なぜShellの知識がないとAnsibleは壊れるのか AnsibleはShellのラッパーとして動作している。Shell編で学んだ構造(Ansible → SSH → Shell → Linux)を再確認し、「なぜmoduleを使うべきなのか」の前提を整理する。
【Ansible編】第1回:なぜshellモジュールは危険なのか shellモジュールはShellの仕様(分割・展開・環境)に依存するため壊れやすい。commandとの違いを理解し、「どこまでShellを許容すべきか」を判断できるようになる。
【Ansible編】第2回:なぜmoduleを使うと安全になるのか file / copy / lineinfile などのmoduleは状態管理を前提としている。「結果」ではなく「状態」を扱うことで、Shell依存を排除し安全な構成を実現する。
【Ansible編】第3回:なぜ毎回changedになるのか shellモジュールは状態を判定できないため、常にchangedになる。changed_whenを使った制御と冪等性(idempotency)の設計を理解する。
【Ansible編】第4回:なぜ条件分岐が壊れるのか(Ansible編) when条件が意図通りに動かない原因は、rc / stdout / stderrの扱いにある。Shell編で学んだ終了ステータスと組み合わせ、正しい条件分岐を設計する。
【Ansible編】第5回:なぜ変数が意図通りに扱えないのか vars / host_vars / group_vars のスコープや、registerの扱いを誤ると値が壊れる。Ansible内での変数管理と評価タイミングを整理する。
【Ansible編】第6回:なぜタスクの順序で壊れるのか Ansibleのタスクはそれぞれ独立したプロセスとして実行される。chdir / environment の制御と、「前の結果に依存する設計」の危険性を理解する。
【Ansible編】第7回:なぜエラー制御が破綻するのか ignore_errors や failed_when の使い方を誤るとエラーが隠蔽または暴発する。Shellのrcと連動させた正しいエラー制御を設計する。
【Ansible編】第8回:なぜ非同期・待機で失敗するのか サービス起動や外部依存処理はタイミング問題を引き起こす。async / poll / wait_for / retries を使い、安定した実行制御を実現する。
【Ansible編】第9回:なぜPlaybookが読めないのか role構成やタスク分割が不適切だと可読性が崩壊する。実務で保守できるPlaybook設計と構造化の原則を理解する。
【Ansible編】第10回:なぜAnsibleを正しく設計できるようになるのか これまでの知識を統合し、Shell編と接続することで「壊れない設計」ができるようになる。Ansibleを実務で使いこなすための設計思想を完成させる。

↑ 目次に戻る


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?