Ansible常用功能说明 [异步、并发、委托等]

Ansible的同步模式与异步模式
同步模式: 如果节点数太多,ansible无法一次在所有远程节点上执行任务,那么将先在一部分节点上执行一个任务(每一批节点的数量取决于fork进程数量,默认为5个,可设置),直到这一批所有节点上该任务完全执行完毕才会接入下一个批节点,直到所有节点将该任务都执行完毕,然后重新回到第一批节点开始执行第二个任务。依次类推,直到所有节点执行完所有任务,ansible端才会释放shell。这是默认同步模式,也就是说在未执行完毕时,ansible是占用当前shell的,任务执行完后,释放shell了才可以输入其他命令做其他动作。

异步模式:假如fork控制的并发进程数为5,远程控制节点为24个,则ansible一开始会将5个节点的任务扔在后台,并每隔一段时间去检查这些节点的任务完成情况,当某节点完成不会立即返回,而是继续等待直到5个进程都空闲了,才会将这5个节点的结果返回给ansible端,ansible会继续将下一批5个节点的任务扔在后台并每隔一段时间进行检查,依次类推,直到完成所有任务。

在异步模式下,如果设置的检查时间间隔为0,在将每一批节点的任务丢到后台后都会立即返回ansible,并立即将下一批节点的任务丢到后台,直到所有任务都丢到后台完后,才返回ansible端,ansible才会立即释放占用的shell。即此时ansible是不会管各个节点任务执行情况的,不管执行成功或失败。因此在轮训检查时间内,ansible仍然正在运行(尽管某批任务已经被放到后台执行了),当前shell进程仍被占用处于睡眠状态,只有指定的检查时间间隔为0,才会尽快将所有任务放到后台并释放shell。

一、Ansible的异步和轮询 [async、poll]
Ansible有时候要执行等待时间很长的操作,这个操作可能要持续很长时间,设置超过ssh的timeout。这种情况下可以选择在step中指定async和poll来实现异步操作。其中:async:表示这个step的最长等待时长, 如果设置为0, 表示一直等待下去直到动作完成;poll:表示检查step操作结果的间隔时长。

ansible默认的清单文件是/etc/ansible/hosts,也就是ansible和ansible-ploybook执行时默认读的清单文件。这个可以自行定义。

[root@hostname ~]# cat /etc/ansible/ansible.cfg|grep inventory
#inventory      = /etc/ansible/hosts

[root@hostname ~]# cat /etc/ansible/hosts|tail -2             
[test_server]                   #组名最好不要使用"-",可以使用"_"
172.16.60.241

1)先来看下面初始配置
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : test_server
  tasks :
    - name : ansible-test
      shell : sleep 10
      #async表示上述shell命令的等待时间,设置为0时会一直等待命令结束
      async : 5
      #poll表示检查step操作结果的间隔时长,设置为0表示 不用等待结果,继续做下面的操作,我们可以在下面的step中来验证这个命令是否成功执行.
      poll : 2

执行下看看是否成功:

[root@hostname ~]# ansible-playbook /etc/ansible/test.yml

PLAY [test_server] *******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.241]

TASK [ansible-test] ******************************************************************************************************************************
fatal: [172.16.60.241]: FAILED! => {"changed": false, "msg": "async task did not complete within the requested time - 5s"}

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0 

如上,这个step失败, 因为ansible的任务(就是上面配置中的shell动作)操作时间(10s)超过了最大等待时长(5s)

2)如果将上面的async异步等待时间设置为大于10s,比如12s,则执行就成功了!

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : test_server
  tasks :
    - name : ansible-test
      shell : sleep 10
      #async表示上述shell命令的等待时间,设置为0时会一直等待命令结束
      async : 12
      #poll表示检查step操作结果的间隔时长,设置为0表示 不用等待结果,继续做下面的操作,我们可以在下面的step中来验证这个命令是否成功执行.
      poll : 2

[root@hostname ~]# ansible-playbook /etc/ansible/test.yml             

