Ansible 学习总结(11)—— task 并行执行之 forks 与 serial 参数详解
Posted 科技D人生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Ansible 学习总结(11)—— task 并行执行之 forks 与 serial 参数详解相关的知识,希望对你有一定的参考价值。
前言
Ansible 执行 task 的时候,会依据配置文件中指定的 forks 参数、inventory 中指定的主机以及主机组、以及选择的目标主机和定义的 task 数决定执行的方式。比如 inventory 主机清单文件中,记录了 20 台服务器,配置文件中的 forks 参数指定为 5,同时有 2 个 task 需要执行,此时就表示每次最多允许对 5 台服务器进行操作,如果剩下的服务器数不足 5 台,则对剩下的所有服务器执行操作。那么对于多个 task 以及多台服务器执行的时候,按照目标主机的执行方式,通常有两种方式:
- 广度优先:将每个 task 先按照 forks 约定的参数,在所有服务器上分批并行执行,等这个 task 执行完毕之后,继续按照相同的方式执行后续的 task。
- 深度优先:serial 指定(通常小于 forks 指定的参数)约定数目的服务器上并行执行完所有的 task 之后继续在下一组服务器上,按照相同的方式执行完所有的 task。
一、forks(广度优先)
依据 forks 参数,决定一次在多少个服务器上并行执行相应的 task,对于包含多个 task 的场景,这种方式会先将 1 个 task 在指定的所有服务器上都执行完成之后,才会执行后续的 task。所以对于一个包含多个 task 的 playbook 来说,此时所有服务器的状态都是不完整的,都是处于中间状态的。但是当这个包含多个 task 的 playbook 执行完成之后,所有执行成功的服务器的状态都是被更新完成的。这种方式适合对服务器的中间状态不敏感的场景,其优点是可以对指定的所有主机执行同步的配置操作;缺点是更新过程中所有服务器都是未完成配置的中间状态。比如下面的场景,有 4 台服务器需要配置(nodeA, nodeB, nodeC, nodeD),playbook 中定义了 2 个 task,forks 指定的参数是 5,而每个 task 执行的耗时为 5 秒。此时,执行过程如下:
- 第一次执行:由于 forks 数大于服务器数,所以所有的服务器都会被选中,并行执行第一个 task,所以此时执行完成耗时为 5 秒;
- 第二次执行:同样所有的服务器都会被选中,并且并行执行第二个 task,所以此时执行完成耗时同样为 5 秒。
- 最终,这个 playbook 在所有服务器上执行完成之后,总的耗时为 10 秒。
示例代码如下:
user@wsg7:1st$ vim test_forks.yml
user@wsg7:1st$ cat test_forks.yml
---
- hosts: c7u6s1,c7u6s2,c7u6s3,c7u6s4
gather_facts: false
tasks:
- name: echo hostname
command: hostname
- name: echo datetime
command: date
user@wsg7:1st$ ansible-playbook -f 5 test_forks.yml
PLAY [c7u6s1,c7u6s2,c7u6s3,c7u6s4] >******************************************************************************************> ******************
TASK [echo hostname] **************************************************************************************************************************
changed: [c7u6s1]
changed: [c7u6s2]
changed: [c7u6s3]
changed: [c7u6s4]
TASK [echo datetime] >**************************************************************************************************************************
changed: [c7u6s1]
changed: [c7u6s3]
changed: [c7u6s2]
changed: [c7u6s4]
PLAY RECAP ************************************************************************************************************************************
c7u6s1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
c7u6s2 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
c7u6s3 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
c7u6s4 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
user@wsg7:1st$
上述输出结果中,显示每个 task 在所有服务器上并行执行,执行完成之后,才会开始执行后续的 task。上面是 forks 数大于服务器数的情况,对于 forks 数小于服务器数的场景,比如有 4 台服务器,playbook 中同样有 2 个task,forks 数配置为 2,每个 task 的执行所需时间仍然为 5 秒。此时的执行过程如下:
- 第一次执行:4台服务器中的2台被选中,并行执行第一个task,所以执行完成之后的耗时为5秒;
- 第二次执行:剩下的2台服务器被选中,执行第一个task,所以此时执行完成之后耗时5秒;
- 第三次执行:4台服务器中的2台被选中,并行执行第二个task,所以执行完成之后耗时为5秒;
- 第四次执行:剩下的2台服务器被选中,执行第二个task,所以执行完成耗时为5秒;
- 最终总的耗时为20秒。
示例代码如下:
---
- hosts: c7u6s1,c7u6s2,c7u6s3,c7u6s4
gather_facts: false
tasks:
- name: echo hostname
shell: hostname; sleep 1
- name: echo datetime
shell: date; sleep 1
执行上述的 palybook,具体如下:
user@wsg7:1st$ ansible-playbook -f 2 -v test_forks.yml
Using /etc/ansible/ansible.cfg as config file
PLAY [c7u6s1,c7u6s2,c7u6s3,c7u6s4] ************************************************************************************************************
TASK [echo hostname] **************************************************************************************************************************
changed: [c7u6s1] => "ansible_facts": "discovered_interpreter_python": "/usr/bin/python", "changed": true, "cmd": "hostname; sleep 1", "delta": "0:00:01.006387", "end": "2022-06-28 17:10:30.556334", "msg": "", "rc": 0, "start": "2022-06-28 17:10:29.549947", "stderr": "", "stderr_lines": [], "stdout": "c7u6s1", "stdout_lines": ["c7u6s1"]
changed: [c7u6s2] => "ansible_facts": "discovered_interpreter_python": "/usr/bin/python", "changed": true, "cmd": "hostname; sleep 1", "delta": "0:00:01.003715", "end": "2022-06-28 17:10:30.557691", "msg": "", "rc": 0, "start": "2022-06-28 17:10:29.553976", "stderr": "", "stderr_lines": [], "stdout": "c7u6s2", "stdout_lines": ["c7u6s2"]
changed: [c7u6s3] => "ansible_facts": "discovered_interpreter_python": "/usr/bin/python", "changed": true, "cmd": "hostname; sleep 1", "delta": "0:00:01.004623", "end": "2022-06-28 17:10:31.800163", "msg": "", "rc": 0, "start": "2022-06-28 17:10:30.795540", "stderr": "", "stderr_lines": [], "stdout": "c7u6s3", "stdout_lines": ["c7u6s3"]
changed: [c7u6s4] => "ansible_facts": "discovered_interpreter_python": "/usr/bin/python", "changed": true, "cmd": "hostname; sleep 1", "delta": "0:00:01.004561", "end": "2022-06-28 17:10:31.811011", "msg": "", "rc": 0, "start": "2022-06-28 17:10:30.806450", "stderr": "", "stderr_lines": [], "stdout": "c7u6s4", "stdout_lines": ["c7u6s4"]
TASK [echo datetime] **************************************************************************************************************************
changed: [c7u6s1] => "changed": true, "cmd": "date; sleep 1", "delta": "0:00:01.005884", "end": "2022-06-28 17:10:33.050719", "msg": "", "rc": 0, "start": "2022-06-28 17:10:32.044835", "stderr": "", "stderr_lines": [], "stdout": "Tue Jun 28 17:10:32 CST 2022", "stdout_lines": ["Tue Jun 28 17:10:32 CST 2022"]
changed: [c7u6s2] => "changed": true, "cmd": "date; sleep 1", "delta": "0:00:01.004152", "end": "2022-06-28 17:10:33.051957", "msg": "", "rc": 0, "start": "2022-06-28 17:10:32.047805", "stderr": "", "stderr_lines": [], "stdout": "Tue Jun 28 17:10:32 CST 2022", "stdout_lines": ["Tue Jun 28 17:10:32 CST 2022"]
changed: [c7u6s3] => "changed": true, "cmd": "date; sleep 1", "delta": "0:00:01.004385", "end": "2022-06-28 17:10:34.231918", "msg": "", "rc": 0, "start": "2022-06-28 17:10:33.227533", "stderr": "", "stderr_lines": [], "stdout": "Tue Jun 28 17:10:33 CST 2022", "stdout_lines": ["Tue Jun 28 17:10:33 CST 2022"]
changed: [c7u6s4] => "changed": true, "cmd": "date; sleep 1", "delta": "0:00:01.004384", "end": "2022-06-28 17:10:34.236657", "msg": "", "rc": 0, "start": "2022-06-28 17:10:33.232273", "stderr": "", "stderr_lines": [], "stdout": "Tue Jun 28 17:10:33 CST 2022", "stdout_lines": ["Tue Jun 28 17:10:33 CST 2022"]
从上述的输出时间中,可以看出,每个 task 都是分 2 次执行的,因为指定了 forks 数为 2,实际主机数为 4 台。上述就是所谓的广度优先,即先在所有主机上执行 1 个 task,直至所有的主机都执行完成之后,采用相同的方式执行后续的 task。
三、serial(深度优先)
而对于深度优先的执行方式,则是在指定数目的服务器上执行完 playbook 的所有 task 之后,才会继续在剩余的其他主机上执行这个 playbook 中定义的 task。这是通过在 playbook 中指定 serial 关键字实现的,所以其是在 forks 参数的基础上,进一步进行约定,从而实现指定数目的服务器执行完成 playbook 之后,才会在其他服务器上执行的操作。这种方式类似于滚动更新。比如,此时仍然为 4 台服务器,forks 仍然设置为 5,然后在 playbook 中增加 serial 关键字,并将其值设置为 2,playbook 中仍然有 2 个 task,且每个 task 执行耗费时间为5秒。此时的执行过程如下:
- 第一次执行:从4台服务器中挑选两台,并行执行第一个task,直至第一个task执行完成,此时耗费时间为5秒;
- 第二次执行:在这两台服务器上,继续并行执行第二个task,直至第二个task执行完成,此时耗费时间为5秒;
- 第三次执行:在剩下的2台服务器上,并行执行第一个task,直至第一个task执行完成,此时耗费时间为5秒;
- 第四次执行:继续在剩下的2台服务器上,并行执行第二个task,直至diergetask执行完成,此时耗时为5秒;
- 至此,4台服务器上都已经执行完成了,总耗时为20秒。
示例代码如下:
---
- hosts: c7u6s1,c7u6s2,c7u6s3,c7u6s4
gather_facts: false
serial: 2
tasks:
- name: echo hostname
shell: hostname; sleep 1
- name: echo datetime
shell: date; sleep 1
执行过程如下:
user@wsg7:1st$ ansible-playbook -f 5 test_serial.yml
PLAY [c7u6s1,c7u6s2,c7u6s3,c7u6s4] ************************************************************************************************************
TASK [echo hostname] **************************************************************************************************************************
changed: [c7u6s2]
changed: [c7u6s1]
TASK [echo datetime] **************************************************************************************************************************
changed: [c7u6s1]
changed: [c7u6s2]
PLAY [c7u6s1,c7u6s2,c7u6s3,c7u6s4] ************************************************************************************************************
TASK [echo hostname] **************************************************************************************************************************
changed: [c7u6s3]
changed: [c7u6s4]
TASK [echo datetime] **************************************************************************************************************************
changed: [c7u6s3]
changed: [c7u6s4]
PLAY RECAP ************************************************************************************************************************************
c7u6s1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
c7u6s2 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
c7u6s3 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
c7u6s4 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
user@wsg7:1st$
上述输出结果,也验证了执行过程,即在 serial 指定数目的服务器上执行完 playbook 中定义的所有 task 之后,才会继续在后续的其他服务器上执行这个 playbook。如果 playbook 中的 serial 值比 forks 的值大,此时以 serial 为准,执行并行操作。修改后的 yaml 文件具体如下所示:
---
- hosts: c7u6s1,c7u6s2,c7u6s3,c7u6s4
gather_facts: false
serial: 4
tasks:
- name: echo hostname
shell: hostname; sleep 1
- name: echo datetime
shell: date; sleep 1
执行上述 playbook 的时候,指定 forks 值为 2,此时 serial 的值比 forks 值大。此时的执行效果如下:
user@wsg7:1st$ ansible-playbook -f 2 test_serial.yml
PLAY [c7u6s1,c7u6s2,c7u6s3,c7u6s4] ************************************************************************************************************
TASK [echo hostname] **************************************************************************************************************************
changed: [c7u6s1]
changed: [c7u6s2]
changed: [c7u6s3]
changed: [c7u6s4]
TASK [echo datetime] **************************************************************************************************************************
changed: [c7u6s1]
changed: [c7u6s2]
changed: [c7u6s3]
changed: [c7u6s4]
PLAY RECAP ************************************************************************************************************************************
c7u6s1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
c7u6s2 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
c7u6s3 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
c7u6s4 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
可以看出,上述的执行过程,是 4 个服务器并行执行,并行数量并不受 forks 的数量限制。从上述的两种方式中可以看出,forks 和 serial 都可以决定并行执行的服务器数量,当没有在 playbook 中指定 serial 的时候,则以 forks 的值作为并行运行的服务器数量依据;当在 playbook 中指定了 serial 且同时配置了 forks 的时候,则以 serial 的值作为并行运行的服务器数量的依据。
serial 的其他用法
serial关 键字除了可以指定为数字之外,还可以指定为百分数,表示单次并行执行的主机数占指定的总主机数的百分比。此时的定义形式如下:
---
- name: test play
hosts: webservers
serial: "30%"
tasks:
- name: task1
command: hostname
- name: task2
command: datetime
上述输出表示,每次从指定的所有主机中选择 30% 执行。还可以将 serial 的值设置为列表,每个列表项表示并行执行的服务器数量。具体如下:
---
- name: test play
hosts: webservers
serial:
- 1
- 5
- 10
tasks:
- name: task1
command: hostname
- name: task2
command: date
上述代码的含义,是第一次执行的时候,从指定的所有服务器中挑选 1 台,执行 playbook 中的所有 task,第二次执行的时候,从指定的所有服务器中选中 5 台执行 playbook 中的所有 task,第三次执行,从指定的所有服务器中选中 10 台执行 playbook 中的所有 task,此时如果还有未执行过的服务器,则按照 forks 定义的数量并行执行。在 serial 的列表中,还可以将数字与百分数混合使用,具体如下:
---
- name: test play
hosts: webservers
serial:
- 1
- "50%"
tasks:
- name: task1
command: hostname
- name: task2
command: date
上述代码的含义,是从指定的服务器中选择 1 台执行 playbook,当执行完成之后,从剩余主机中选择全部主机总数的 50% 的主机执行 playbook,此时如果还有未执行过的指定主机,则按照 forks 的指定参数,并行执行。代码如下:
---
- hosts: c7u6s1,c7u6s2,c7u6s3,c7u6s4
gather_facts: false
serial:
- 1
- 50%
tasks:
- name: echo hostname
shell: hostname; sleep 1
- name: echo datetime
shell: date; sleep 1
上述 playbook 的执行过程如下:
user@wsg7:1st$ ansible-playbook -f 5 test_serial.yml
PLAY [c7u6s1,c7u6s2,c7u6s3,c7u6s4] ************************************************************************************************************
TASK [echo hostname] **************************************************************************************************************************
changed: [c7u6s1]
TASK [echo datetime] **************************************************************************************************************************
changed: [c7u6s1]
PLAY [c7u6s1,c7u6s2,c7u6s3,c7u6s4] ************************************************************************************************************
TASK [echo hostname] **************************************************************************************************************************
changed: [c7u6s2]
changed: [c7u6s3]
TASK [echo datetime] **************************************************************************************************************************
changed: [c7u6s3]
changed: [c7u6s2]
PLAY [c7u6s1,c7u6s2,c7u6s3,c7u6s4] ************************************************************************************************************
TASK [echo hostname] **************************************************************************************************************************
changed: [c7u6s4]
TASK [echo datetime] **************************************************************************************************************************
changed: [c7u6s4]
PLAY RECAP ************************************************************************************************************************************
c7u6s1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
c7u6s2 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
c7u6s3 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
c7u6s4 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
从上述输出中可以看出,先挑选出一台服务器执行 playbook,执行完成之后,从剩余的服务器中选择总数的 50% 的服务器(总数 4 台,50% 即为 2 台)继续执行 playbook,此时还有主机没有被执行完,且此时的 forks 数大于剩余的服务器数,所以直接将剩余的服务器全部执行。
以上是关于Ansible 学习总结(11)—— task 并行执行之 forks 与 serial 参数详解的主要内容,如果未能解决你的问题,请参考以下文章
Ansible 学习总结—— Ansible playbook 入门详解
Ansible 学习总结—— Ansible playbook 入门详解
Ansible 学习总结—— Ansible 循环条件判断触发器处理失败等任务控制使用总结