Ansibleのループ処理はデフォルトでは逐次処理で動作します。
例えばループの回数が5回で、1回の処理に10秒ずつかかる場合は合計50秒はかかる計算になります。
Ansibleではループによって複数回実行される処理がお互いに干渉せずに並列に実行可能なのであれば、async
とpoll
の指定で並列に処理することができます。
1回10秒(x5)の処理を並行で同時に実行することで10秒で完了する計算になります。
デフォルト(逐次処理)の動作
まずは普通の逐次処理されるループ処理のplaybookです。
--- - hosts: localhost gather_facts: false tasks: - name: clear log file: path: async.log state: absent - name: loop sample shell: echo "$(date +%Y.%m.%d_%H.%M.%S); {{ item }} begin" >> async.log; sleep "{{ item }}"; echo "$(date +%Y.%m.%d_%H.%M.%S); {{ item }} end" >> async.log loop: - 2 - 3 - 4
ごちゃごちゃ書いてるけど、shell
でやってるのは以下の3つのコマンドを実行している。
- 現在日時とsleepする時間と
begin
をファイル書き込み - 指定秒数sleep
- 現在日時とsleepした時間と
end
をファイル書き込み
loop
を使って[2, 3, 4]
のリストを渡すことで、「2秒スリープ」「3秒スリープ」「4秒スリープ」が順番に処理されることを出力されるファイルで確認できます。
出力されるファイルはこんな感じ。
2021.04.06_20.06.50; 2 begin 2021.04.06_20.06.52; 2 end 2021.04.06_20.06.53; 3 begin 2021.04.06_20.06.56; 3 end 2021.04.06_20.06.56; 4 begin 2021.04.06_20.07.00; 4 end
shell
で実行される処理が並行して実行されていないことが確認できました。
今回は、このスリープ処理を同時に実行させてみます。
並列処理
Ansibleの並列処理に必要な非同期の処理は基本全部ここにのってる。はず。
ちなみにループの場合の応用もこのページに載ってます。
asyncとpollによる非同期処理
今回使うのはasync
とpoll
。どちらも整数(秒)を引数に取ります。
async
async
を単体で使用すると、「指定秒数以内にtaskが完了しない場合はエラー」となります。
- name: async sample shell: echo "start" >> async-poll.log; sleep 10; echo "end" >> async-poll.log async: 4
これを実行すると、sleep 10
の最中にasync: 4
によるタイムアウトが発動し、以下のようにエラーとなります。
fatal: [localhost]: FAILED! => changed=false msg: async task did not complete within the requested time - 4s
async + poll
poll
の指定が無い場合は、デフォルト値は15
のため、15秒経ってエラーとして動作します。
なので、async
に指定した値より大きい値はあまり意味がない(と思う)ので、おそらくasync
の秒数の約数にしておくとベターと思う。
- name: async sample shell: echo "start" >> async-poll.log; sleep 10; echo "end" >> async-poll.log async: 4 poll: 1
このようにpoll: 1
にしておくと、実行しているモジュールが完了するかasync
で指定した4秒が経過するかを、poll
に指定した1秒毎にチェックする動作になります。
poll:0
poll: 0
は特殊で、async
で指定しているtaskを非同期で処理しつつ、その完了を待たずに次のtaskへ処理が進むという動作になります。
- name: async sample shell: echo "start" >> async-poll.log; sleep 10; echo "end" >> async-poll.log async: 11 poll: 0 # shellで実行している処理がタイムアウトしないように`async`の値は11秒に変更
上記のtask定義でansible-playbook
を実行すると、shell
の完了を待たずに非同期で処理が継続され、taskが完了します。
また、他にtaskがなければansible-playbook
の実行は終了しシェルに処理が戻りますが、バックグラウンドで非同期処理用のプロセスが動作し続けます。
ansible-playbook
は終了しても処理が実行してる最中にps auxfw
するとこんな感じ。
zaki 110284 0.0 0.1 215616 11040 ? S 21:08 0:00 /home/zaki/src/ansible-sample/venv/a2.10/bin/python3 /home/zaki/.a zaki 110285 0.0 0.1 215616 11412 ? S 21:08 0:00 \_ /home/zaki/src/ansible-sample/venv/a2.10/bin/python3 /home/zak zaki 110286 5.0 0.1 207000 14188 ? S 21:08 0:00 \_ /home/zaki/src/ansible-sample/venv/a2.10/bin/python3 /home zaki 110298 0.0 0.0 113288 1420 ? S 21:08 0:00 \_ /bin/sh -c echo "start" >> async-poll.log; sleep 10; e zaki 110299 0.0 0.0 108056 356 ? S 21:08 0:00 \_ sleep 10
ループ処理に応用
ここまでの async
+ poll:0
設定を踏まえて、ループ処理とコンボすることで、ループで実行される各処理を逐次処理でなく並列に同時に実行することができます。
- name: async loop shell: echo "{{ item }} begin" >> async.log; sleep "{{ item }}"; echo "{{ item }} end" >> async.log loop: - 2 - 3 - 4 async: 5 poll: 0
冒頭の逐次処理を行っていたtaskにasync
+poll
を追加。
これを実行した結果出力されるasync.log
ファイルは以下の通り。
2021.04.06_21.12.22; 2 begin 2021.04.06_21.12.22; 3 begin 2021.04.06_21.12.22; 4 begin 2021.04.06_21.12.24; 2 end 2021.04.06_21.12.25; 3 end 2021.04.06_21.12.26; 4 end
begin
が同時に書き込まれ、end
は各時間のスリープが終わったあとに書き込まれることを確認でき、ループ内の各処理が並列に処理されています。
非同期で実行した処理を待つ
async
とpoll:0
の指定で並列処理できるようになったけど、実際のplaybookでは「並列処理したtaskの結果がどうだったか」というのを以降の処理を実行する前に結果を確認したうえで次の処理を行うことが多いと思います。
Ansibleのasync
を使った非同期処理はその結果を保持した上で、ansible.builtin.async_status
モジュールを使ってwaitできます。
# ループの並列処理 - name: async loop shell: echo "$(date +%Y.%m.%d_%H.%M.%S); {{ item }} begin" >> async.log; sleep "{{ item }}"; echo "$(date +%Y.%m.%d_%H.%M.%S); {{ item }} end" >> async.log loop: - 2 - 3 - 4 async: 1 poll: 0 register: result_async # 並列処理の完了を待つ - name: wait ansible.builtin.async_status: jid: "{{ item.ansible_job_id }}" loop: "{{ result_async.results }}" register: async_poll_results until: async_poll_results.finished retries: 10 delay: 1
並列ループ処理はregister
を使って結果を保持。
そしてその結果にはansible_job_id
という値が含まれており、これをjid
(Job identifier)パラメタに指定します。
このときに「ループとregister
を組み合わせた場合はresults
にリスト形式で各ループごとの結果が保持される」という動作に合わせて、async_status
モジュールを使ったJob identifierの指定もloop
ディレクティブと{{ item.ansible_job_id }}
を使ってループごとに指定します。
そしてこのtaskは、前の並列ループ処理が非同期で処理される結果、まだ未完了の状態で起動されるため、「並列で動作している非同期処理がすべて完了するまで」リトライし続けるように記述します。
これは、このtaskの結果をregister
で保持し、その保持した結果をuntil
とretries
を使って指定の状態(register
で保持した辞書型の変数のfinished
の値)が真になるまで繰り返し実行します。
非同期処理を待つplaybook自体は以下のエントリを参考。
これで、並列実行のループ処理を実現しつつ、後続のtaskではその結果を待ったうえで処理を続けることができます。
環境
確認した環境は以下の通り。
ansible 2.10.5 config file = /home/zaki/.ansible.cfg configured module search path = ['/home/zaki/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] ansible python module location = /home/zaki/src/ansible-sample/venv/a2.10/lib64/python3.6/site-packages/ansible executable location = /home/zaki/src/ansible-sample/venv/a2.10/bin/ansible python version = 3.6.8 (default, Nov 16 2020, 16:55:22) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]