PLAY [test_server] *******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.241]

TASK [ansible-test] ******************************************************************************************************************************
changed: [172.16.60.241]

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

这时候就不怕任务超时了。可以执行一个12s的任务(大于上面shell执行的时间)。另外,如果poll为0,就相当于一个不关心结果的任务。

3)或者将上面的poll数值设置为0,即不用等待ansible任务执行的结果,立即执行下一个step。
即只需要将任务命令推送到ansible客户机上,不需要等待任务执行完成就立即执行下一个step。

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : test_server
  tasks :
    - name : ansible-test
      shell : sleep 10
      #async表示上述shell命令的等待时间,设置为0时会一直等待命令结束
      async : 5
      #poll表示检查step操作结果的间隔时长,设置为0表示 不用等待结果,继续做下面的操作,我们可以在下面的step中来验证这个命令是否成功执行.
      poll : 0

[root@hostname ~]# ansible-playbook /etc/ansible/test.yml

PLAY [test_server] *******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.241]

TASK [ansible-test] ******************************************************************************************************************************
changed: [172.16.60.241]

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

4)如果还想要更方便地看轮询结果,ansible还提供了这个模块async_status。

[root@hostname ~]# cat /etc/ansible/test.yml             
- hosts : test_server
  tasks :
    - name : ansible-test
      shell : sleep 3
      async : 8
      poll : 2
      register: kevin_result

    - name: 'check ansible-test task polling results '
      async_status: jid={{ kevin_result.ansible_job_id }}
      register: job_result
      until: job_result.finished
      retries: 10

[root@hostname ~]# ansible-playbook /etc/ansible/test.yml

PLAY [test_server] *******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.241]

TASK [ansible-test] ******************************************************************************************************************************
changed: [172.16.60.241]

TASK [check ansible-test task polling results] ***************************************************************************************************
changed: [172.16.60.241]

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

第一个job执行异步任务sleep,并且注册了一个名字叫kevin-result的register变量,用于提供给第二个job作为轮询对象,并且它自己poll设为2 (即自己轮询2次)。
register用于在ansible的playbook中task之间的相互传递变量,
register 这个功能非常有用。当我们需要判断对执行了某个操作或者某个命令后,如何做相应的响应处理(执行其他 ansible 语句),则一般会用到register 。
until表示循环。

第二个job使用async_status模块,进行轮询并返回轮询结果。准备检查10次。
async参数值:代表了这个任务执行时间的上限值。即任务执行所用时间如果超出这个时间,则认为任务失败。此参数若未设置,则为同步执行。
poll参数值:代表了任务异步执行时轮询的时间间隔。

二、Ansible的并发限制 [serial、max_fail_percentage]
当ansible清单文件里设置的组里有很多机器,可以限制一下ansible任务的并发。ansible的并发功能可以在ansible.cfg里修改配置,也可以在playbook中限制服务端的并发数量,这是ansible经常用到的一个关键功能。ansible默认情况下只会创建5个进程,所以一次任务只能同时控制5台机器执行。如果有大量的机器需要控制,或者希望减少进程数,那就可以采取异步执行(async),ansible的模块可以把task放进后台,然后轮询它(poll)。

使用async和poll这两个关键字便可以并行运行一个任务,即在所有机器上一次性运行。async这个关键字会触发ansible并行运作任务,async的值是ansible等待运行这个任务的最大超时值(如果执行超时任务会强制中断导致失败),而poll就是ansible检查这个任务是否完成的频率时间。

  1. serial参数设置并发数
    =====================================================================
    一般情况下, ansible会同时在所有服务器上执行用户定义的操作, 但是用户可以通过serial参数来定义同时可以在多少太机器上执行操作。
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : test_server
  serial: 3

  tasks :
    - name: Install telnet
      yum: name=telnet state=installed

即test_server组内的3台机器完全执行完成play后, 其他机器才能开始执行。

