前回は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
このブログを応援する
お寄せいただいたお気持ちは全額サーバ代や次の記事を執筆するための原資として活用させていただいております。この記事が参考になった場合などぜひご検討ください。