※当サイトの記事には、広告・プロモーションが含まれます。

Ansibleでエラーがあった場合に後続のblockのタスクをスキップしたい

gigazine.net

アメリカにおける研究資金の大幅な削減や研究環境の混乱を受けて、科学者のうち約75%が退職して国外への移住を検討していると、学術誌のNatureが報告しています。特に、大学院生やポスドクなど初期キャリアの研究者の間でその傾向が強く、カナダやヨーロッパへの移住希望が多く挙がっているとのことです。

アメリカの科学者の約75%が退職と国外への移住を検討している - GIGAZINE

Natureの取材に応えたある博士課程の学生は、アメリカのトップ大学で植物ゲノミクスと農業の研究を行っていましたが、トランプ政権による国際開発庁(USAID)の資金打ち切りにより、研究費と生活費を失ったとのこと。学生の指導教員が短期的な緊急資金を手配してくれたもののそれだけでは不十分で、記事作成時点ではティーチングアシスタント職に応募して何とかプログラムを継続しようとしている状態ですが、非常に競争率が高いため、見通しはよくないそうです。

アメリカの科学者の約75%が退職と国外への移住を検討している - GIGAZINE

また、別の若手研究者は、「PI(研究代表者)たちはこの騒動を乗り切れると考えていますが、私たちのような経歴の浅い研究者にはそんな余裕がありません。今後の経歴にとって極めて重要な時期なのに、数週間で完全に混乱させられました」と語っています。

アメリカの科学者の約75%が退職と国外への移住を検討している - GIGAZINE

⇧ 結局のところ、お金が全てなのよね...

Natureは、科学者たちが深い危機感を抱き「本当はアメリカを離れたくないが、代わりの選択肢がない」という思いに至っている現状を、今回のアンケート結果が浮き彫りにしていることを強調しています。

アメリカの科学者の約75%が退職と国外への移住を検討している - GIGAZINE

⇧ これで人手不足とか言っているとしたら、とんだ茶番ですな...

『待遇は悪くしますが、成果は上げてください』では、モチベーションも上がるわけないですからね...

「産学連携」が著しく後退しそうな感はありますな...

2025年3月30日(日)追記:↓ ここから

何やら、

gigazine.net

2025年1月20日に第47代アメリカ合衆国大統領ドナルド・トランプ氏が就任して以来、アメリカ政府は保健福祉省公衆衛生局の下にある医学研究機関であるアメリカ国立衛生研究所(NIH)の科学者に対して、外部への情報発信停止や雇用の凍結、出張の中止といった命令を出しています。The Washington Postの分析によると、トランプ政権発足以降、NIH助成金は明らかに減少しており、その額は30億ドル(約4500億円)以上だそうです。

トランプ政権に移行してアメリカ国立衛生研究所の助成金が前年比で4500億円以上も減っていることが明らかに - GIGAZINE

⇧ とあるのだが、

保健福祉省公衆衛生局の下にある医学研究機関であるアメリカ国立衛生研究所(NIH)の科学者に対して、外部への情報発信停止や雇用の凍結、出張の中止といった命令

を実施する根拠が知りたくはある。

つまり、上記の命令をすることによる「メリット」が何なのかを知りたい。

何も考えていないのだとしたら、狂気の沙汰としか思えないのだが...

2025年3月30日(日)追記:↑ ここまで

Ansibleでエラーがあった場合に後続のblockのタスクをスキップしたい

前に、

ts0818.hatenablog.com

⇧ 上記の記事の時に、「Ansible」では「切り戻し」の面倒は見てくれないことが分かり、

  1. エラーがあった場合は処理を中止する
  2. エラーがあった場合でも処理を継続する

のどちらのアプローチでいくのが良いのか悩んだものの、複数のホストを対象にするケースを想定するのであれば、「2. エラーがあった場合でも処理を継続する」のアプローチでいくのが良いのかなと。

とは言え、エラーがあった場合に後続のタスクは実行せずにスキップしたいと思うのが人情。

で、公式のドキュメントを見た限り、様々なアプローチがあるのですが、

