はじめに
Ansibleでは、特に何も指定しなければ、1度に全てのノードに対し処理を実行しますが、
全てのノードに対して処理を実行すると、コントロールノードの負荷が高くなり、処理速度が遅くなる可能性があります。
そのため、serial
ディレクティブを使って、指定した台数づつ処理を実行させたところ、見事にハマったので備忘録として残しておきます。
環境
serial ディレクティブとは
Ansible では、serial
ディレクティブを使うことで、指定した台数づつ処理を実行することができます。
Delegation, Rolling Updates, and Local Actions — Ansible DocumentationTitle
下記のようなplaybookを実行すると、PLAYレベルで分割され、sv というグループに対して1度に2台づつ、ディレクトリ作成とファイルをコピーできます。
Playbook
- name: copy file hosts: sv serial: 2 gather_facts: false tasks: - name: Create directory ansible.builtin.file: path: ./dest state: directory - name: Copy file ansible.builtin.copy: src: ./src/file dest: ./dest/file
実行結果
$ anisble-playbook test.yml -i inventory.ini ### 1回目のプレイ PLAY [sv] ***************************************************************** TASK [Create directory] *************************************************** changed: [test1] changed: [test2] TASK [Copy file] ********************************************************** changed: [test1] changed: [test2] ### 2回目のプレイ PLAY [sv] ***************************************************************** TASK [Create directory] *************************************************** changed: [test3] changed: [test4] TASK [Copy file] ********************************************************** changed: [test3] changed: [test4] PLAY RECAP **************************************************************** test1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 test2 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 test3 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 test4 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ただ、serial
を指定した状態で、他のタスクで delegate_to
と run_once
を指定している場合は注意が必要です。
serial と run_once + delegate_to の併用の注意点
例えば、上記の Playbook に対して、コントロールノード側で git clone
を実行させたい場合などです。
この場合、git clone
は1度だけ実行すれば良く、かつコントロールノード側で処理させたいため、run_once
+ delegate_to
を指定します。
すると、以下のように、serial
と run_once
+ delegate_to
を併用すると、serial
で指定した台数づつ処理を実行するときに、1度だけ実行させたいタスクが、serial
で分割された回数分実行されてしまいます。
Playbook
- name: delegate hosts: sv serial: 2 gather_facts: false tasks: - name: git clone ansible.builtin.command: cmd: git clone http://nakayumc:<api>@gitlab.kensho.nakayumc.com/ansible/ansible_repo.git delegate_to: localhost run_once: true - name: Create directory ansible.builtin.file: path: ./dest state: directory - name: Copy file ansible.builtin.copy: src: ./src/file dest: ./dest/file
実行結果
$ anisble-playbook test.yml -i inventory.ini ### 1回目のプレイ PLAY [sv] ***************************************************************** TASK [git clone] ********************************************************** changed: [test1 -> localhost] TASK [Create directory] *************************************************** changed: [test1] changed: [test2] TASK [Copy file] ********************************************************** changed: [test1] changed: [test2] ### 2回目のプレイ PLAY [sv] ***************************************************************** ### ディレクトリが既にあるため、git clone は実行できなかった TASK [git clone] ********************************************************** fatal: [test3 -> localhost]: FAILED! => {"changed": true, "cmd": ["git", "clone", "http://nakayumc:<api>@gitlab.kensho.nakayumc.com/ansible_repo.git"], "delta": "0:00:00.002001", "end": "2023-12-23 10:32:12.488677", "msg": "non-zero return code", "rc": 128, "start": "2023-12-23 10:32:12.486676", "stderr": "fatal: destination path 'ansible_repo' already exists and is not an empty directory.", "stderr_lines": ["fatal: destination path 'ansible_repo' already exists and is not an empty directory."], "stdout": "", "stdout_lines": []} NO MORE HOSTS LEFT ******************************************************** PLAY RECAP ****************************************************************
解決方法
解決方法としては、PLAYレベルで localhost
に対して処理を実行させるもの、sv
に対して処理を実行させるものに分けることで解消できました。
localhost に対して処理を実行させるものは、serial
を指定せず、run_once
+ delegate_to
を指定します。
Playbook
- name: Localhost hosts: localhost gather_facts: false tasks: - name: Git clone ansible.builtin.command: cmd: git clone http://nakayumc:<api>@gitlab.kensho.nakayumc.com/ansible/test.git delegate_to: localhost run_once: true ignore_errors: true - name: Linux Hosts hosts: sv serial: 2 gather_facts: false tasks: - name: Create directory ansible.builtin.file: path: ./dest state: directory - name: Copy file ansible.builtin.copy: src: ./src/file dest: ./dest/file
実行結果
$ play test.yml -i inventory.ini PLAY [Localhost] ********************************************************** TASK [Git clone] ********************************************************** changed: [localhost] PLAY [Linux Hosts] ******************************************************** TASK [Create directory] *************************************************** ok: [test1] ok: [test2] TASK [Copy file] ********************************************************** ok: [test2] ok: [test1] PLAY [Linux Hosts] ******************************************************** TASK [Create directory] *************************************************** ok: [test4] ok: [test3] TASK [Copy file] ********************************************************** ok: [test4] ok: [test3] PLAY RECAP **************************************************************** localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 test1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 test2 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 test3 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 test4 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
まとめ
今回は、serial
ディレクティブがある場合で、run_once
+ delegate_to
のタスクがある場合について、
serial
と run_once
+ delegate_to
を併用すると、serial
で指定した台数づつ処理を実行するときに、1度だけ実行させたいタスクが、serial
で分割された回数分実行されてしまうことについて紹介しました。
serial
と run_once
+ delegate_to
を併用する場合は、PLAYレベルで localhost に対して処理を実行させるもの、sv に対して処理を実行させるものに分けることで解消できました。