接着看下面的配置

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : all
  serial: 7

  tasks :
    - name: Install telnet
      yum: name=telnet state=installed

    - name : Run Serverstart.sh
      command : /bin/bash /opt/scripts/Serverstart.sh
      async : 300
      poll : 10
      register: kevin_result

如上配置,发现当ansible配置控制超过5台机器时,上面ansible中:
a)yum模块会先在5台机器上跑,完成后再继续剩余2台的机器;
b)command模块的任务会一次性在所有机器上都执行了,然后监听它的回调结果;

这里需要注意下面两种情况
a)情况一: 设置poll=0
如果上面command模块是控制机器开启一个进程放到后台,那就不需要检查这个任务是否完成了,只需要继续其他的动作,
最后再使用wait_for这个模块去检查之前的进程是否按预期中开启了便可。
这时只需要把poll这个值设置为0, 便可以按上面的要求配置ansible不等待job的完成。
b)情况二: 设置async=0
如果有一种需求是有一个task它是需要运行很长的时间,那就需要设置一直等待这个job完成。
这个时候只需要把async的值设成0便可。

简单总结下,适合使用到ansible的polling特性的场景

  • 有一个task需要运行很长的时间,这个task很可能会达到timeout;
  • 有一个任务需要在大量的机器上面运行;
  • 有一个任务是不需要等待它完成的;

不适合使用polling特性的场景

  • task任务是需要运行完后才能继续另外的任务的;
  • task任务能很快的完成;
  1. max_fail_percentage:最大失败百分比
    =====================================================================
    默认情况下, 只要ansible的group中还有server没有失败, ansible就是继续执行tasks。实际上, 用户可以通过max_fail_percentage(最大失败百分比)来限制ansible的并发执行。
    只要超过max_fail_percentage的server失败, ansible就可以中止tasks的执行。serial参数在ansible-1.8以后就开始支持百分比功能了!!

试想一下如果group组里有200台机器,那么如果使用serial来限制并发数量,比如设置serial=10,意思就是一次只执行10台,一直到200台完成。
只要组内还有server没有失败, ansible就是继续执行tasks。这样就显得效率很低了,很不方便!这时就可以使用类似控制流的max_fail_percentage功能了!!

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : all
  max_fail_percentage: 30
  serial: 10

  tasks :
    - name: Install telnet
      yum: name=telnet state=installed

    - name : Run Serverstart.sh
      command : /bin/bash /opt/scripts/Serverstart.sh
      async : 300
      poll : 10
      register: kevin_result

如上配置,即10台机器里有30%的机器执行yum模块的task任务失败,那么就终止这个10台机器的task任务的执行,接着执行下一组10台机器的task任务,这样效果就很棒了。

温馨提示:
实际失败机器必须大于这个百分比时, tasks任务才会被中止;如果等于这个百分比时,task任务是不会被终止的!
踩坑经验:Ansible并发失败(fork=100. 但是真正执行playbook时并没有实现并发)

[root@hostname ~]# cd /usr/lib/python2.7/site-packages/ansible/
[root@hostname ansible]# find . -name ssh.py
./plugins/connection/ssh.py

[root@hostname ansible]# vim plugins/connection/ssh.py
.........
.........
   if C.HOST_KEY_CHECKING and not_in_host_file:
        # lock around the initial SSH connectivity so the user prompt about whether to add
        # the host to known hosts is not intermingled with multiprocess output.
        fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX)
        fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_EX)

   # create process
  (p, stdin) = self._run(ssh_cmd, in_data)
.........
.........

通过以上文件代码可以看出:
如果ansible配置"HOST_KEY_CHECKING=True", 并且ansible客户机信息没有在ansible服务端的~/.ssh/known_hosts里面, 一个进程就会锁死~/.ssh/known_hosts文件。
这样ansible就不能实现并发!