docs.ansible.com

⇧「Controlling errors in blocks」にある「block」レベルで対応するのが良さそうではある。

なのだが、

docs.ansible.com

⇧「rescue」の説明によると、

『Ansible only runs rescue blocks after a task returns a ‘failed’ state. Bad task definitions and unreachable hosts will not trigger the rescue block.』

とあるので、

  1. Bad task definitions
  2. unreachable hosts

の2つのケースについては、「rescue」をすり抜けてしまうようだ。

とは言え、「1. Bad task definitions」については、開発している時の動作確認で懸念を潰せると思うので、「2. unreachable hosts」だけが問題になってくる気はするが、

  1. ネットワーク障害
  2. 対象のホストの機器がダウンしている

といった要因で発生する懸念と思われることから、「Controller node」側で対応できることは無い気がするので、「rescue」でカバーできるといって良いかと。

で、残念ながら、公式のドキュメントを見た限りでは、複数の「block」のいずれかでエラーがあった場合に後続の処理をスキップするサンプルが紹介されていないので、推測になってしまうのですが、スキップさせたい場合は、以下のような感じになるのかな?

tasks:
  - name: 
    block:
      - name: Test 01
        block:
          - name: Execute Test 01
            ansible.builtin.command: python3 test01.py

        rescue:
          - name: Failed Test 01
            ansible.builtin.debug:
              msg: "Failed Test 01!"

          - name: Failed task info
            debug:
              var: ansible_failed_task

          - name: Failed task execute result info
            debug:
              var: ansible_failed_result

          - name: Catch Error
            ansible.builtin.set_fact:
              is_error: true
        
        always:
          - name: Finish Test 01
            ansible.builtin.debug:
              msg: "Test 01 finish"

      - name: Test 02
        block:
          - name: Execute Test 02
            ansible.builtin.command: python3 test02.py
        when is_error is not define or not is_error

        rescue:
          - name: Failed Test 02
            ansible.builtin.debug:
              msg: "Failed Test 02!"

          - name: Failed task info
            debug:
              var: ansible_failed_task

          - name: Failed task execute result info
            debug:
              var: ansible_failed_result

          - name: Catch Error
            ansible.builtin.set_fact:
              is_error: true
        
        always:
          - name: Finish Test 02
            ansible.builtin.debug:
              msg: "Test 02 finish"

      - name: Test 03
        block:
          - name: Execute Test 03
            ansible.builtin.command: python3 test03.py
        when is_error is not define or not is_error

        rescue:
          - name: Failed Test 03
            ansible.builtin.debug:
              msg: "Failed Test 03!"

          - name: Failed task info
            debug:
              var: ansible_failed_task

          - name: Failed task execute result info
            debug:
              var: ansible_failed_result

          - name: Catch Error
            ansible.builtin.set_fact:
              is_error: true
        
        always:
          - name: Finish Test 03
            ansible.builtin.debug:
              msg: "Test 03 finish"

    rescue:
      - name: Failed Test
        ansible.builtin.debug:
          msg: "Failed Test!"
    when is_error is not define or not is_error
    
    always:
      - name: Finish Test
        ansible.builtin.debug:
          msg: "Test finish"

⇧ 上記のような感じで、スキップすることはできそう。

テストの作りが同じであれば、「金太郎飴」の如く構造を複製する感じにできるので「Template Method パターン 」っぽく、テストの内容だけ変えれば良く、ループ処理に置き換えれると思われる。

ただし、

  1. タスクのファイル名などは連番とする必要がある
  2. テストの起点となるタスクのあるファイル名は、roles/test/tasks/main.ymlでansible.builtin.include_tasksするファイル名に合わせる

など、制約を設けておく必要はある。

ディレクトリ構成

.
├── roles
│   └── test
│       ├── tasks
│       │   ├── main.yml
│       │   ├── test_01.yml
│       │   ├── test_02.yml
│       │   └── test_03.yml
│       └── vars
│           └── main.yml
└── playbook.yml

■./roles/test/tasks/main.yml

