今回は Ansible の Playbook で使える notify と handlers の使い方について調べてみる。 このふたつはペアになっていて、タスクに notify を書いておくと、そのタスクで状態に変更があった場合にそれと対応する handlers が実行される仕組みになっている。
環境には CentOS7 を使った。
$ cat /etc/redhat-release CentOS Linux release 7.1.1503 (Core) $ uname -r 3.10.0-229.11.1.el7.x86_64
Ansible をインストールする
まずは EPEL から Ansible をインストールしておく。
$ sudo yum -y install epel-release $ sudo yum -y install ansible
基本的な使い方を試してみる
Apache httpd のインストールを題材にして基本的な使い方を確認しておく。
まずは notify と handlers を使った Playbook を用意する。 今回は Ansible をローカルホストで実行することにしたので connection: local の指定がある。 この Playbook では Apache httpd がインストールされた場合に、それを notify で通知する。 対応する handlers では Apache httpd のサービスを (再) 起動している。
$ cat << EOF > site.yml --- - hosts: all connection: local sudo: True tasks: - name: install httpd yum: name=httpd notify: restart httpd handlers: - name: restart httpd service: name=httpd state=restarted EOF
ローカルホストで実行するので Inventory は localhost のみ書いておく。
$ cat << EOF > hosts localhost EOF
Playbook を元に Ansible を実行する。
$ ansible-playbook -i hosts site.yml PLAY [all] ******************************************************************** GATHERING FACTS *************************************************************** ok: [localhost] TASK: [install httpd] ********************************************************* changed: [localhost] NOTIFIED: [restart httpd] ***************************************************** changed: [localhost] PLAY RECAP ******************************************************************** localhost : ok=3 changed=2 unreachable=0 failed=0
初回なので Apache httpd がインストールされて、その通知を元にサービスの起動が走っている。
試しにもう一度実行してみよう。
$ ansible-playbook -i hosts site.yml PLAY [all] ******************************************************************** GATHERING FACTS *************************************************************** ok: [localhost] TASK: [install httpd] ********************************************************* ok: [localhost] PLAY RECAP ******************************************************************** localhost : ok=2 changed=0 unreachable=0 failed=0
今度は Apache httpd が既にインストール済みなので、状態に変更はないため通知は行われずサービスの (再) 起動も行われることがない。
複数回 notify しても通知先が同じであれば実行は一回にまとめられる
何度同じ宛先に通知しても実行は一回にまとめられるという点が notify / handlers の特徴になっている。 例えばアプリケーションのコンフィグを複数回にわたって変更したとしても、そのアプリケーションの再起動は最後に一回行えば良いといった感じ。
上記の挙動を確認するために、次のような Playbook を用意した。 単なる debug モジュールを複数回実行しているだけだが、changed_when: True にすることで毎回同じ通知が行われるようになっている。
$ cat << EOF > site.yml --- - hosts: all connection: local tasks: - name: notify1 debug: msg="notify1" notify: handler changed_when: True - name: notify2 debug: msg="notify2" notify: handler changed_when: True handlers: - name: handler debug: msg="handler" EOF
Inventory は前回と変わらない
$ cat << EOF > hosts localhost EOF
実行してみる。
$ ansible-playbook -i hosts site.yml PLAY [all] ******************************************************************** GATHERING FACTS *************************************************************** ok: [localhost] TASK: [notify1] *************************************************************** changed: [localhost] => { "changed": true, "msg": "notify1" } TASK: [notify2] *************************************************************** changed: [localhost] => { "changed": true, "msg": "notify2" } NOTIFIED: [handler] *********************************************************** ok: [localhost] => { "msg": "handler" } PLAY RECAP ******************************************************************** localhost : ok=4 changed=2 unreachable=0 failed=0
複数回同じ宛先に通知が行われているが、ハンドラの実行は一回にまとめられている。
通知の条件を自分で決める
shell モジュールや command モジュールを使う場合、通知するか否かの条件を自分で決めたい場合がある。 そうした場合には changed_when を使うのがよさげ。
次の Playbook では command モジュールの実行結果を register で変数に格納した上で、その内容を changed_when でチェックしている。 この changed_when の条件が True になる場合に通知が行われる。 どうやら changed_when には Python のコードがそのまま記述できるようだ。
$ cat << EOF > site.yml --- - hosts: all connection: local tasks: - name: notify command: echo "Hello, World!" register: result changed_when: result.stdout.find('World') notify: handler handlers: - name: handler debug: msg="'World' is contained" EOF
Inventory は先ほどと同じ。
$ cat << EOF > hosts localhost EOF
実行してみる。
$ ansible-playbook -i hosts site.yml PLAY [all] ******************************************************************** GATHERING FACTS *************************************************************** ok: [localhost] TASK: [notify] **************************************************************** changed: [localhost] NOTIFIED: [handler] *********************************************************** ok: [localhost] => { "msg": "'World' is contained" } PLAY RECAP ******************************************************************** localhost : ok=3 changed=1 unreachable=0 failed=0
command の実行結果を元に状態に変更があったと判断されて通知が行われた。 ちなみに上記ではコマンドの標準出力を元に判断しているが、返り値を使う場合には Jinja2 のフィルタを使って "result | success" としたり、あるいはより直接的に "result.rc == 0" といったように書くことができる。
単なる条件分岐であれば when を使った方が良い
本題からはちょっと脱線するけど、単なる条件分岐であれば notify/handlers よりも when を使った方がよさげ。
次の Playbook では main processing の実行結果を元に post processing を実行するかを when で判断している。 main processing の結果を register で変数 result に格納した上で、その標準出力を when で条件分岐させる。
$ cat << EOF > site.yml --- - hosts: all connection: local tasks: - name: main processing command: echo "Hello, World!" register: result - name: post processing debug: msg="'World' is contained" when: result.stdout.find('World') EOF
Inventory については先ほどと同じ。
$ cat << EOF > hosts localhost EOF
実行してみる。
$ ansible-playbook -i hosts site.yml PLAY [all] ******************************************************************** GATHERING FACTS *************************************************************** ok: [localhost] TASK: [main processing] ******************************************************* changed: [localhost] TASK: [post processing] ******************************************************* ok: [localhost] => { "msg": "'World' is contained" } PLAY RECAP ******************************************************************** localhost : ok=3 changed=1 unreachable=0 failed=0
when の条件が True になるので post processing が実行される。
Roles で notify / handlers を使う
Ansible では設定ファイルの再利用性を高めるために Roles を使うのがベストプラクティスになっているようだ。 Roles は Playbook を細かく分割するためのルールのように考えればよさそう。 今度は Roles を使った場合に notify / handlers を実行する方法について書く。
その前に、今度は題材を Apache httpd のインストールに戻すので一旦アンインストールしておく。
$ sudo yum -y remove httpd
Playbook は次の通り。 これで web というロールを実行することになる。
$ cat << EOF > site.yml --- - name: deploy web hosts: webservers connection: local roles: - web EOF
Inventory は先ほどとちょっと異なり localhost を webservers というグループに参加させてみた。
$ cat hosts [webservers] localhost
Roles を使う場合は roles というディレクトリ以下に所定のファイルを用意していく。 先ほど Playbook で指定した web/tasks と web/handlers という名前で更にディレクトリを掘る。 この中に分割した Playbook を置いていく。
$ mkdir -p roles/web/tasks $ mkdir -p roles/web/handlers
tasks には文字通り先ほど Playbook の中にあった tasks を書く。 main.yml という名前のファイルを置いておけば自動的に読み込んでくれる。
$ cat << EOF > roles/web/tasks/main.yml --- - name: install httpd yum: name=httpd notify: restart httpd sudo: True EOF
handlers も同様に Playbook の中にあった handlers の内容を記述する。 ファイル名についても同じで main.yml を自動的に読む。
$ cat << EOF > roles/web/handlers/main.yml --- - name: restart httpd service: name=httpd state=restarted sudo: True EOF
この状態で実行してみる。
$ ansible-playbook -i hosts site.yml PLAY [deploy web] ************************************************************* GATHERING FACTS *************************************************************** ok: [localhost] TASK: [web | install httpd] *************************************************** changed: [localhost] NOTIFIED: [web | restart httpd] *********************************************** changed: [localhost] PLAY RECAP ******************************************************************** localhost : ok=3 changed=2 unreachable=0 failed=0
ばっちり動いた。
まとめ
今回は notify と handlers を実際に使ってみて、その挙動について調べてみた。 何らかの変更があった場合に必ず必要な処理というのは結構あるので、そうした内容をスマートに扱う上で有用そうだ。
スマートPythonプログラミング: Pythonのより良い書き方を学ぶ
- 作者: もみじあめ
- 発売日: 2016/03/12
- メディア: Kindle版
- この商品を含むブログを見る