前回はPlaybookの分割に挑戦しました。今回は特定のタスクが完了した場合に後続処理を定義する方法についてまとめます。
notify と handler
後続処理を実行する
各タスクに記述することができるnotify
を利用すると、特定のタスクが終了したタイミングで、handlers
内に定義された後続のタスクを自動的に実行してくれます。notify
は「通知する」という意味の英単語です。
- hosts: servers remote_user: ansibleman become: yes tasks: #-- Apacheをインストール --# - name: Install Apache yum: name=httpd state=present notify: - start apache #------------------------------------ # ハンドラー #------------------------------------ handlers: - name: start apache service: name: httpd state: started
ご覧の通りname
に半角スペースが入っていても大丈夫です。気持ち悪い場合はアンダーバー(_)をつけたりキャメルケース(startApache)といった感じで書いても良いかもしれません。
またnotify
が配列になっているところからも分かる通り、複数のハンドラーを同時に指定することも可能です。
- hosts: servers remote_user: ansibleman become: yes tasks: #-- 必要なミドルウェをインストール --# - name: Install Middleware service: name={{ item }} state=present with_items: - httpd - memcached notify: - start apache - start memcached #------------------------------------ # ハンドラー #------------------------------------ handlers: #-- Apacheを起動 --# - name: start apache service: name=httpd state=started #-- Memcachedを起動 --# - name: start memcached service: name=memcached state=started
注意点
👉 同じハンドラーの呼び出しは1回にまとめられる
特に意味のない例ですが、以下のPlaybookを実行するとansible.logには3行の日付が記録されていることを期待しますが、実際には1つにまとめられます。
- hosts: servers remote_user: ansibleman become: yes tasks: - name: ls 1st shell: ls notify: write date - name: ls 2nd shell: ls notify: write date - name: ls 3rd shell: ls notify: write date handlers: - name: write date shell: date >> ansible.log
ご覧の通りです。1行だけしか記録されていません。
$ cat ansible.log 2019年 10月 30日 水曜日 20:39:36 JST
なぜこのようなことになるかと言うと、notify/handlerはまとめて最後に実行されるからのようです。
これはあえての仕様で、重複して処理を行う方を防ぐ目的でこのような動作になっているようです。知らないとハマりそうですよねw
👉 ハンドラーはタスクがchangedのときだけ実行される
これも知らないとハマりそうなところです。以下のPlaybookを2回目に実行するとどういう挙動になるか考えてみます。
- hosts: servers remote_user: ansibleman become: yes tasks: #-- slコマンドをインストール --# - name: Install sl yum: name=sl state=present notify: - write date handlers: - name: write date shell: date >> ansible.log
slコマンドはすでにインストールされており、yumモジュールのstate
にはpresent
が指定されているためこのタスクは通常何も実行しません。ここで注目すべきはnotify
です。結論から言うとここでもhandlerは何も実行されません。notifyは何らかの処理が実行された場合にのみ、つまり実行結果がchanged
になった場合にだけ通知されるのです。
さらに言うと、Playbook実行中にエラーが発生した場合、それまで正常終了していたタスクのハンドラー分もあわせてすべて実行されません。notify/handlerは最後にまとめて実行されるからです。
つまり以下のようなPlaybookを書いた場合、write date1
は正常終了しているので実行されるかに見えますが実際には実行されません。何という罠。
- hosts: servers remote_user: ansibleman become: yes tasks: #-- [正常終了] slコマンドをインストール --# - name: Install sl yum: name=sl state=present notify: - write date1 #-- [エラー] 存在しないパッケージをインストール --# - name: Install XXXXX yum: name=XXXXX state=present notify: - write date2 handlers: - name: write date1 shell: date >> ansible1.log - name: write date2 shell: date >> ansible2.log
ちなみにslコマンドはTerminal上でSLが走るコマンドですw lsコマンドを打ち間違えた際に同僚をビビらせるための物ですね← いつもサーバに忍ばせています。
動画は2倍速にしてありますが実際にはもっとゆっくり通過するため、急いでるときだと殺意の波動に目覚めそうになりますのでご注意くださいw
ハンドラーをエラー発生時でも実行する
実行時にエラーが発生するとすべてのハンドラーが実行されないのは納得がいかない、成功したタスクの分だけでも実行したいという場合には、公式ドキュメントに記載されている手段を利用します。
Handlers and Failure You can change this behavior with the --force-handlers command-line option, or by including force_handlers: True in a play, or force_handlers = True in ansible.cfg. When handlers are forced, they will run when notified even if a task fails on that host. (Note that certain errors could still prevent the handler from running, such as a host becoming unreachable.)
適当に日本語訳すると、以下の通り。
- コマンドラインで実行する際に
--force-handlers
オプションをつける - Playbookに
force_handlers: True
を記述 - ansible.cfgに
force_handlers = True
を記述
先ほどのPlaybookにforce_handlers: True
を追加しました。
- hosts: servers remote_user: ansibleman become: yes force_handlers: True tasks: #-- [正常終了] slコマンドをインストール --# - name: Install sl yum: name=sl state=present notify: - write date1 #-- [エラー] 存在しないパッケージをインストール --# - name: Install XXXXX yum: name=XXXXX state=present notify: - write date2 handlers: - name: write date1 shell: date >> ansible1.log - name: write date2 shell: date >> ansible2.log
実行すると最初にハンドラーだけ無事に実行されていますね。
$ ansible-playbook playbook.yml PLAY [servers] ******************************************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************************************ ok: [neecbox.net] TASK [Install sl] ************************************************************************************************************************* changed: [example.com] TASK [Install XXXXX] ************************************************************************************************************************** fatal: [neecbox.net]: FAILED! => {"changed": false, "cmd": "dnf install -y python2-dnf", "msg": "[Errno 2] そのようなファイルやディレクトリはありません", "rc": 2} RUNNING HANDLER [write date1] ***************************************************************************************************************** changed: [example.com] PLAY RECAP ************************************************************************************************************************************ example.com : ok=3 changed=2 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
管理対象のサーバを確認するとちゃんと最初のログファイルだけ生成されているのがわかります。
$ ls ansible1.log $ cat ansible1.log 2019年 11月 6日 水曜日 21:38:38 JST
続き
参考ページ
インプレス (2019-10-18)
売り上げランキング: 14,785