---
- name: Execute tests in sequence
  block:
    - name: Include test tasks dynamically using index_var
      ansible.builtin.include_tasks: "test_{{ loop.index }}.yml"
      loop: "{{ range(1, lookup('ansible.builtin.fileglob', playbook_dir + '/roles/test/tasks/test_*.yml') | length + 1) | list }}"
      loop_control:
        index_var: index_var
      when: is_error is not defined or not is_error
      rescue:
        - name: Failed Test
          ansible.builtin.debug:
            msg: "A test failed!"
        - name: Catch Error
          ansible.builtin.set_fact:
            is_error: true
      always:
        - name: Finish test all
          ansible.builtin.debug:
            msg: "Test all finished"
    

■./roles/test/tasks/test_01.yml

---
- name: Execute Test {{ index_var }}
  block:
    # TODO:テスト毎に異なる処理
    - name: Execute Test {{ index_var }}
      ansible.builtin.command: "echo test{{ index_var }}"

  rescue:
    - name: Failed Test {{ index_var }}
      ansible.builtin.debug:
        msg: "Failed Test {{ index_var }}!"

    - name: Failed task info
      debug:
        var: ansible_failed_task

    - name: Failed task execute result info
      debug:
        var: ansible_failed_result

    - name: Catch Error
      ansible.builtin.set_fact:
        is_error: true

  always:
    - name: Finish Test {{ index_var }}
      ansible.builtin.debug:
        msg: "Test {{ index_var }} finished"   

■./roles/test/tasks/test_02.yml

---
- name: Execute Test {{ index_var }}
  block:
    # TODO:テスト毎に異なる処理
    - name: Execute Test {{ index_var }}
      ansible.builtin.command: "echo test{{ index_var }}"

  rescue:
    - name: Failed Test {{ index_var }}
      ansible.builtin.debug:
        msg: "Failed Test {{ index_var }}!"

    - name: Failed task info
      debug:
        var: ansible_failed_task

    - name: Failed task execute result info
      debug:
        var: ansible_failed_result

    - name: Catch Error
      ansible.builtin.set_fact:
        is_error: true

  always:
    - name: Finish Test {{ index_var }}
      ansible.builtin.debug:
        msg: "Test {{ index_var }} finished"   

■./roles/test/tasks/test_03.yml

---
- name: Execute Test {{ index_var }}
  block:
    # TODO:テスト毎に異なる処理
    - name: Execute Test {{ index_var }}
      ansible.builtin.command: "echo test{{ index_var }}"

  rescue:
    - name: Failed Test {{ index_var }}
      ansible.builtin.debug:
        msg: "Failed Test {{ index_var }}!"

    - name: Failed task info
      debug:
        var: ansible_failed_task

    - name: Failed task execute result info
      debug:
        var: ansible_failed_result

    - name: Catch Error
      ansible.builtin.set_fact:
        is_error: true

  always:
    - name: Finish Test {{ index_var }}
      ansible.builtin.debug:
        msg: "Test {{ index_var }} finished"   

■./playbook.yml

---
- name: Run tests in sequence
  hosts: localhost
  gather_facts: false

  roles:
    - role: test
      tasks_from: main    

⇧ 動作確認していないので動くかは不明...

そもそも、ローカル(Control node)を対象にしている時点で実用的なサンプルとは言えないのだが...

「Ansible」における「Control node」はというと、

docs.ansible.com

⇧ 上図に該当するものですね。

要するに、「Playbook」が実行される環境ですかね。

本来であれば、

medium.com

⇧ 上図のように、「Managed node」を対象とするのが一般的と思われる。

「Ansible」は、

  1. ローカル(Control node)
  2. リモート(Managed node)

のどちらを対象にしているかが分り辛いのよね...

例えば、「ansible.builtin.copy」なる「モジュール」なんかは、

docs.ansible.com

⇧「remote_src」という設定でコピー元が変わるという…

remote_src: src: ※1 dest: ※2
false ※3 ローカル リモート
true リモート リモート

※1 コピー元

※2 コピー先

