うさラボ

お勉強と備忘録

所属するグループ内に失敗したホストがあれば処理スキップしたい

Ansible Advent Calendar 2023 9日目の記事です。

qiita.com

やりたいこと

拠点に所属する機器に対して作業をするとき同拠点内の機器でエラーがあったとき ほかの機器の後続スキップさせたいので考えてみました。

機器1と機器8で何かしらの処理が失敗するとします。
この場合、拠点1の機器2と機器3,拠点3の機器7と機器9も後続処理をスキップさせたいといったものです。

拠点1
  機器1 × ← 失敗
  機器2   ← 失敗してないけどスキップ
  機器3   ← 失敗してないけどスキップ
拠点2
  機器3
  機器5
  機器6
拠点3
  機器7   ← 失敗してないけどスキップ
  機器8 × ← 失敗
  機器9   ← 失敗してないけどスキップ

手順

inventory

インベントリは以下のように作成しました
グループA,B,Cにそれぞれ3ホストづつ所属させています。

グループ変数に定義したgroup_idは、所属するグループ(拠点)を識別させるものです。 グループ名と一致させています(Playbookで動的に取得させようともしましたが、ホストが複数のグループに所属していたりするのを考えるのが面倒だったのでこの形にしちゃいました)

---
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

block/resucueとgroup_byを使って処理が失敗したホストをfailedグループに含め、
マジック変数のgroupsとグループ変数に定義したgroup_idを使って自分が所属するグループのホスト一覧を取得し、failedグループに含まれているホストが含まれてない場合のみ処理させます。
intersectフィルタはリストの共通する要素を抽出するもので、failedグループと共通する要素があればグループ内に失敗していたホストがあるといった条件にしました。

最後にfailedグループで処理することで失敗したホストを対象のなんやかんやできます resuceが動くとタスクの失敗ステータスが「取り消され」、成功したかのように続行していきます。

---
- name: グループ内の1hostが失敗していたら、ほかの処理も止めたい
  hosts: targets
  gather_facts: false
  connection: local

  tasks:
    - name: block
      block:
        - name: なにかしらの失敗
          fail:
            msg: "Fail"
          when: "inventory_hostname == 'host1' or inventory_hostname == 'host8'" 
      rescue:
        - name: 失敗したホストfailedグループに追加
          group_by:
            key: failed

    - name: block
      when: ( groups[group_id] | intersect(groups['failed']) | length ) == 0
      block:
        - name: 失敗無いグループのホストのみ処理
          debug:
            msg: "Errorないよ"
      rescue:
        - name: 失敗したホストfailedグループに追加
          group_by:
            key: failed

- name: 失敗したホストの所属するグループを表示
  hosts: failed
  gather_facts: false
  connection: local

  tasks:
    - name: dev
      debug:
        msg: "{{ group_id }}"
    - name: fail
      fail:
        msg: "Error"

実行結果

実行結果はこちらです。 rescueに引っかかるhost1とhost8は、後続の「失敗無いグループのホストのみ処理」でも実行対象になっていることから失敗が取り消されていることも確認できます

想定通り、host1の所属するgroupAとhost8が所属するgroupCの別ホストは処理をスキップさせ グループ内に失敗のないgroupBのみ処理させることができました

$ ansible-playbook group_in_error.yml -i hosts.yaml 

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: [host1]
changed: [host8]

TASK [失敗無いグループのホストのみ処理] *****************************************************************************
skipping: [host1]
skipping: [host2]
skipping: [host3]
ok: [host4] => {
    "msg": "Errorないよ"
}
ok: [host5] => {
    "msg": "Errorないよ"
}
ok: [host6] => {
    "msg": "Errorないよ"
}
skipping: [host7]
skipping: [host8]
skipping: [host9]

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=2    changed=1    unreachable=0    failed=1    skipped=1    rescued=1    ignored=0   
host2                      : ok=0    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
host3                      : ok=0    changed=0    unreachable=0    failed=0    skipped=2    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=2    rescued=0    ignored=0   
host8                      : ok=2    changed=1    unreachable=0    failed=1    skipped=1    rescued=1    ignored=0   
host9                      : ok=0    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0

わーい

別解も知りたい