Ansible的task执行方式控制:forks以及serial


在这里插入图片描述
Ansible执行task的时候,会依据配置文件中指定的forks参数、inventory中指定的主机以及主机组、以及选择的目标主机和定义的task数决定执行的方式。

比如inventory主机清单文件中,记录了20台服务器,配置文件中的forks参数指定为5,同时有2个task需要执行,此时就表示每次最多允许对5台服务器进行操作,如果剩下的服务器数不足5台,则对剩下的所有服务器执行操作。

那么,对于多个task,以及多台服务器执行的时候,按照目标主机的执行方式,通常有2种方式:广度优先和深度优先。

所谓的广度优先,是将每个task先按照forks约定的参数,在所有服务器上分批并行执行,等这个task执行完毕之后,继续按照相同的方式执行后续的task。

而所谓的深度优先,是在serial参数指定的参数(通常小于forks指定的参数)约定数目的服务器上并行执行完所有的task之后,才会继续在下一组服务器上,按照相同的方式执行完所有的task。

1. Ansible执行task的方式:广度优先

依据forks参数,决定一次在多少个服务器上并行执行相应的task,对于包含多个task的场景,这种方式会先将1个task在指定的所有服务器上都执行完成之后,才会执行后续的task。所以对于一个包含多个task的playbook来说,此时所有服务器的状态都是不完整的,都是处于中间状态的。

但是当这个包含多个task的playbook执行完成之后,所有执行成功的服务器的状态都是被更新完成的。

这种方式适合对服务器的中间状态不敏感的场景,其优点是可以对指定的所有主机执行同步的配置操作;缺点是更新过程中所有服务器都是未完成配置的中间状态。

比如下面的场景,有4台服务器需要配置(nodeA, nodeB, nodeC, nodeD),playbook中定义了2个task,forks指定的参数是5,而每个task执行的耗时为5秒。

此时,执行过程如下:

  1. 第一次执行:由于forks数大于服务器数,所以所有的服务器都会被选中,并行执行第一个task,所以此时执行完成耗时为5秒;
  2. 第二次执行:同样所有的服务器都会被选中,并且并行执行第二个task,所以此时执行完成耗时同样为5秒。
  3. 最终,这个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秒。

此时的执行过程如下:

  1. 第一次执行:4台服务器中的2台被选中,并行执行第一个task,所以执行完成之后的耗时为5秒;
  2. 第二次执行:剩下的2台服务器被选中,执行第一个task,所以此时执行完成之后耗时5秒;
  3. 第三次执行:4台服务器中的2台被选中,并行执行第二个task,所以执行完成之后耗时为5秒;
  4. 第四次执行:剩下的2台服务器被选中,执行第二个task,所以执行完成耗时为5秒;
  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。

2. Ansible执行task的方式:深度优先

而对于深度优先的执行方式,则是在指定数目的服务器上执行完playbook的所有task之后,才会继续在剩余的其他主机上执行这个playbook中定义的task。

这是通过在playbook中指定serial关键字实现的,所以其是在forks参数的基础上,进一步进行约定,从而实现指定数目的服务器执行完成playbook之后,才会在其他服务器上执行的操作。这种方式,类似于滚动更新。

比如,此时仍然为4台服务器,forks仍然设置为5,然后在playbook中增加serial关键字,并将其值设置为2,playbook中仍然有2个task,且每个task执行耗费时间为5秒。

此时的执行过程如下:

  1. 第一次执行:从4台服务器中挑选两台,并行执行第一个task,直至第一个task执行完成,此时耗费时间为5秒;
  2. 第二次执行:在这两台服务器上,继续并行执行第二个task,直至第二个task执行完成,此时耗费时间为5秒;
  3. 第三次执行:在剩下的2台服务器上,并行执行第一个task,直至第一个task执行完成,此时耗费时间为5秒;
  4. 第四次执行:继续在剩下的2台服务器上,并行执行第二个task,直至diergetask执行完成,此时耗时为5秒;
  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的数量限制。

从上述的两种方式中可以看出,forksserial都可以决定并行执行的服务器数量,当没有在playbook中指定serial的时候,则以forks的值作为并行运行的服务器数量依据;当在playbook中指定了serial且同时配置了forks的时候,则以serial的值作为并行运行的服务器数量的依据。

2.1. 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数大于剩余的服务器数,所以直接将剩余的服务器全部执行。

3. References

[1]. Difference between Forks and Serial in Ansible
[2]. Controlling playbook execution: strategies and more

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值