この記事は Ansible Advent Calendar 2023 の 11日目の記事です。
はじめに
今回の Advent Calendar 8日目の記事で @usagi_automate さんが、「所属するグループ内に失敗したホストがあれば処理スキップしたい」という記事(以下、元記事)を書かれていました。
興味深く拝見して、ちょっと別解を考えたくなりました。
微妙に要件が異なりますが、スキップではなく該当ホストの Play をとめるパターンの別解を考えてみました。
- 検証環境
- ansible-core 2.15.7
インベントリー
元記事と全く同じものを利用します。GroupA、GroupB、GroupC にそれぞれ 3台ずつ所属しています。
--- all: children: targets: children: groupA: hosts: host1: ansible_connection: local host2: ansible_connection: local host3: ansible_connection: local vars: group_id: groupA groupB: hosts: host4: ansible_connection: local host5: ansible_connection: local host6: ansible_connection: local vars: group_id: groupB groupC: hosts: host7: ansible_connection: local host8: ansible_connection: local host9: ansible_connection: local vars: group_id: groupC
Playbook
実装上の違い
failed
グループに所属させる方法の違い
元記事では rescue
内(失敗したホストでも処理を続行)で group_by
モジュールを使っていました。これにより「自分が失敗したら自分を failed
グループに所属させる」を実現していました。
一方、本記事では block
、rescue
は利用しません。これらを使わずに似たようなことをできるかなぁ、と考えたのが発端のためです。rescue
を利用しない場合、失敗したホストはその時点で処理が止まります(デフォルトでは)。そのため、自分が失敗したら自分で何かするということができません。本記事では代わりに、続行するほかのホストのタスクとして ansible.builtin.add_host
モジュールで、failed
に失敗ホストを所属させています(イマイチかもです)。
失敗ホストの求め方の違い
元記事では、前述どおり「自分が失敗したら自分を failed
グループに所属させる」というアプローチのため、(failed
グループに内に)自然と失敗ホストのリストができる仕組みです。
一方、本記事では同じアプローチができず、他の方法で失敗ホストのリスト求める必要があります。これを求めるには以下の2つの特別な変数を利用します。
変数名 | 説明 | 今回の Playbook の場合の値 |
---|---|---|
ansible_play_hosts_all |
Play内のすべてのホストが入る | ["host1", "host2", "host3", "host4, "host5", "host6", "host7", "host8", "host9"] |
ansible_play_hosts |
Play内で現在正常なホストのリストが入る。エラーのホストは除外される | ["host2", "host3", "host4, "host5", "host6", "host7", "host9"] |
ansible_play_hosts_all
にあって、ansible_play_hosts
にないホストを求めることにより、失敗ホストのリストを求めます。
今回の場合 "{{ ansible_play_hosts_all | difference(ansible_play_hosts) }}"
で、失敗ホストのリスト ["host1", "host8"]
になります。
要件の違いによる実装の違い
元記事では、タイトルの「所属するグループ内に失敗したホストがあれば処理スキップしたい」どおり、スキップするのが要件です。
一方、本記事では、スキップではなくそのホストの Play を止めます。そのために、meta: end_host
を利用します。Play を止めるホストの条件の指定として、 when
の条件に「自分が所属するグループ内のホストに、失敗したホストが所属していること」を指定します。
処理の継続のさせ方の違い
元記事では、失敗したホストを rescue
で扱うことで、
タスクの失敗ステータスが「取り消され」、成功したかのように続行していきます。
を実現し、2Play目の hosts: failed
の処理を実行していました。
一方、本記事では rescue
を使用していないため、これもやはり別の方法が必要です。そのために、meta: clear_host_errors
を利用します。この指定をしないと 2Play目の hosts: failed
内に正常ホストがない扱いになって何も起こりません。
Playbook 内容
Playbook は以下の通りです。
--- - name: グループ内の1hostが失敗していたら、ほかの処理も止めたい hosts: targets gather_facts: false connection: local tasks: - name: なにかしらの失敗 fail: msg: "Fail" when: "inventory_hostname == 'host1' or inventory_hostname == 'host8'" # (*1) - name: 失敗ホストをfailedグループに追加 ansible.builtin.add_host: name: "{{ item }}" groups: - failed # 失敗したホストのリスト(今回は ["host1", "host8"] )のループ loop: "{{ ansible_play_hosts_all | difference(ansible_play_hosts) }}" - name: 自分が所属するグループ内のホストに、失敗したホストが所属していたら Play 停止 meta: end_host when: - (groups[group_id] | intersect(failed_hosts) | length) >= 1 vars: # 失敗したホストのリスト(今回は ["host1", "host8"] ) failed_hosts: "{{ ansible_play_hosts_all | difference(ansible_play_hosts) }}" - name: 失敗無いグループのホストのみ処理 debug: msg: "Errorないよ" # (*1) - name: エラー状態のクリア ansible.builtin.meta: clear_host_errors - name: 失敗したホストの所属するグループを表示 hosts: failed gather_facts: false connection: local tasks: - name: dev debug: msg: "{{ group_id }}" - name: fail fail: msg: "Error"
なお、2Play目の「失敗したホストの所属するグループを表示」が不要であれば、1Play目で (*1)
で示した2つのタスクは不要です。
実行結果
タスク「失敗無いグループのホストのみ処理」では、どのホストの失敗しなかったグループ GroupB に所属するホスト host4
、host5
、host6
の分だけが処理されています。
その他のホストは skipped
にもなりません。その他のホストは meta: end_host
によって Play が止まっていたり、そもそも意図的にエラー(host1
、host8
)を起こしているホストであるためです。ここが元記事と要件的に異なる点です。
実行結果は以下の通りです。
PLAY [グループ内の1hostが失敗していたら、ほかの処理も止めたい] **************************************************** TASK [なにかしらの失敗] ******************************************************************************************* fatal: [host1]: FAILED! => {"changed": false, "msg": "Fail"} skipping: [host2] skipping: [host3] skipping: [host4] skipping: [host5] skipping: [host6] skipping: [host7] fatal: [host8]: FAILED! => {"changed": false, "msg": "Fail"} skipping: [host9] TASK [失敗ホストをfailedグループに追加] *************************************************************************** changed: [host2] => (item=host1) changed: [host2] => (item=host8) TASK [自分が所属するグループ内のホストに、失敗したホストが所属していたら Play 停止] ******************************* TASK [自分が所属するグループ内のホストに、失敗したホストが所属していたら Play 停止] ******************************* TASK [自分が所属するグループ内のホストに、失敗したホストが所属していたら Play 停止] ******************************* skipping: [host4] TASK [自分が所属するグループ内のホストに、失敗したホストが所属していたら Play 停止] ******************************* skipping: [host5] TASK [自分が所属するグループ内のホストに、失敗したホストが所属していたら Play 停止] ******************************* skipping: [host6] TASK [自分が所属するグループ内のホストに、失敗したホストが所属していたら Play 停止] ******************************* TASK [自分が所属するグループ内のホストに、失敗したホストが所属していたら Play 停止] ******************************* TASK [失敗無いグループのホストのみ処理] *************************************************************************** ok: [host4] => { "msg": "Errorないよ" } ok: [host5] => { "msg": "Errorないよ" } ok: [host6] => { "msg": "Errorないよ" } TASK [エラー状態のクリア] ***************************************************************************************** PLAY [失敗したホストの所属するグループを表示] ********************************************************************* TASK [dev] ******************************************************************************************************** ok: [host1] => { "msg": "groupA" } ok: [host8] => { "msg": "groupC" } TASK [fail] ******************************************************************************************************* fatal: [host1]: FAILED! => {"changed": false, "msg": "Error"} fatal: [host8]: FAILED! => {"changed": false, "msg": "Error"} PLAY RECAP ******************************************************************************************************** host1 : ok=1 changed=0 unreachable=0 failed=2 skipped=0 rescued=0 ignored=0 host2 : ok=1 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 host3 : ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 host4 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 host5 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 host6 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 host7 : ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 host8 : ok=1 changed=0 unreachable=0 failed=2 skipped=0 rescued=0 ignored=0 host9 : ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
meta
のタスクのログの出方が少し特徴的ですかね。
おわりに
block
、rescue
を避けるアプローチを考えたらこうなりました。結果的に要件が元記事とは異なるので単純な比較はできませんが。
@usagi_automate さん、ネタ提供ありがとうございました!