はじめに
Ansible には、タスクのステータスが changed
のときだけ実行したい処理を定義するために、ハンドラーという機能があります。
ハンドラーには、通常のタスクと同様に name
を指定できます。ハンドラーで指定した name
をこの記事ではハンドラー名と呼びます。
タスクとハンドラーを関連付ける方法は複数あります。その一つは、通常のタスクの notify
ディレクティブで、呼びたいハンドラー名を指定する方法です。
(実際には一通りタスクの処理が終わってからハンドラーが呼ばれます。ロールを含めた処理順はこちら)
そのため、ハンドラー名は重要な識別子の役割を持ちます。もし、同じハンドラー名のハンドラーが複数あると思わぬ結果を招く場合あります。ハンドラー名はユニークにしておくべきと、ドキュメントに記載があります。
Each handler should have a globally unique name.
同じくドキュメントに記載されている、グロバルスコープで扱われるという点も重要です。
Handlers from roles are not just contained in their roles but rather inserted into the global scope with all other handlers from a play.
この記事では、同じ名前のハンドラーがある場合の挙動を、通常の場合と比較して検証します。
- 検証環境
- ansible-core 2.15.2
1. 通常の場合(ユニークなハンドラー名の場合)
まずは、比較のためにユニークなハンドラー名で構成された通常の場合の挙動です。
フォルダ・ファイル構成
それぞれハンドラーを持つ 2つのロール role1
、role2
からなる構成とします。
. ├── playbook.yml └── roles ├── role1 │ ├── handlers │ │ └── main.yml │ └── tasks │ └── main.yml └── role2 ├── handlers │ └── main.yml └── tasks └── main.yml
role1
ロール
role1/tasks/main.yml
:
--- - name: Task with changed ansible.builtin.command: # changed cmd: "echo Hello!" notify: - Test Handler in role1
role1/handlers/main.yml
:
--- - name: Test Handler in role1 # ロール固有の名前 ansible.builtin.debug: msg: "handler {{ ansible_role_name }}"
role2
ロール
(role1
とすべて同じです)
role2/tasks/main.yml
:
--- - name: Task with changed ansible.builtin.command: # changed cmd: "echo Hello!" notify: - Test Handler in role2
role2/handlers/main.yml
:
--- - name: Test Handler in role2 # ロール固有の名前 ansible.builtin.debug: msg: "handler {{ ansible_role_name }}"
Playbook
2つのロールを呼び出すPlaybookです。
playbook.yml
:
--- - name: Test Play hosts: localhost connection: local gather_facts: false tasks: - name: Import role1 ansible.builtin.import_role: name: role1 - name: Import role2 ansible.builtin.import_role: name: role2
実行結果
以下のような実行順序になります。
role1
のタスクrole2
のタスクrole1
のハンドラーrole2
のハンドラー
コマンド実行ログは以下のとおりです。
$ ansible-playbook -i localhost, playbook.yml PLAY [Test Play] **************************************************************************************************** TASK [role1 : Task with changed] ************************************************************************************ changed: [localhost] TASK [role2 : Test Handler in role1] ******************************************************************************** changed: [localhost] RUNNING HANDLER [role1 : Test Handler in role1] ********************************************************************* ok: [localhost] => { "msg": "handler role1" } RUNNING HANDLER [role2 : Test Handler in role2] ********************************************************************* ok: [localhost] => { "msg": "handler role2" } PLAY RECAP ********************************************************************************************************** localhost : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
2. 同じ名前のハンドラーがある場合
次に、同じ名前のハンドラーが複数に含まれている場合です。
role1
ロール
role1/tasks/main.yml
:
--- - name: Task with changed ansible.builtin.command: # changed cmd: "echo Hello!" notify: - Test Handler
role1/handlers/main.yml
:
--- - name: Test Handler # 他のロールにあるハンドラーと同じ名前 ansible.builtin.debug: msg: "handler {{ ansible_role_name }}"
role2
ロール
(role1
とすべて同じです)
role2/tasks/main.yml
:
--- - name: Task with changed ansible.builtin.command: # changed cmd: "echo Hello!" notify: - Test Handler
role2/handlers/main.yml
:
--- - name: Test Handler # 他のロールにあるハンドラーと同じ名前 ansible.builtin.debug: msg: "handler {{ ansible_role_name }}"
Playbook
2つのロールを呼び出すPlaybookです。
playbook.yml
:
--- - name: Test Play hosts: localhost connection: local gather_facts: false tasks: - name: Import role1 ansible.builtin.import_role: name: role1 - name: Import role2 ansible.builtin.import_role: name: role2
実行結果
以下のような実行順序になります。
role1
のタスクrole2
のタスクrole2
のハンドラー
role1
のハンドラーが呼ばれていないところがポイントです。
コマンド実行ログは以下のとおりです。
$ ansible-playbook -i localhost, playbook.yml PLAY [Test Play] **************************************************************************************************** TASK [role1 : Task with changed] ************************************************************************************ changed: [localhost] TASK [role2 : Task with changed] ************************************************************************************ changed: [localhost] RUNNING HANDLER [role2 : Test Handler] ****************************************************************************** ok: [localhost] => { "msg": "handler role2" } PLAY RECAP ********************************************************************************************************** localhost : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
この、role1
のハンドラーが呼ばれなかったところが、ドキュメントの言葉でいう
shadowing all of the previous handlers with the same name.
かなと思います。
なお、ansible.builtin.import_role
でインポートするロールを逆( role2
、role1
の順)にしたら、role1
のハンドラーのみが呼ばれました。
おまけ
ハンドラーはグローバルで有効という性質上、以下のようなこともできます。
特定のロールのハンドラーを指定
notify
で ロール名 : ハンドラー名
を指定すると、ロールを明示的に指定できます。ドキュメントにも方法が載っています(最近知りました)。
たとえば、role1/tasks/main.yml
で、以下のように role2
内の ハンドラーを Test Handler
呼ぶとします。
--- - name: Task with changed ansible.builtin.command: cmd: "echo Hello!" notify: - "role2 : Test Handler"
これで、role2/handlers/main.yml
の以下のハンドラーが呼ばれます。
--- - name: Test Handler ansible.builtin.debug: msg: "handler {{ ansible_role_name }}"
ただし、role2
も Playbook 側で読み込んでおく必要があります。
ハンドラーのみのロールでハンドラーを共通化
ハンドラーのみのロールを用意して、他のロールからハンドラーを呼ぶようにして、ハンドラーを共通化するような方法です。
. ├── playbook.yml └── roles ├── role1 │ └── tasks │ └── main.yml └── role_handler_only └── handlers └── main.yml
role_handler_only/handlers/main.yml
:
--- - name: Test Handler # role_handler_only ロールはこれのみ ansible.builtin.debug: msg: "handler {{ ansible_role_name }}"
role1/tasks/main.yml
:
--- - name: Task with changed # role1 ロールはこれのみ ansible.builtin.command: cmd: "echo Hello!" notify: - Test Handler
playbook.yml
:
--- - name: Test Play hosts: localhost connection: local gather_facts: false tasks: - name: Import role1 ansible.builtin.import_role: name: role1 - name: Import role_handler_only ansible.builtin.import_role: name: role_handler_only # ハンドラーのみのロール
実行ログ:
ansible-playbook -i localhost, playbook.yml PLAY [Test Play] ************************************************************************************************************* TASK [role1 : Task with changed] ********************************************************************************************* changed: [localhost] RUNNING HANDLER [role_handler_only : Test Handler] *************************************************************************** ok: [localhost] => { "msg": "handler role_handler_only" } PLAY RECAP ******************************************************************************************************************* localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ハンドラーのみのロールの実装のヒントは、以下の記事から得ました。ありがとうございます。
おわりに
同じ名前のハンドラーがあることで、起こる現象を検証しました。
ざっくり、ハンドラー名は Play 内でグローバルなスコープで処理される、というイメージでとらえておけばよいかなと思いました。