※3 remote_src: 自体を設定しなかった場合は、remote_src: false の挙動になるということ。

話が脱線しましたが、エラーがあった場合に後続のタスクをスキップしたい、に話を戻します。

ただ、フラグを利用するのは好ましい方法ではないので、本当であれば避けたいところなのだが...

ちなみに、

dekitakotono.blogspot.com

入れ子になっている「block」と「rescue」の挙動については、おそらく、内側の「rescue」でエラーが捕捉された場合、外側の「rescue」にエラーが伝番されない気がするので、外側の「rescue」の処理に遷移させたいのであれば、

  1. 内側のエラーがあったことが分かる情報をwhenで判定する
  2. 内側のエラーで更にエラーを投げる

などの対応が必要になってきそうではある。

By the way、

zaki-hmkc.hatenablog.com

⇧ 上記サイト様によりますと、

  1. ansible.builtin.meta: end_play
  2. ansible.builtin.meta: end_host

を利用すれば、次のホストにスキップできるらしい。

と思ったら、

docs.ansible.com

end_play (added in Ansible 2.2) causes the play to end without failing the host(s). Note that this affects all hosts.

end_host (added in Ansible 2.8) is a per-host variation of end_play. Causes the play to end for the current host without failing it.

https://docs.ansible.com/ansible-core/2.15_ja/collections/ansible/builtin/meta_module.html#ansible-collections-ansible-builtin-meta-module

⇧ とあり、「end_play」だと、複数のホストがあった場合に全てのホストに影響するとあるので、「インベントリ」で対象のホストが複数ある場合に次のホストの処理にスキップできずに死ぬことになると思われる...

「rescue」を利用しない方法としては、

tekunabe.hatenablog.jp

⇧ 上記サイト様が紹介して下さっていました。

「Ansible」が用意している変数「inventory_hostname」を利用して条件を判定しているようです。

docs.ansible.com

対象のホストが増えてきた場合に、辛いことになりそうではある。

とりあえず、

- name: Check Docker container running
  ansible.builtin.command:
    cmd: docker-compose ps
  register: result

- name: Exit current inventory host play and skip next inventory host play 
  ansible.builtin.meta: end_host
  when
    - (result.rc != 0)
    - (inventory_hostname in ansible_play_hosts_all)

⇧ のような条件判定ができるのであれば、次のホストにスキップできるのかしら?

ちなみに、

tekunabe.hatenablog.jp

Ansible には、やや特殊な ansible.builtin.meta というモジュールがあります。

このモジュールに、ロールの処理を正常のまま止めるキーワード end_role が、ansible-core 2.18.0 で追加されました。

[Ansible] ロールの処理を正常のまま止める meta モジュールのキーワード「end_role」 - てくなべ (tekunabe)

⇧「ansible-core 2.18.0」を利用している場合は、

  1. ansible.builtin.meta: end_role

なるものが利用できるようになっているらしい。

docs.ansible.com

end_role (added in Ansible 2.18) causes the currently executing role to end without failing the host(s). Effectively all tasks from within a role after end_role is executed are ignored. Since handlers live in a global, play scope, all handlers added via the role are unaffected and are still executed if notified. It is an error to call end_role from outside of a role or from a handler. Note that end_role does not have an effect to the parent roles or roles that depend (via dependencies in meta/main.yml) on a role executing end_role.

https://docs.ansible.com/ansible/latest/collections/ansible/builtin/meta_module.html#ansible-collections-ansible-builtin-meta-module

⇧ う~む、「roles/*/tasks/main.yml」の「アスタリスク」が複数あるケースでないと意味は無さそうね...

「end_host」についても、現在実行対象のホストの場合という条件が指定できるか分からないのよね...

そうなってくると、フラグを利用する形でいくしかなくなってしまうわけなのだが...

毎回思うのだが、公式のドキュメントのサンプルが実用的でないのよね...

「Ansible」の思想が分からないので何とも言えないのだが、どうも噛み合わない...

毎度モヤモヤ感が半端ない…

今回はこのへんで。