はじめに
ansible-playbookにおいて、「設定変更が走ったときに handler
で再起動を掛ける」というコードはよく書くのではないかと思います。でも、冗長構成になっているサーバー群へのPlaybookだとしたら、 同時に再起動されては困ります よね。ロードバランス配下のWebサーバー用のPlaybookとか、あるあるなんじゃないかと思います。
一般にそういうときは serial: 1
などと付けて同時に複数台で更新作業(再起動含む)が走らないようにするものです。
一般的な実装の例:
- hosts: all
become: yes
gather_facts: yes
serial: 1
roles:
- risky_role
さて、この実装だと、Playbookが長かったり、対象ホスト数が多いときに、 非常に時間がかかる という問題が生じてしまいます。
タスクやblockに対して serial
が指定できたら良いのですが、2019/02/15現在、まだそのような実装はありません。
本記事は どうにかhandlerのタスクをパラレル実行できないか と思って色々試したTipsの(自分用)メモです。
解決方法
ネットを探すとタスク毎にシリアル化する実装例は多々出てきます。でもそれをhandlerで適用したいわけです。
・・・というか、この記事をまとめ始めてから再度検索してみたら、Qiita内に近い実装例の記事があることに気付いた(笑)ので、そちらへのリンクもメモしておきます。
- 参考にしたもの: https://shasawas.wordpress.com/2017/01/30/how-to-run-a-particular-task-serially-in-the-middle-of-the-play-in-ansible/
- 似た実装: https://qiita.com/yunano/items/5deb78f0fa008d98227b
これらの記事にあるテクニックを応用して、handlersにもシリアル実行を適用します。
実装例(snippet)
handlerの実装例は以下。
複数のタスクからなるhandlerなので、 import_tasks
をします。
- name: serial handler
import_tasks: handlers/serial_task.yml
実際の処理は import_tasks
しているファイル serial_task.yml
に記載します。
以下の例では、タスク名 serial task
がシリアルに1台ずつ実行したいタスクです。
- name: set serial_required
set_fact:
serial_required: yes
- block:
- name: serial task
shell: |
sleep 3
touch /tmp/testfile
when:
- hostvars[item]['serial_required'] is defined
- hostvars[item]['serial_required']
delegate_to: "{{item}}"
with_items: '{{ ansible_play_hosts }}'
run_once: yes
changed_when: no
- name: reset serial_required
set_fact:
serial_required: no
簡単に説明
引用している記事でも丁寧な解説が書いてありますが、ジャンプするのも面倒なのでメモしておきます。
シリアル実行の基礎部分
タスク名 serial task
では、以下の3要素でシリアルに実行することを実現します。
delegate_to: "{{item}}"
with_items: '{{ ansible_play_hosts }}'
run_once: yes
-
run_once: yes
: 実行対象ホストの内、1台だけで実行する -
with_items: '{{ ansible_play_hosts }}'
: 実行対象ホストの一覧でループする -
delegate_to: "{{item}}"
: ループのそれぞれで、処理を委譲する
これらの組み合わせにより、 A, B, C
3台のホストに対してPlaybookを実行しているとしたら、以下のようにタスク名 serial task
は実行されることになります。
- with_itemsのループでは
item
=A
→B
→C
と遷移する - したがって、
delegate_to: "{{item}}"
はdelegate_to: A
→delegate_to: B
→delegate_to: C
という処理になる
以上により、シリアルに1台ずつ処理が実行されるということになります。
ただし、 ansible_play_hosts
はhandlerが呼び出されたかどうかを考慮しないリストのため、このままでは handlerを呼び出していないホストでも処理が実行されてしまいます。
それを防ぐため、 serial_required
変数をsetして利用します。
実行対象ホストの制限
serial_task.yml
冒頭でset_factしていますが、 serial_required: yes
となるホストは、handlerを呼び出したホストのみです。これをwhen条件に利用します。
ただし、 run_once
や delegate_to
のせいで直接ホストの変数を参照できないため、以下のような回りくどい when
条件を書くことで値を参照します。
when:
- hostvars[item]['serial_required'] is defined
- hostvars[item]['serial_required']
その他
後で実行例をお見せしますが、 run_once
や delegate_to
のせいでPLAY RECAPにおいて実行ホスト間で変更数に差異が出てしまいます。
changed
の値に差分が出るのが気持ち悪いため、 changed_when: no
オプションを付けています。(その結果 ok
に差分が出るのですが)
また、 serial_required
変数が再利用されると弊害があるため、 serial_required: no
に値をリセットしています。
実行例
test task
で一部のホストだけ変更が走った例です。
結果だけではわかりにくいですが、 RUNNING HANDLER [test : serial task]
は順次処理が実行されています。実行ホストは test-01
になっていますが、delegateされて矢印の先に実際の実行ホストが書かれています。なお、ホスト skip-0X
ではhandlerが呼ばれていないため、この処理もskipされています。
PLAY [all] ******************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************
ok: [test-01]
ok: [test-02]
ok: [skip-01]
ok: [skip-02]
TASK [test : test task] ***************************************************************************************************
skipping: [skip-01]
skipping: [skip-02]
changed: [test-01]
changed: [test-02]
RUNNING HANDLER [test : set serial_required] *******************************************************************************
ok: [test-01]
ok: [test-02]
RUNNING HANDLER [test : serial task] ********************************************************************************************
ok: [test-01 -> test-01] => (item=test-01)
ok: [test-01 -> test-02] => (item=test-02)
skipping: [test-01] => (item=skip-01)
skipping: [test-01] => (item=skip-02)
RUNNING HANDLER [test : reset serial_required] *****************************************************************************
ok: [test-01]
ok: [test-02]
PLAY RECAP ******************************************************************************************************************
skip-01 : ok=1 changed=0 unreachable=0 failed=0
skip-02 : ok=1 changed=0 unreachable=0 failed=0
test-01 : ok=5 changed=1 unreachable=0 failed=0
test-02 : ok=4 changed=1 unreachable=0 failed=0
PLAY RECAP
を見ると、run_onceで実行ホストに選ばれた test-01
のみ、 ok
の値が1つ多くなっています。これはもう、そういうものだと思って諦めましたw
おわりに
handlerの一部タスクを無理やりシリアル実行にする実装例を紹介しました。
特殊な変数を多用しているので、このメモなしには諳で書ける気がしません・・・w