Edited at

Ansibleでhandlerタスクを部分的にシリアル化する


はじめに

ansible-playbookにおいて、「設定変更が走ったときに handler で再起動を掛ける」というコードはよく書くのではないかと思います。でも、冗長構成になっているサーバー群へのPlaybookだとしたら、 同時に再起動されては困ります よね。ロードバランス配下のWebサーバー用のPlaybookとか、あるあるなんじゃないかと思います。

一般にそういうときは serial: 1 などと付けて同時に複数台で更新作業(再起動含む)が走らないようにするものです。

一般的な実装の例:


test.yml

- hosts: all

become: yes
gather_facts: yes
serial: 1
roles:
- risky_role

さて、この実装だと、Playbookが長かったり、対象ホスト数が多いときに、 非常に時間がかかる という問題が生じてしまいます。

タスクやblockに対して serial が指定できたら良いのですが、2019/02/15現在、まだそのような実装はありません。

本記事は どうにかhandlerのタスクをパラレル実行できないか と思って色々試したTipsの(自分用)メモです。


解決方法

ネットを探すとタスク毎にシリアル化する実装例は多々出てきます。でもそれをhandlerで適用したいわけです。

・・・というか、この記事をまとめ始めてから再度検索してみたら、Qiita内に近い実装例の記事があることに気付いた(笑)ので、そちらへのリンクもメモしておきます。

これらの記事にあるテクニックを応用して、handlersにもシリアル実行を適用します。


実装例(snippet)

handlerの実装例は以下。

複数のタスクからなるhandlerなので、 import_tasks をします。


roles/risky_role/handlers/main.yml

- name: serial handler

import_tasks: handlers/serial_task.yml

実際の処理は import_tasks しているファイル serial_task.yml に記載します。

以下の例では、タスク名 serial task がシリアルに1台ずつ実行したいタスクです。


roles/risky_role/handlers/serial_task.yml

- 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 = ABC と遷移する

  • したがって、 delegate_to: "{{item}}"delegate_to: Adelegate_to: Bdelegate_to: C という処理になる

以上により、シリアルに1台ずつ処理が実行されるということになります。

ただし、 ansible_play_hosts はhandlerが呼び出されたかどうかを考慮しないリストのため、このままでは handlerを呼び出していないホストでも処理が実行されてしまいます。

それを防ぐため、 serial_required 変数をsetして利用します。


実行対象ホストの制限

serial_task.yml 冒頭でset_factしていますが、 serial_required: yes となるホストは、handlerを呼び出したホストのみです。これをwhen条件に利用します。

ただし、 run_oncedelegate_to のせいで直接ホストの変数を参照できないため、以下のような回りくどい when 条件を書くことで値を参照します。

    when: 

- hostvars[item]['serial_required'] is defined
- hostvars[item]['serial_required']


その他

後で実行例をお見せしますが、 run_oncedelegate_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