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任务能很快的完成;
2) 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']}}"