解决方案:
在ansible服务端的/etc/ansible/ansible.cfg文件里配置"host_key_checking = False" [其实ansible.cfg文件里该项默认配置的就是False]
三、Ansible的任务委托 [delegate_to、delegate_facts、run_once]
默认情况下,ansible的所有任务都是在指定的机器上运行的。当在一个独立的群集环境中配置时,只是想操作其中的某一台主机,或者在特定的主机上运行task任务,此时就需要用到ansible的任务委托功能。使用delegate_to关键字可以配置task任务在指定的机器上执行,就是说其他的task任务还是在hosts关键字配置的机器上运行,到了这个关键字所在的任务时,就使用委托的机器运行。

1)委托

通过"delegate_to", ansible可以把某一个task任务放在委托的机器上执行。即在指定的组内的某一台或多台机器上执行task任务。

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : test_server
  serial: 10

  tasks :
    - name: test-haha
      shell: echo "test" > /root/test.list
      delegate_to: 172.16.60.245

则上面的shell模块的task任务只会在172.16.60.245这台节点上执行,test_server组内其他的机器不会执行shell任务。


如果 "delegate_to: 127.0.0.1" 则可以用local_action来代替。即下面两个配置效果是一样的!!

[root@hostname ~]# cat /etc/ansible/test.yml 
- hosts : test_server
  serial: 10

  tasks :
    - name: test-haha
      shell: echo "test" > /root/test.list
      delegate_to: 127.0.0.1

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : test_server
  serial: 10

  tasks :
    - name: test-haha
      local_action: shell echo "test" > /root/test.list

如果设置了多个delegate_to,则执行时只会匹配最下面那个。
例如下面配置中,只会执行"delegate_to: 172.16.60.245", 上面那个"delegate_to: 172.16.60.241"就会被忽略了。

[root@hostname ansible]# cat /etc/ansible/test.yml
- hosts : all
  serial: 10

  tasks :
    - name: test-haha
      shell: echo "test" > /root/test.list
      delegate_to: 172.16.60.241
      delegate_to: 172.16.60.245

delegate_to默认后面只能跟一个主机ip,不能跟多个主机ip。即默认委托到单个主机。
如果有多个ip需要委托,则可以将这些ip重新放一个group,然后delegate_to委托给group组。
delegate_to委托到组的方式:通过items变量方式!!!

[root@hostname ansible]# cat /etc/ansible/hosts |tail -8
[test_server]
172.16.60.241
172.16.60.245
172.16.60.246
127.0.0.1

[kevin_server]
172.16.60.246
127.0.0.1

[root@hostname ansible]# cat /etc/ansible/test.yml 
- hosts: all
  tasks:
    - name: test-haha
      shell: echo "test" > /root/test.list
      delegate_to: "{{item}}"
      with_items: "{{groups['kevin_server']}}"

即将shell这个task任务委托给kevin_server组内的机器执行。

2)委托者的facts

默认情况下, ansible委托任务的facts是inventory_hostname中主机的facts, 而不是被委托机器的facts。

a) delegate_facts
在ansible 2.0 中, 通过设置"delegate_facts: True"可以让task任务去收集被委托机器的facts。

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: test_server
  tasks:
    - name: test-haha
      shell: echo "test" > /root/test.list
      delegate_to: "{{item}}"
      delegate_facts: True
      with_items: "{{groups['kevin_server']}}"

如上配置,表示会收集kevin_server的facts并分配给这些机器, 而不会去收集test_server的facts

b)RUN ONCE
通过设置"run_once: true"来指定该task只能在委托的某一台机器或委托的组内机器上执行一次!!可以和delegate_to 结合使用。
如果没有delegate_to, 那么这个task默认就会在第一台机器上执行!!!

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: test_server
  tasks:
    - name: test-haha
      shell: echo "test" > /root/test.list
      delegate_to: "{{item}}"
      run_once: true
      delegate_facts: True
      with_items: "{{groups['kevin_server']}}"

转自 https://cloud.tencent.com/developer/article/1519723

你可能感兴趣的:(Ansible常用功能说明 [异步、并发、委托等])