Ansible默认只会创建5个进程并发执行任务,所以一次任务只能同时控制5台机器执行。如果有大量的机器需要控制,例如20台,Ansible执行一个任务时会先在其中5台上执行,执行成功后再执行下一批5台,直到全部机器执行完毕。使用-f选项可以指定进程数,指定的进程数量多一些,不仅会实现全并发,对异步的轮训poll也会有正面影响。

Ansible默认是同步阻塞模式,它会等待所有的机器都执行完毕才会在前台返回。Ansible可以采取异步执行模式。异步模式下,Ansible会将节点的任务丢在后台,每台被控制的机器都有一个job_id,Ansible会根据这个job_id去轮训该机器上任务的执行情况,例如某机器上此任务中的某一个阶段是否完成,是否进入下一个阶段等。即使任务早就结束了,但只有轮训检查到任务结束后才认为该job结束。Ansible可以指定任务检查的时间间隔,默认是10秒。除非指定任务检查的间隔为0,否则会等待所有任务都完成后,Ansible端才会释放占用的shell。如果指定时间间隔为0,则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任务能很快的完成; 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']}}"

四、Ansible的任务暂停  [ local_action、wait_for ]
当Ansible一些任务的运行需要等到一些状态的恢复,比如某一台主机或者应用刚刚重启,需要等待其某个端口开启,这个时候就需要用到Ansible的任务暂停功能。Ansible任务的暂停操作是通过local_action配合wait_for模块来完成的。

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: test_server
remote_user: root
gather_facts: no tasks:
- name: kevin_test
local_action:
module: wait_for #模块名字
port: 2379
host: 172.16.60.241
delay: 10
timeout: 300
state: started 使用local_action配合wait_for模块来完成任务的暂停操作。
上面配置说明kevin_test任务每隔10s检查指定主机上的2379端口是否开启,如果操作300s,2379端口任未开启,将返回失败信息。 上面host指定了一台机器,如果是需要指定多台机器呢?
可以将执行的多台机器放在一台新group内,然后通过变量去指定group。
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: test_server
remote_user: root
gather_facts: no tasks:
- name: kevin_test
local_action:
module: wait_for
port: 2379
host: "{{item}}"
delay: 10
timeout: 300
state: started
with_items: "{{groups['kevin_server']}}" 如上面配置,每间隔10s检查指定的kevin_server组内的主机的2379端口是否开启,如果操作300s,2379端口没开启,则返回失败信息。
注意:上面的"with_items"这一项配置要和"local_action"对齐!!否则会报错!

五、Ansible如何判断并中断执行  [ when、fail ]
在使用ansible-playbook在执行一个脚本时,如何根据脚本返回的内容判断是否继续往下执行还是中断执行?查询官网可以发现使用register寄存器可以实现记录脚本输出,并且使用when+fail模块来判断是否往下继续执行或者中断

远端机器172.16.60.242有如下脚本:
[root@242 ~]# cat /mnt/scripts/test.sh
#!/bin/bash TEXT=$1 if [ $1 == "kevin" ];then
echo "Success"
else
echo "Failed"
fi [root@242 ~]# /bin/bash /mnt/scripts/test.sh kevin
Success
[root@242 ~]# /bin/bash /mnt/scripts/test.sh kevin234
Failed 现在要求:
a)通过ansible执行172.16.60.242的test.sh脚本,当脚本返回Success时,在172.16.60.242机器上创建一个目录/opt/kevin。
b)通过ansible执行172.16.60.242的test.sh脚本,当脚本返回Failed时,则中断执行。 在ansible服务端配置yml文件,相关配置过程如下:
1)如下配置,将command模块的task任务委托给kevin_server组内的172.16.60.242机器执行。
先使用了register寄存器,具体寄存了什么内容,可以使用-v参数来查看输出 [root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
remote_user: root tasks :
- name: an_bo
command: /bin/bash /mnt/scripts/test.sh kevin
delegate_to: 172.16.60.242
register: result [root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file PLAY [kevin_server] ****************************************************************************************************************************** TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.242]
ok: [172.16.60.241] TASK [an_bo] *************************************************************************************************************************************
changed: [172.16.60.242 -> 172.16.60.242] => {"changed": true, "cmd": ["/bin/bash", "/mnt/scripts/test.sh", "kevin"], "delta": "0:00:00.004078",
"end": "2019-10-11 15:35:49.850430", "rc": 0, "start": "2019-10-11 15:35:49.846352", "stderr": "", "stderr_lines": [], "stdout": "Success",
"stdout_lines": ["Success"]}
changed: [172.16.60.241 -> 172.16.60.242] => {"changed": true, "cmd": ["/bin/bash", "/mnt/scripts/test.sh", "kevin"], "delta": "0:00:00.004502",
"end": "2019-10-11 15:35:49.852445", "rc": 0, "start": "2019-10-11 15:35:49.847943", "stderr": "", "stderr_lines": [], "stdout": "Success",
"stdout_lines": ["Success"]} PLAY RECAP ***************************************************************************************************************************************
172.16.60.241 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
172.16.60.242 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 可以看出:
register保存的信息就是上面执行结果中"=>"后面的字典信息,信息保存在result变量中。
并且看到"stdout"就是脚本的标准输出信息,这时可以使用"when"来判断是否执行或者跳过。 2)使用"when"来判断是否执行或者跳过。
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
remote_user: root tasks :
- name: an_bo
command: /bin/bash /mnt/scripts/test.sh kevin
delegate_to: 172.16.60.242
register: result - name: ru_bo
file: path=/opt/kevin state=directory
delegate_to: 172.16.60.242
when: result.stdout == 'Success' 查看执行结果:
[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file PLAY [kevin_server] ****************************************************************************************************************************** TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.241]
ok: [172.16.60.242] TASK [an_bo] *************************************************************************************************************************************
changed: [172.16.60.242 -> 172.16.60.242] => {"changed": true, "cmd": ["/bin/bash", "/mnt/scripts/test.sh", "kevin"], "delta": "0:00:00.002337", "end": "2019-10-11 15:48:20.427582", "rc": 0, "start": "2019-10-11 15:48:20.425245", "stderr": "", "stderr_lines": [], "stdout": "Success", "stdout_lines": ["Success"]}
changed: [172.16.60.241 -> 172.16.60.242] => {"changed": true, "cmd": ["/bin/bash", "/mnt/scripts/test.sh", "kevin"], "delta": "0:00:00.002579", "end": "2019-10-11 15:48:20.425082", "rc": 0, "start": "2019-10-11 15:48:20.422503", "stderr": "", "stderr_lines": [], "stdout": "Success", "stdout_lines": ["Success"]} TASK [ru_bo] *************************************************************************************************************************************
changed: [172.16.60.241 -> 172.16.60.242] => {"changed": true, "gid": 0, "group": "root", "mode": "0755", "owner": "root", "path": "/opt/kevin", "size": 6, "state": "directory", "uid": 0}
ok: [172.16.60.242 -> 172.16.60.242] => {"changed": false, "gid": 0, "group": "root", "mode": "0755", "owner": "root", "path": "/opt/kevin", "size": 6, "state": "directory", "uid": 0} PLAY RECAP ***************************************************************************************************************************************
172.16.60.241 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
172.16.60.242 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 可以发现,当脚本返回Success时,已经在172.16.60.242机器上创建一个目录/opt/kevin。 3)现在将脚本输出内容修改为"Failed" (即执行脚本时,$1为非kevin字符)
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
remote_user: root tasks :
- name: an_bo
command: /bin/bash /mnt/scripts/test.sh shibo
delegate_to: 172.16.60.242
register: result - name: if stdout 'Failed',Interrupt execution
delegate_to: 172.16.60.242
fail: msg="Check Failed"
when: result.stdout == 'Failed' - name: ru_bo
file: path=/opt/kevin state=directory
delegate_to: 172.16.60.242
when: result.stdout == 'Success' 查看执行结果:
[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file PLAY [kevin_server] ****************************************************************************************************************************** TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.242]
ok: [172.16.60.241] TASK [an_bo] *************************************************************************************************************************************
changed: [172.16.60.241 -> 172.16.60.242] => {"changed": true, "cmd": ["/bin/bash", "/mnt/scripts/test.sh", "shibo"], "delta": "0:00:00.002767", "end": "2019-10-11 15:57:56.049142", "rc": 0, "start": "2019-10-11 15:57:56.046375", "stderr": "", "stderr_lines": [], "stdout": "Failed", "stdout_lines": ["Failed"]}
changed: [172.16.60.242 -> 172.16.60.242] => {"changed": true, "cmd": ["/bin/bash", "/mnt/scripts/test.sh", "shibo"], "delta": "0:00:00.002698", "end": "2019-10-11 15:57:56.051455", "rc": 0, "start": "2019-10-11 15:57:56.048757", "stderr": "", "stderr_lines": [], "stdout": "Failed", "stdout_lines": ["Failed"]} TASK [if stdout 'Failed',Interrupt execution] ****************************************************************************************************
fatal: [172.16.60.241 -> 172.16.60.242]: FAILED! => {"changed": false, "msg": "Check Failed"}
fatal: [172.16.60.242 -> 172.16.60.242]: FAILED! => {"changed": false, "msg": "Check Failed"} PLAY RECAP ***************************************************************************************************************************************
172.16.60.241 : ok=2 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
172.16.60.242 : ok=2 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 可以看出:
playbook运行到第二个task时就会报错并抛出msg!根据第二个task任务,脚本输出结果为"Failed",直接中断任务执行。那么第三个task任务就不会被执行了。 注意:
result寄存器中的数据都可以拿来使用,如"rc","stderr"等。
当然也有很多种方法,文中的"Failed"是严格匹配,也可以使用模糊查找,如"result.stdout.find('Failed') != -1"也可以达到相同的效果 [root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
remote_user: root tasks :
- name: an_bo
command: /bin/bash /mnt/scripts/test.sh shibo
delegate_to: 172.16.60.242
register: result - name: if stdout 'Failed',Interrupt execution
delegate_to: 172.16.60.242
fail: msg="Check Failed"
when: result.stdout.find('Failed') != -1 # 等同于 when: result.stdout == 'Failed' - name: ru_bo
file: path=/opt/kevin state=directory
delegate_to: 172.16.60.242
when: result.stdout == 'Success'

六、Ansible之条件判断  [ when ]
在日常运维工作中,在有的时候ansble-playbook的结果依赖于变量、fact或者是前一个任务的执行结果,从而需要使用到条件语句。使用ansible-playbook时,可能需要对某些条件进行判断,只有当满足条件才执行相应的tasks。有下面几种条件判断:

1)when条件判断:只条满足when的条件时才执行对应的tasks
=====================================================================
需要注意:when关键字后面跟着的是python的表达式,在表达式中我们能够使用任何的变量或者facts。 另外注意:当需要用远程主机的一些信息时,gather_facts必须要开启,默认是开启状态!!!!!
[root@hostname ~]# cat /etc/ansible/hosts |tail -3
[kevin_server]
172.16.60.241
172.16.60.242 注意:下面debug中msg后面引用的变量都是在setup模块中查询出来的(可直接作为变量引用)
[root@hostname ~]# ansible 172.16.60.242 -m setup|grep ansible_fqdn
"ansible_fqdn": "webserver02", [root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
remote_user: root
gather_facts: True tasks:
- name: Host 172.16.60.242 run this task
debug: 'msg=" {{ ansible_default_ipv4.address }}"'
when: ansible_default_ipv4.address == "172.16.60.242" - name: memtotal < 500M and processor_cores == 2 run this task
debug: 'msg="{{ ansible_fqdn }}"'
when: ansible_memtotal_mb < 500 and ansible_processor_cores == 2 - name: all host run this task
shell: hostname
register: info - name: Hostname is webserver01 Machie run this task
debug: 'msg="{{ ansible_fqdn }}"'
when: info['stdout'] == "webserver01" - name: Hostname is startswith l run this task
debug: 'msg="{{ ansible_fqdn }}"'
when: info['stdout'].startswith('l') 查看执行结果:
[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file PLAY [kevin_server] ****************************************************************************************************************************** TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.242]
ok: [172.16.60.241] TASK [Host 172.16.60.242 run this task] **********************************************************************************************************
skipping: [172.16.60.241] => {}
ok: [172.16.60.242] => {
"msg": " 172.16.60.242"
} TASK [memtotal < 500M and processor_cores == 2 run this task] ************************************************************************************
skipping: [172.16.60.241] => {}
skipping: [172.16.60.242] => {} TASK [all host run this task] ********************************************************************************************************************
changed: [172.16.60.241] => {"changed": true, "cmd": "hostname", "delta": "0:00:00.003661", "end": "2019-10-11 17:19:29.912525", "rc": 0,
"start": "2019-10-11 17:19:29.908864", "stderr": "", "stderr_lines": [], "stdout": "webserver01", "stdout_lines": ["webserver01"]}
changed: [172.16.60.242] => {"changed": true, "cmd": "hostname", "delta": "0:00:00.004133", "end": "2019-10-11 17:19:29.922962", "rc": 0,
"start": "2019-10-11 17:19:29.918829", "stderr": "", "stderr_lines": [], "stdout": "webserver02", "stdout_lines": ["webserver02"]} TASK [Hostname is webserver01 Machie run this task] **********************************************************************************************
ok: [172.16.60.241] => {
"msg": "k8s-master01"
}
skipping: [172.16.60.242] => {} TASK [Hostname is startswith l run this task] ****************************************************************************************************
skipping: [172.16.60.241] => {}
skipping: [172.16.60.242] => {} PLAY RECAP ***************************************************************************************************************************************
172.16.60.241 : ok=3 changed=1 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
172.16.60.242 : ok=3 changed=1 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0 2)when条件判断之引用变量
=====================================================================
when变量引用错误提示:[WARNING]: when statements should not include jinja2 templating delimiters such as {{ }} or {% %}. 正确的引用方式:将{{}} or {% %} 改为() 错误写法示例:when: ansible_default_ipv4.address == {{ webserver01 }}
正确写法示例:when: ansible_default_ipv4.address == (webserver01) [root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
remote_user: root
gather_facts: True tasks:
- name: Host 192.168.1.101 run this task
#debug: 'msg=" {{ ansible_default_ipv4.address }}"'
shell: hostname
when: ansible_default_ipv4.address == (webserver02) 查看执行结果:
[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml -e "webserver02=172.16.60.242"
Using /etc/ansible/ansible.cfg as config file PLAY [kevin_server] ****************************************************************************************************************************** TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.241]
ok: [172.16.60.242] TASK [Host 192.168.1.101 run this task] **********************************************************************************************************
skipping: [172.16.60.241] => {"changed": false, "skip_reason": "Conditional result was False"}
changed: [172.16.60.242] => {"changed": true, "cmd": "hostname", "delta": "0:00:00.004349", "end": "2019-10-11 17:23:39.961860", "rc": 0,
"start": "2019-10-11 17:23:39.957511", "stderr": "", "stderr_lines": [], "stdout": "webserver02", "stdout_lines": ["webserver02"]} PLAY RECAP ***************************************************************************************************************************************
172.16.60.241 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
172.16.60.242 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 3)changed_when:先执行task,并对task返回的值进行判断,当满足changed_when指定的条件时说明是执行成功的
=====================================================================
需要注意:默认情况下执行了命令的主机状态都为changed,本例对输出进行判断,包含是某个指定字符才能为changed; [root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
remote_user: root
gather_facts: True tasks:
- name: all host run this task
shell: hostname
register: info
changed_when: '"webserver01" in info.stdout' 查看执行结果:
[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file PLAY [kevin_server] ****************************************************************************************************************************** TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.241]
ok: [172.16.60.242] TASK [all host run this task] ********************************************************************************************************************
changed: [172.16.60.241] => {"changed": true, "cmd": "hostname", "delta": "0:00:00.004531", "end": "2019-10-11 17:25:15.865591", "rc": 0,
"start": "2019-10-11 17:25:15.861060", "stderr": "", "stderr_lines": [], "stdout": "webserver01", "stdout_lines": ["webserver01"]}
ok: [172.16.60.242] => {"changed": false, "cmd": "hostname", "delta": "0:00:00.004694", "end": "2019-10-11 17:25:15.872135", "rc": 0,
"start": "2019-10-11 17:25:15.867441", "stderr": "", "stderr_lines": [], "stdout": "webserver02", "stdout_lines": ["webserver02"]} PLAY RECAP ***************************************************************************************************************************************
172.16.60.241 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
172.16.60.242 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 4)failed_when
=====================================================================
failed_when:当执行失败后,会将信息存在register的stderr中,通过判断指定的字符是否在stderr中来确定是否真的失败; [root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
remote_user: root
gather_facts: True tasks:
- name: this command prints FAILED when it fails
command: echo "FAILED"
register: command_result
failed_when: "'FAILED' in command_result.stdout" - name: this is a test
shell: echo "haha" [root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file PLAY [kevin_server] ****************************************************************************************************************************** TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.242]
ok: [172.16.60.241] TASK [this command prints FAILED when it fails] **************************************************************************************************
fatal: [172.16.60.241]: FAILED! => {"changed": true, "cmd": ["echo", "FAILED"], "delta": "0:00:00.002550", "end": "2019-10-11 19:19:47.918921", "failed_when_result": true, "rc": 0, "start": "2019-10-11 19:19:47.916371", "stderr": "", "stderr_lines": [], "stdout": "FAILED", "stdout_lines": ["FAILED"]}
fatal: [172.16.60.242]: FAILED! => {"changed": true, "cmd": ["echo", "FAILED"], "delta": "0:00:00.002410", "end": "2019-10-11 19:19:47.943843", "failed_when_result": true, "rc": 0, "start": "2019-10-11 19:19:47.941433", "stderr": "", "stderr_lines": [], "stdout": "FAILED", "stdout_lines": ["FAILED"]} PLAY RECAP ***************************************************************************************************************************************
172.16.60.241 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
172.16.60.242 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 可以看出,第一个task任务的failed_when已经满足了,所以就此停止playbook的运行了,下面的task任务也不会执行了! failed_when其实是ansible的一种错误处理机制,是由fail模块使用了when条件语句的组合效果。
所以,上面的配置也可以调整成下面写法(上面第一个task可以调整为下面第1和第2个task的写法,是一样的效果):
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
remote_user: root
gather_facts: True tasks:
- name: this command prints FAILED when it fails
command: echo "FAILED"
register: command_result - name: fail the play if the previous command did not succeed
fail: msg="the command failed"
when: "'FAILED' in command_result.stdout" - name: this is a test
shell: echo "haha" [root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file PLAY [kevin_server] ****************************************************************************************************************************** TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.242]
ok: [172.16.60.241] TASK [this command prints FAILED when it fails] **************************************************************************************************
changed: [172.16.60.241] => {"changed": true, "cmd": ["echo", "FAILED"], "delta": "0:00:00.003989", "end": "2019-10-11 19:19:06.741840", "rc": 0, "start": "2019-10-11 19:19:06.737851", "stderr": "", "stderr_lines": [], "stdout": "FAILED", "stdout_lines": ["FAILED"]}
changed: [172.16.60.242] => {"changed": true, "cmd": ["echo", "FAILED"], "delta": "0:00:00.003135", "end": "2019-10-11 19:19:06.744136", "rc": 0, "start": "2019-10-11 19:19:06.741001", "stderr": "", "stderr_lines": [], "stdout": "FAILED", "stdout_lines": ["FAILED"]} TASK [fail the play if the previous command did not succeed] *************************************************************************************
fatal: [172.16.60.241]: FAILED! => {"changed": false, "msg": "the command failed"}
fatal: [172.16.60.242]: FAILED! => {"changed": false, "msg": "the command failed"} PLAY RECAP ***************************************************************************************************************************************
172.16.60.241 : ok=2 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
172.16.60.242 : ok=2 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 这里就可以看出"failed_when"的作用,它的作用就是当failed_when关键字对应的条件成立时,failed_when会将对应的任务的执行状态设置为失败,以停止playbook的运行!
但是需要注意的时:failed_when虽然会将任务的执行状态设置为失败,但是它并不代表任务真的失败了!就以上面例子来说,上面的command模块的确时完全正常的执行了,
只不过在执行之后,failed_when对应的条件成立了,failed_when将command模块的执行状态设置为失败而已!所以,failed_when并不会影响command模块的执行过程,
只会在条件成立时影响command模块最终的执行状态,以便于停止playbook的运行。 因此需要注意:
failed_when:关键字的作用是在条件成立时,将对应任务的执行状态设置为失败!
changed_when:关键字的作用是在条件成立时,将对应任务的执行状态设置为changed!

七、Ansible之性能优化  [ 提升ansible执行效率 ]
最初,ansible的执行效率和saltstack(基于zeromq消息队列的方式)相比要慢的多的多,特别是被控节点量很大的时候。但是ansible发展到现在,它的效率得到了极大的改善。在被控节点不太多的时候,默认的设置已经够快。即使被控节点数量巨大的时候,也可以通过一些优化去极大的提高ansible的执行效率。所以在使用 Ansible 的过程中,当管理的服务器数量增加时,不得不面对一个无法避免的问题执行效率慢,这里列出一些解决办法。

1.  关闭gathering facts功能

如果观察过ansible-playbook的执行过程,就会发现ansible-playbook的第1个步骤总是执行gather facts,不论你有没有在playbook设定这个tasks。
如果你不需要获取被控机器的fact数据的话,就可以关闭获取fact数据功能。关闭之后,可以加快ansible-playbook的执行效率,尤其是你管理很大量的机器时,这非常明显。
关闭获取facts很简单,只需要在playbook文件中加上"gather_facts: False" 或者 "gather_facts: No"即可(False和No都为小写也可以)。 [root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
remote_user: root tasks:
- name: this is a test
shell: echo "haha" 执行这个paly,会发现第一个执行的是gather facts,因为默认是打开gather facts功能的!!!!
[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file PLAY [kevin_server] ****************************************************************************************************************************** TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.242]
ok: [172.16.60.241] TASK [this is a test] ****************************************************************************************************************************
changed: [172.16.60.241] => {"changed": true, "cmd": "echo \"haha\"", "delta": "0:00:00.002949", "end": "2019-10-11 19:33:54.883702", "rc": 0, "start": "2019-10-11 19:33:54.880753", "stderr": "", "stderr_lines": [], "stdout": "haha", "stdout_lines": ["haha"]}
changed: [172.16.60.242] => {"changed": true, "cmd": "echo \"haha\"", "delta": "0:00:00.003409", "end": "2019-10-11 19:33:54.884398", "rc": 0, "start": "2019-10-11 19:33:54.880989", "stderr": "", "stderr_lines": [], "stdout": "haha", "stdout_lines": ["haha"]} PLAY RECAP ***************************************************************************************************************************************
172.16.60.241 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
172.16.60.242 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 现在关闭gathering facts功能
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
remote_user: root
gather_facts: False tasks:
- name: this is a test
shell: echo "haha" 再执行这个play,就会发现没有了gathering facts执行过程,整个执行速度也快了!
[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file PLAY [kevin_server] ****************************************************************************************************************************** TASK [this is a test] ****************************************************************************************************************************
changed: [172.16.60.242] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": true, "cmd": "echo \"haha\"", "delta": "0:00:00.002571", "end": "2019-10-11 19:35:06.821842", "rc": 0, "start": "2019-10-11 19:35:06.819271", "stderr": "", "stderr_lines": [], "stdout": "haha", "stdout_lines": ["haha"]}
changed: [172.16.60.241] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": true, "cmd": "echo \"haha\"", "delta": "0:00:00.003121", "end": "2019-10-11 19:35:06.842207", "rc": 0, "start": "2019-10-11 19:35:06.839086", "stderr": "", "stderr_lines": [], "stdout": "haha", "stdout_lines": ["haha"]} PLAY RECAP ***************************************************************************************************************************************
172.16.60.241 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
172.16.60.242 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

2.  开启 SSH pipelining

pipeline是openssh的一个特性,ssh pipelining 是一个加速Ansible执行速度的简单方法。

在ansible执行每个任务的整个流程中,有一个过程是将临时任务文件put到远程的ansible客户机上,然后通过ssh连接过去远程执行这个任务。
如果开启了pipelining,一个任务的所有动作都在一个ssh会话中完成,也会省去sftp到远端的过程,它会直接将要执行的任务在ssh会话中进行。 ssh pipelining 默认是关闭!!!!之所以默认关闭是为了兼容不同的sudo 配置,主要是 requiretty 选项。如果不使用sudo,建议开启!!!
打开此选项可以减少ansible执行没有传输时ssh在被控机器上执行任务的连接数。
不过,如果使用sudo,必须关闭requiretty选项。修改/etc/ansible/ansible.cfg 文件可以开启pipelining [root@hostname ~]# vim /etc/ansible/ansible.cfg
........
pipelining = True 这样开启了pipelining之后, ansible执行的整个流程就少了一个PUT脚本去远程服务端的流程,然后就可以批量对机器执行命令试下,可以明显感受到速度的提升。 ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
但是要注意的是:
如果在ansible中使用sudo命令的话(ssh user@host sudo cmd),需要在被控节点的/etc/sudoers中禁用"requiretty"!!!! 之所以要设置/etc/sudoers中的requiretty,是因为ssh远程执行命令时,它的环境是非登录式非交互式shell,默认不会分配tty,没有tty,ssh的sudo就无法关闭密码回显(使用
"-tt"选项强制SSH分配tty)。所以出于安全考虑,/etc/sudoers中默认是开启requiretty的,它要求只有拥有tty的用户才能使用sudo,也就是说ssh连接过去不允许执行sudo。
可以通过visudo编辑配置文件,注释该选项来禁用它。 [root@webserver01 ~]# grep requiretty /etc/sudoers  
# Defaults requiretty

3.  开启SSH长连接 (ControlPersist特性)

ansible天然支持openssh,默认连接方式下,它对ssh的依赖性非常强。所以优化ssh连接,在一定程度上也在优化ansible。其中一点是开启ssh的长连接,即长时间保持连接状态。

Ansible模式是使用SSH和远程主机进行通信, 所以Ansible对SSH的依赖性非常强, 在OpenSSH 5.6版本以后SSH就支持了Multiplexing(多路复用)。
所以如果Ansible中控机的SSH -V版本高于5.6时, 就可以使用ControlPersist来提高ssh连接速度,从而提高ansible执行效率。 [root@hostname ansible]# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core) [root@hostname ansible]# ssh -V
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 2017 我们可以直接在ansible.cfg文件中设置SSH长连接, 设置参数如下:
[root@hostname ansible]# vim /etc/ansible/ansible.cfg
..........
ssh_args = -C -o ControlMaster=auto -o ControlPersist=5d 注意:
ConrolPersist=5d, 这个参数是设置整个长连接保持时间为5天。 开启此参数的ssh长连接功能后,在会话过期前会一直建立连接,在netstat的结果中会看到ssh连接是一直established状态,且通过SSH连接过的设备都会在当前用户家目录的
".ansible/cp"目录下生成一个socket文件,每个会话对应生成一个socket文件。也可以通过netstat命令查看, 会发现有一个ESTABLISHED状态的连接一直与远程设备进行着TCP连接。 [root@hostname ansible]# ps -ef|grep ssh|grep ansible
root 5614 1 0 23:09 ? 00:00:00 ssh: /root/.ansible/cp/7e37065045 [mux]
root 5617 1 0 23:09 ? 00:00:00 ssh: /root/.ansible/cp/e2056334cd [mux] [root@hostname ansible]# netstat -anptu|grep ESTABLISHED|grep ssh|grep /root
tcp 0 0 172.16.60.246:44430 172.16.60.242:22 ESTABLISHED 5617/ssh: /root/.an
tcp 0 0 172.16.60.246:43498 172.16.60.241:22 ESTABLISHED 5614/ssh: /root/.an [root@hostname ansible]# ls /root/.ansible/cp/
7e37065045 e2056334cd 需要注意:
ControlPersist 特性需要高版本的SSH才支持,CentOS 6默认是不支持的,如果需要使用,需要自行升级openssh(确保SSH -V版本高于5.6)。
ControlPersist即持久化socket,一次验证,多次通信。并且只需要修改 ssh 客户端就行,也就是 Ansible 机器即可。

4.  开启accelerate模式  [ 注意:这个只针对centos6系统 ] 

Ansible还有一个accelerate模式, 这和前面的Multiplexing有点类似, 因为都依赖Ansible中控机跟远程机器有一个长连接。
但是accelerate是使用python程序在远程机器上运行一个守护进程, 然后Ansible会通过这个守护进程监听的端口进行通信。
开启accelerate模式很简单, 只要在playbook中配置accelerate: true即可. 但是需要注意的是:
如果开启accelerate模式, 则需要在Ansible中控机与远程机器都安装python-keyczar软件包。
下面是在ansible.cfg文件中定义一些accelerate参数, 当然也可以在写playbook的时候再定义 第一步:ansible服务端和客户端都要安装python-keyczar
[root@hostname ~]# yum install -y python-keyczar 第二步:修改ansible服务端的ansible.cfg文件
[root@hostname ~]# vim /etc/ansible/ansible.cfg
..........
[accelerate]
accelerate_port = 5099
accelerate_timeout = 30
accelerate_connect_timeout = 5.0 第三步:修改ansible服务端的ansible-playbook的剧本文件,加入 accelerate: true
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
remote_user: root
gather_facts: False
accelerate: true tasks:
- name: this is a test
shell: echo "haha" 需要注意:
这种优化方式只针对centos6系统来提高连接速度。在centos7下不可用,否则会报错:"ERROR! 'accelerate' is not a valid attribute for a Play"
如果ansible没有性能瓶颈的情况下,不推荐使用这种优化措施!

5.  设置facts缓存

如果细心的话, 就会发现执行playbook的时候, 默认第一个task都是GATHERING FACTS, 这个过程就是Ansible在收集每台主机的facts信息。
方便我们在playbook中直接饮用facts里的信息,当然如果你的playbook中不需要facts信息, 可以在playbook中设置"gather_facts: False"来提高playbook效率. 但是如果我们既想在每次执行playbook的时候都能收集facts, 又想加速这个收集过程, 那么就需要配置facts缓存了。
目前Ansible支持使用json文件存储facts信息。 第一种缓存方式:使用json文件缓存
[root@hostname ~]# vim /etc/ansible/ansible.cfg
.........
gathering = smart
fact_caching_timeout = 86400
fact_caching = jsonfile
fact_caching_connection = /dev/shm/ansible_fact_cache 正常配置palybook,不需要关闭gathering facts功能
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
remote_user: root tasks:
- name: this is a test
shell: echo "haha" 查看这个playbook过程,用时1.102s(第一次可能稍微慢点,缓存之后,后面执行就很快了)
[root@hostname ~]# time ansible-playbook /etc/ansible/test.yml PLAY [kevin_server] ****************************************************************************************************************************** TASK [this is a test] ****************************************************************************************************************************
changed: [172.16.60.241]
changed: [172.16.60.242] PLAY RECAP ***************************************************************************************************************************************
172.16.60.241 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
172.16.60.242 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 real 0m1.102s
user 0m0.879s
sys 0m0.179s 如果去掉上面的facts缓存的四行配置,再次执行上面的playbok,发现用时10s左右!!! 查看缓存文件:
[root@hostname ~]# ls /dev/shm/ansible_fact_cache/
172.16.60.241 172.16.60.242 第二种缓存方式:使用redis存储facts文件需安装redis,还需要安装python库
[root@hostname ~]# yum install redis [root@hostname ~]# yum -y install epel-release
[root@hostname ~]# yum install python-pip
[root@hostname ~]# pip install redis [root@hostname ~]# vim /etc/ansible/ansible.cfg
........
gathering = smart
facts_caching_timeout = 86400 #设置缓存过期时间86400秒
facts_caching = redis # 使用redis或者 (或者使用memcached,即"facts_caching = memcached")
fact_caching_connection = 127.0.0.1:6379
#若redis设置了密码,比如密码为"admin",则配置修改如下:
# fact_caching_connection = localhost:6379:0:admin 启动redis
[root@hostname ~]# systemctl start redis
[root@hostname ~]# lsof -i:6379
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
redis-ser 29218 redis 4u IPv4 291786209 0t0 TCP localhost:6379 (LISTEN) 执行上面的palybook
[root@hostname ~]# time ansible-playbook /etc/ansible/test.yml PLAY [kevin_server] ****************************************************************************************************************************** TASK [this is a test] ****************************************************************************************************************************
changed: [172.16.60.241]
changed: [172.16.60.242] PLAY RECAP ***************************************************************************************************************************************
172.16.60.241 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
172.16.60.242 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 real 0m1.132s
user 0m0.909s
sys 0m0.178s 需要注意:
在使用redis缓存后,如果出现异常(若未出现,请忽略):TypeError: the JSON object must be str, not 'bytes'。
解决办法:
[root@hostname ~]# find / -name ansible
[root@hostname ~]# vim /usr/lib/python2.7/site-packages/ansible/plugins/cache/redis.py
..........
self._cache[key] = json.loads(value.decode('utf-8')) #修改为这个 查看redis存储情况
[root@hostname ~]# redis-cli
127.0.0.1:6379> keys *
1) "ansible_facts172.16.60.242"
2) "ansible_facts172.16.60.241"
3) "ansible_cache_keys" 总之:不同网络环境下的耗时肯定是不同的,但是设置缓存是肯定可以加快 Ansible 运行速度的,特别是 playbook 的运行。

6.  Ansible取消交互

[root@hostname ~]# vim /etc/ansible/ansible.cfg
........
host_key_checking = False # 打开注释即可 取消ssh的yes和no的交互:
[root@hostname ~]# vim /root/.ssh/config
UserKnownHostsFile /dev/null
ConnectTimeout 15
StrictHostKeyChecking no 或者直接ssh时增加一个参数
[root@hostname ~]# ssh -o StrictHostKeyChecking=no -p22 root@172.16.60.247

7.  Ansible的-t选项,提高ansible执行效率

ansible的"-t"或"--tree"选项是将ansible的执行结果按主机名保存在指定目录下的文件中。

有些时候,ansible执行起来的速度会非常慢,这种慢体现在即使执行的是一个立即返回的简单命令(如ping模块),也会耗时很久,且不是因为ssh连接慢导致的。
如果使用-t选项,将第一次执行得到的结果按inventory中定义的主机名保存在文件中,下次执行到同一台主机时速度将会变快很多,即使之后不再加上-t选项,
也可以在一定时间内保持迅速执行。即使执行速度正常(如执行一个Ping命令0.7秒左右),使用-t选项也可以在此基础上变得更快。 除了使用-t选项,使用重定向将结果重定向到某个文件中也是一样的效果。
这也算是一种ansible提速方式,但在centos6上使用低版本ansible时,有时会出现执行很慢的现象,但不是每次都这样,且centos7执行速度正常
所以这也是一种"bug"式问题,故这种方式没有通用性。 [root@hostname ~]# time ansible kevin_server -m command -a "hostname"
[root@hostname ~]# time ansible kevin_server -m command -a "hostname" -t /tmp/test [root@hostname ~]# ll /tmp/a
total 8
-rw-r--r-- 1 root root 2780 Oct 12 02:03 172.16.60.241
-rw-r--r-- 1 root root 2776 Oct 12 02:03 172.16.60.242 上面做了对比,发现使用-t或重定向方式,将ansible的执行结果按主机名保存在指定目录下的文件中,ansible执行效率会有所提升。

八、Ansible之变量设置
首先Ansible通过facts组件来收集被管理节点信息,facts收集的信息是json格式的,其中任一项都可以当作变量被直接引用,如在ansible-playbook、jinja2模板中引用。

1.  Ansible facts
facts组件是用来收集被管理节点信息的,使用setup模块可以获取这些信息。

[root@ss-server ~]# ansible-doc -s setup
- name: Gathers facts about remote hosts
setup:
..................

如下示例,是setup收集客户机172.16.60.21的信息示例,由于收集的信息项非常多,这里只是截取了部分内容项。

[root@ss-server ~]# ansible 172.16.60.21 -m setup
172.16.60.21 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"172.16.60.21"
],
"ansible_all_ipv6_addresses": [
"fe80::20c:29ff:fe03:a452"
],
"ansible_apparmor": {
"status": "disabled"
},
"ansible_architecture": "x86_64",
"ansible_bios_date": "07/02/2019",
"ansible_bios_version": "6.00",
"ansible_cmdline": {
"BOOT_IMAGE": "/vmkivin-3.10.0-327.el7.x86_64",
"LANG": "en_US.UTF-8",
"biosdevname": "0",
"crashkernel": "auto",
"net.ifnames": "0",
"quiet": true,
"ro": true,
"root": "UUID=b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8"
},
........................................
"ansible_default_ipv6": {},
"ansible_devices": {
"sda": {
"holders": [],
"host": "SCSI storage controller: LSI Logic / Symbios Logic 53c1030 PCI-X Fusion-MPT Dual Ultra320 SCSI (rev 01)",
"model": "VMware Virtual S",
"partitions": {
"sda1": {
"holders": [],
"sectors": "512000",
"sectorsize": 512,
"size": "250.00 MB",
"start": "2048",
"uuid": "367d6a77-033b-4037-bbcb-416705ead095"
},
"sda2": {
"holders": [],
"sectors": "37332992",
"sectorsize": 512,
"size": "17.80 GB",
"start": "514048",
"uuid": "b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8"
},
................................
"ansible_user_dir": "/root",
"ansible_user_gecos": "root",
"ansible_user_gid": 0,
"ansible_user_id": "root",
"ansible_user_shell": "/bin/bash",
"ansible_user_uid": 0,
"ansible_userspace_architecture": "x86_64",
"ansible_userspace_bits": "64",
"ansible_virtualization_role": "guest",
"ansible_virtualization_type": "VMware",
"module_setup": true
},
"changed": false
}

使用filter可以筛选指定的facts信息,如下示例:

[root@ss-server ~]# ansible 172.16.60.21 -m setup -a "filter=changed"
172.16.60.21 | SUCCESS => {
"ansible_facts": {},
"changed": false
} [root@ss-server ~]# ansible localhost -m setup -a "filter=*ipv4"
localhost | SUCCESS => {
"ansible_facts": {
"ansible_default_ipv4": {
"address": "172.16.60.20",
"alias": "eth0",
"broadcast": "172.16.60.255",
"gateway": "172.16.60.2",
"interface": "eth0",
"macaddress": "00:0c:29:d9:0b:71",
"mtu": 1500,
"netmask": "255.255.255.0",
"network": "172.16.60.0",
"type": "ether"
}
},
"changed": false
}

总之,facts收集的信息是json格式的,其里面的任一项都可以在playbook、jinja2模板中当作变量被直接引用。

2.  变量引用json数据的方式
在ansible中,任何一个模块都会返回json格式的数据,即使是错误信息都是json格式的。在ansible中,json格式的数据,其中每一项都可以通过变量来引用它。当然,引用的前提是先将其注册为变量。例如下面的playbook是将shell模块中echo命令的结果注册为变量,并使用debug模块输出。

---
- hosts: 172.16.60.21
tasks:
- shell: echo good work
register: kevin_bo
- debug: var=kevin_bo

debug输出的结果如下:

TASK [debug] *********************************************
ok: [172.16.60.21] => {
"kevin_bo": {
"changed": true,
"cmd": "echo good work",
"delta": "0:00:00.002086",
"end": "2019-05-20 11:23:40.484507",
"rc": 0,
"start": "2019-05-20 11:23:40.482421",
"stderr": "",
"stderr_lines": [],
"stdout": "good work",
"stdout_lines": [
"good work"
]
}
}

可以看出,结果是一段json格式的数据,最顶端的key为kevin_bo,内部是一大段的字典(即使用大括号包围的),其中的stdout_lines还包含了一个json数组,也就是所谓的yaml列表项(即使用中括号包围的)。

2.1  引用json字典数据的方式
如果想要输出json数据的某一字典项,则应该使用"key.dict"或"key['dict']"的方式引用。例如最常见的stdout项"good work"是想要输出的项,以下两种方式都能引用该字典变量。

---
- hosts: 172.16.60.21
tasks:
- shell: echo good work
register: kevin_bo
- debug: var=kevin_bo.stdout
- debug: var=kevin_bo['stdout']

ansible-playbook的部分输出结果如下:

TASK [debug] ************************************************
ok: [172.16.60.21] => {
"kevin_bo.stdout": "good work"
} TASK [debug] ************************************************
ok: [172.16.60.21] => {
"kevin_bo['stdout']": "good work"
}

"key.dict"或"key['dict']"的方式都能引用,但在dict字符串本身就包含"."的时候,应该使用中括号的方式引用。例如:

anykey['172.16.60.21']

2.2  引用json数组数据的方式
如果想要输出json数据中的某一数组项(列表项),则应该使用"key[N]"的方式引用数组中的第N项,其中N是数组的index,从0开始计算。如果不使用index,则输出的是整个数组列表。例如想要输出上面的stdout_lines中的"good work",它是数组stdout_lines中第一项所以使用stdout_lines[0]来引用,再加上stdout_lines上面的kevin_bo,于是引用方式如下:

---
- hosts: 172.16.60.21
tasks:
- shell: echo good work
register: kevin_bo
- debug: var=kevin_bo.stdout_lines[0]

由于stdout_lines中仅有一项,所以即使不使用index的方式即kevin_bo.stdout_lines也能得到期望的结果。输出结果如下:

TASK [debug] *************************************************
ok: [172.16.60.21] => {
"kevin_bo.stdout_lines[0]": "good work"
}

接着看下面一段json数据:

"ipv6": [
{
"address": "fe80::20c:29ff:fe26:1498",
"prefix": "64",
"scope": "link"
}
]

其中key=ipv6,其内部有且仅有是一个列表项,但该列表内包含了数个字典项。要引用列表内的字典,例如上面的address项。应该如下引用:

ipv6[0].address

2.3  引用facts数据
如上介绍已经可以了解json数据中的字典和列表项的引用方式,显然facts中的一大堆数据就能被引用并派上用场了。例如以下是一段facts数据。

[root@ss-server ~]# ansible localhost -m setup -a "filter=*eth*"
localhost | SUCCESS => {
"ansible_facts": {
"ansible_eth0": {
"active": true,
"device": "eth0",
"features": {
"busy_poll": "off [fixed]",
"fcoe_mtu": "off [fixed]",
"generic_receive_offload": "on",
.........................
},
"ipv4": {
"address": "172.16.60.18",
"broadcast": "172.16.60.255",
"netmask": "255.255.255.0",
"network": "172.16.60.0"
},
"macaddress": "00:0c:29:d9:0b:71",
"module": "e1000",
............................
}
},
"changed": false
}

显然,facts数据的顶级key为ansible_facts,在引用时应该将其包含在变量表达式中。但自动收集的facts比较特殊,它以ansible_facts作为key,ansible每次收集后会自动将其注册为变量,所以facts中的数据都可以直接通过变量引用,甚至连顶级key即ansible_facts都要省略。

例如引用上面的ipv4的地址address项,需要写成:

ansible_eth0.ipv4.address

而不能写成:

ansible_facts.ansible_eth0.ipv4.address

但其他任意时候,都应该带上所有的key!

3.  设置本地facts
在ansible收集facts时,还会自动收集/etc/ansible/facts.d/*.fact文件内的数据到facts中,且以ansible_local做为key。目前fact支持两种类型的文件:ini和json。当然,如果fact文件的json或ini格式写错了导致无法解析,那么肯定也无法收集。例如,在/etc/ansible/facts.d目录下存在一个kevin.fact的文件,其内数据如下:

[root@ss-server ~]# cat /etc/ansible/facts.d/kevin.fact
{
"member": {
"wangbo": {
"address": "anhui",
"age": "31"
},
"liuxiaoru": {
"address": "hebei",
"age": "28"
}
}
}

ansible收集facts后的本地facts数据如下:

[root@ss-server ~]# ansible localhost -m setup -a "filter=ansible_local"
localhost | SUCCESS => {
"ansible_facts": {
"ansible_local": {
"kevin": {
"member": {
"wangbo": {
"age": "31",
"address": "anhui"
},
"liuxiaoru": {
"age": "28",
"address": "hebei"
}
}
}
}
},
"changed": false
}

可见,如果想要引用本地文件中的某个key,除了带上ansible_local外,还必须得带上fact文件的文件名。例如,引用father的name。

ansible_local.kevin.member.wangbo.address

4.  输出和引用变量
上面已经展示了一种变量的引用方式:使用debug的var参数。debug的另一个参数msg也能输出变量,且msg可以输出自定义信息,而var参数只能输出变量。另外,msg和var引用参数的方式有所不同。例如:

---
- hosts: 172.16.60.21
tasks:
- debug: 'msg="ipv4 address: {{ansible_eth0.ipv4.address}}"'
- debug: var=ansible_eth0.ipv4.address

msg引用变量需要加上双大括号包围,既然加了大括号,为了防止被解析为内联字典,还得加引号包围。这里使用了两段引号,因为其内部还包括了一个": ",加引号可以防止它被解析为"key: "的格式。而var参数引用变量则直接指定变量名。这就像bash中引用变量的方式是一样的,有些时候需要加上$,有些时候不能加$。即当引用的是变量的值的时候,msg就需要加双大括号,就像加$一样,而当引用的是变量本身的时候,则msg不能加双大括号。其实双大括号是jinja2中的分隔符。执行的部分结果如下:

TASK [debug] *****************************************************
ok: [172.16.60.21] => {
"msg": "ipv4 address: 172.16.60.21"
} TASK [debug] *****************************************************
ok: [172.16.60.21] => {
"ansible_eth0.ipv4.address": "172.16.60.21"
}

在Ansible使用中,几乎所有地方都可以引用变量,例如循环、when语句、信息输出语句、template文件等等。只不过有些地方不能使用双大括号,有些地方需要使用。

5.  注册和定义变量的各种方式
Ansible中定义变量的方式有很多种,大致有下面七种:1) 将模块的执行结果注册为变量;2) 直接定义字典类型的变量;3) role中文件内定义变量;4) 命令行传递变量;5) 借助with_items迭代将多个task的结果赋值给一个变量;6) inventory中的主机或主机组变量;7) 内置变量。

5.1  register注册变量
使用register选项,可以将当前task的输出结果赋值给一个变量。例如,下面的示例中将echo的结果"good work"赋值给kevin_bo变量。注意,模块的输出结果是json格式的,所以,引用变量时要指定引用的对象。

---
- hosts: localhost
tasks:
- shell: echo good work
register: kevin_bo
- debug: var=kevin_bo.stdout

5.2  set_fact定义变量
set_fact和register的功能很相似,也是将值赋值给变量。它更像shell中变量的赋值方式,可以将某个变量的值赋值给另一个变量,也可以将字符串赋值给变量。例如:

---
- hosts: 172.16.60.21
tasks:
- shell: echo good work
register: kevin_bo
- set_fact: var1="{{kevin_bo.stdout}}"
- set_fact: var2="your name is"
- debug: msg="{{var2}} {{var1}}"

5.3  vars定义变量
可以在play或task层次使用vars定义字典型变量。如果同名,则task层次的变量覆盖play层次的变量。例如:

---
- hosts: localhost
vars:
var1: anhui
var2: beijing
tasks:
- debug: msg="{{var1}} {{var2}}"
vars:
var2: shanghai

输出结果为:

TASK [debug] ********************************************
ok: [localhost] => {
"msg": "anhui shanghai"
}

5.4  vars_files定义变量
和vars一样,只不过它是将变量以字典格式定义在独立的文件中,且vars_files不能定义在task层次,只能定义在play层次。

---
- hosts: localhost
vars_files:
- /tmp/var_file1.yml
- var_file2.yml
tasks:
- debug: msg="{{var1}} {{var2}}"

上面var_file2.yml使用的是相对路径,基于playbook所在的路径。例如该playbook为/tmp/x.yml,则var_file2.yml也应该在/tmp下。当然,完全可以使用绝对路径。

5.5  roles中的变量
由于role是整合playbook的,它有默认的文件组织结构。其中有一个目录vars,其内部的main.yml用于定义变量。还有defaults目录内的main.yml则是定义role默认变量的,默认变量的优先级最低。

[root@ss-server ~]# tree /yaml
/yaml
├── roles
│ └── nginx
│ ├── defaults
│ └── main.yml
│ ├── files
│ ├── handlers
│ ├── meta
│ ├── tasks
│ ├── templates
│ └── vars
│ └── main.yml
└── site.yml

main.yml中变量定义方式也是字典格式,例如:

---
mysql_port: 3306

5.6  命令行传递变量
ansible和ansible-playbook命令的"-e"选项都可以传递变量,传递的方式有两种:-e key=value 和 -e @var_file。注意:当key=value方式传递变量时,如果变量中包含特殊字符,必须防止其被shell解析。例如:

ansible localhost -m shell -a "echo {{kevin_bo}}" -e 'kevin_bo="good work"'
ansible localhost -m shell -a "echo {{kevin_bo}}" -e @/tmp/var_file1.yml

其中/tmp/var_file1.yml中的内容如下:

---
kevin_bo: good work

5.7  借助with_items叠加变量
ansible中可以借助with_items实现列表迭代的功能,作用于变量注册的行为上,就可以实现将多个结果赋值给同一个变量。例如下面的playbook中,给出了3个item列表,并在shell模块中通过固定变量"{{item}}"分别迭代,第一次迭代的是anhui,第二次迭代的是beijing,第三次迭代的是shanghai,也就实现了3次循环。最后,将结果注册为变量kevin_bo。

---
- hosts: localhost
remote_user: root
tasks:
- name: test
shell: echo "{{item}}"
with_items:
- anhui
- beijing
- shanghai
register: kevin_bo
- debug: var=kevin_bo.results[0].stdout
- debug: var=kevin_bo.results[1].stdout
- debug: var=kevin_bo.results[2].stdout

每次迭代的过程中,调用item的模块都会将结果保存在一个key为results的数组中。因此,引用迭代后注册的变量时,需要在变量名中加上results,并指定数组名。例如上面的kevin_bo.results[N].stdout。此外,还可以使用for循环遍历列表。例如:

- debug: msg="{% for i in kevin_bo.results %} {{i.stdout}} {% endfor %}"

其实,看一下kevin_bo的输出就很容易理解了。以下是kevin_bo的第一个列表的输出。

"kevin_bo": {
"changed": true,
"msg": "All items completed",
"results": [
{
"_ansible_item_result": true,
"_ansible_no_log": false,
"_ansible_parsed": true,
"changed": true,
"cmd": "echo \"anhui\"",
"delta": "0:00:00.001942",
"end": "2019-05-12 04:45:57.032946",
"invocation": {
"module_args": {
"_raw_params": "echo \"anhui\"",
"_uses_shell": true,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"warn": true
}
},
"item": "anhui",
"rc": 0,
"start": "2019-05-12 04:45:57.031004",
"stderr": "",
"stderr_lines": [],
"stdout": "anhui",
"stdout_lines": [
"anhui"
]
}

5.8  inventory中主机变量和主机组变量
在inventory文件中可以为主机和主机组定义变量,不仅包括内置变量赋值,还包括自定义变量赋值。例如以下inventory文件(/root/ansible/test.yml)。

172.16.60.20 ansible_ssh_port=22 var1=1        #变量优先级别排第二
[webserver] #变量优先级别排第一
172.16.60.18
172.16.60.19
172.16.60.20 var1=2
[webserver:vars] #变量优先级别排第三
var1=2.2
var2=3
[all:vars] #变量优先级别排第四
var2=4

其中ansible_ssh_port是主机内置变量,为其赋值22,这类变量是内置类变量。此外还在多处为主机172.16.60.20进行了赋值。其中[webserver:vars]和[all:vars]表示为主机组赋值,前者是为webserver这个组赋值,后者是为所有组赋值。以下是执行语句:

[root@ss-server ~]# ansible 172.16.60.20 -i /root/ansible/test.yml -m shell -a 'echo "{{var1}} {{var2}} {{ansible_ssh_port}}"'
172.16.60.20 | SUCCESS | rc=0 >>
2 3 22

从结果可知:主机组里面的主机变量优先级高于无主机组的主机变量,设定的主机组变量优先级高于all特殊组。除了在inventory文件中定义主机、主机组变量,还可以将其定义在host_vars和group_vars目录下的独立的文件中,但要求这些host_vars或group_vars这两个目录和inventory文件或playbook文件在同一个目录下,且变量的文件以对应的主机名或主机组名命名。例如,inventory文件路径为/etc/ansible/hosts,playbook文件路径为/root/ansible/play.yml,则主机172.16.60.20和主机组webserver的变量文件路径可以为以下几种:

/etc/ansible/host_vars/172.16.60.20
/etc/ansible/group_vars/webserver
/root/ansible/host_vars/172.16.60.20
/root/ansible/group_vars/webserver

如下是一个host_vars目录下的文件内容:

[root@ss-server ~]# cat /root/ansible/host_vars/172.16.60.20
var1: 1.1
var2: 2.2
var3: 3.3
var4: 4.4

以下为/root/ansible/play.yml的内容

---
- hosts: 172.16.60.20
tasks:
- debug: msg='{{var1}} {{var2}} {{var3}} {{var4}}'

执行结果如下:

TASK [debug] **********************************************
ok: [172.16.60.20] => {
"msg": "1.1 2.2 3.3 4.4"
}

5.9  内置变量
ansible除了inventory中内置的一堆不可被引用的设置类变量,还有几个全局都可以引用的内置变量,主要有以下几个:inventory_hostname、inventory_hostname_short、groups、group_names、hostvars、play_hosts、inventory_dir和ansible_version。

1)inventory_hostname 和 inventory_hostname_short
分别代表的是inventory中被控节点的主机名和主机名的第一部分,如果定义的是主机别名,则变量的值也是别名。例如inventory中webserver主机组定义为如下:

[webserver]
172.16.60.20
web01 ansible_ssh_host=172.16.60.21
www.kevin.com ansible_ssh_host=172.16.60.22

分别输出它们的inventory_hostname和inventory_hostname_short。

[root@ss-server ~]# ansible webserver -m debug -a 'msg="{{inventory_hostname}} & {{inventory_hostname_short}}"'
172.16.60.20 | SUCCESS => {
"msg": "172.16.60.20 & 172"
}
web01 | SUCCESS => {
"msg": "web01 & web01"
}
www.kevin.com | SUCCESS => {
"msg": "www.kevin.com & www"
}

2)groups 和 group_names
group_names返回的是主机所属主机组,如果该主机在多个组中,则返回多个组,如果它不在组中,则返回ungrouped这个特殊组。例如,某个inventory文件如下:

172.16.60.20
172.16.60.21
172.16.60.22
172.16.60.23
[webserver]
172.16.60.20
[dbserver]
172.16.60.21
db01 ansible_ssh_host=172.16.60.22
www.kevin.com ansible_ssh_host=172.16.60.23
[server:children]
webserver
dbserver

其中172.16.60.20定义在webserver和server中,所以返回这两个组。同理172.16.60.21返回dbserver和server。172.16.60.22和172.16.60.23则返回ungrouped,虽然它们在dbserver中都定义了别名,但至少将172.16.60.22和172.16.60.23作为主机名时,它们是不在任何主机中的。另一方面,db01和www.kevin.com这两个别名主机都返回dbserver和server两个组。groups变量则是返回其所在inventory文件中所有组和其内主机名。注意:该变量对每个控制节点都返回一次,所以返回的内容可能非常多。例如,上面的inventory中,如果指定被控节点为dbserver,则会重复返回3次(因为有3台被控主机)该inventory文件。其中的第三台主机www.kevin.com的返回结果为:

www.kevin.com | SUCCESS => {
"msg": {
"all": [
"172.16.60.20",
"172.16.60.21",
"172.16.60.22",
"172.16.60.23",
"db01",
"www.kevin.com"
],
"server": [
"172.16.60.20",
"172.16.60.21",
"db01",
"www.kevin.com"
],
"webserver": [
"172.16.60.20"
],
"dbserver": [
"172.16.60.21",
"db01",
"www.kevin.com"
],
"ungrouped": [
"172.16.60.20",
"172.16.60.21",
"172.16.60.22",
"172.16.60.23"
]
}
}

3)hostvars
该变量用于引用其他主机上收集的facts中的数据,或者引用其他主机的主机变量、主机组变量。其key为主机名或主机组名。举个例子,假如使用ansible部署一台nginx服务器web01,且配置文件内需要指向另一台数据库服务器web02的ip地址ip2,可以直接在配置文件中指定ip2,但也可以在模板配置文件中直接引用host2收集的facts数据中的ansible_eth0.ipv4.address变量。例如,dbserver主机组中包含了172.16.60.[21:23]共3台主机。playbook内容如下:

---
- hosts: dbserver
tasks:
- debug: msg="{{hostvars['172.16.60.21'].ansible_eth0.ipv4.address}}"

执行结果如下:

TASK [debug] *********************************************************
ok: [172.16.60.21] => {
"msg": "172.16.60.21"
}
ok: [172.16.60.22] => {
"msg": "172.16.60.21"
}
ok: [172.16.60.23] => {
"msg": "172.16.60.21"
}

但是请注意:在引用其他主机facts中数据时,要求被引用主机进行了facts收集动作,或者有facts缓存。否则都没收集,当然无法引用其facts数据。也就是说,当被引用主机没有facts缓存时,ansible的控制节点中必须同时包含引用主机和被引用主机。除了引用其他主机的facts数据,还可以引用其他主机的主机变量和主机组变量,且不要求被引用主机有facts数据,因为主机变量和主机组变量是在ansible执行任务前加载的。例如,inventory中格式如下:

172.16.60.18
[dbserver]
172.16.60.21 var1=1.1
172.16.60.22
172.16.60.23
[dbserver:vars]
var2=2.2

playbook内容如下:

---
- hosts: 172.16.60.18
tasks:
- debug: msg="{{hostvars['172.16.60.21'].var1}} & {{hostvars['172.16.60.23'].var2}}"

执行结果如下:

TASK [debug] ***************************************
ok: [172.16.60.18] => {
"msg": "1.1 & 2.2"
}

4)play_hosts 和 inventory_dir
play_hosts代表的是当前play所涉及inventory内的所有主机名列表。inventory_dir是所使用inventory所在的目录。例如,inventory内容为:

172.16.60.18
[webserver]
172.16.60.21
172.16.60.22
[dbserver]
172.16.60.24
172.16.60.25

那么,该inventory内的任意一或多台主机作为ansible或ansible-playbook的被控节点时,都会返回整个inventory内的所有主机名称。

5)ansible_version
代表的是ansible软件的版本号。变量返回的内容如下:

{
"full": "2.3.1.0",
"major": 2,
"minor": 3,
"revision": 1,
"string": "2.3.1.0"
}

#####  ansibl变量的优先级  #####
1.  extra vars变量(在命令行中使用 -e);优先级最高
2.  在inventory中定义的连接变量(比如ansible_ssh_user);优先级第二
3.  大多数的其他变量(命令行转换,play中的变量,include的变量,role的变量等);优先级第三
4.  在inventory定义的其他变量;优先级第四
5.  有系统发现的facts;优先级第五
6.  "role默认变量",这个是最默认的值,很容易丧失优先权。优先级最小。

##### inventory清单列表里定义变量:单个主机定义的变量优先级高于主机组定义的变量 #####
经过实验,ansible使用inventory定义变量的优先级顺序从高到低为:
1.  host_vars下定义变量
2. inventory中单个主机定义变量
3. group_vars下定义变量
4. inventory中组定义变量

总之,ansible提供的变量定义方式真的是太丰富了,这些变量可以让ansible使用起来十分灵活,功能强大!

Ansible 日常使用技巧 - 运维总结的更多相关文章

  1. ansible环境部署及常用模块总结 - 运维笔记

    一.  Ansible 介绍Ansible是一个配置管理系统configuration management system, python 语言是运维人员必须会的语言, ansible 是一个基于py ...

  2. 自动化运维工具-Ansible基础

    目录 自动化运维工具-Ansible基础 什么是Ansible 同类型软件对比 Ansible的功能及优点 Ansible的架构 Ansible的执行流程 安装Ansible ansible配置文件 ...

  3. 18.自动运维工具ansible

    1 Ansible 介绍和架构 1.1 Ansible介绍 ansible 的名称来自科幻小说<安德的游戏>中跨越时空的即时通信工具,使用它可以在相距数光年的 距离,远程实时控制前线的舰队 ...

  4. Linux运维工程师应具备哪些技能?

      对于我们这些刚入门的运维小白来说,极强的好奇心总会驱使我们去涉猎各种技术,弄到最后很可能该学的知识半懵半解,知识体系混乱,学习毫无章法.因此,我们学习 时要有一个明确的目标和知识体系(也是我学习的 ...

  5. 运维自动化管理服务器 CheungSSH

    CheungSSH 是一款中国人自主研发的Linux运维自动化管理服务器软件,后端使用 Python 语言+Django 的 Web 框架,前端使用 Bootstrap+Javascript+jQue ...

  6. linux运维 技能树

    linux运维 技能树:: 初级运维: 基础:mysql基础,网络基础,计算机基础,linux系统vim, nginx ,grep ,awk,sed ,zaabix和常用开源软件,java tomca ...

  7. 如何使用 K8s 两大利器"审计"和"事件"帮你摆脱运维困境?

    概述 下面几个问题,相信广大 K8s 用户在日常集群运维中都曾经遇到过: 集群中的某个应用被删除了,谁干的? Apiserver 的负载突然变高,大量访问失败,集群中到底发生了什么? 集群节点 Not ...

  8. Linux企业运维高效技巧心得及分享

    本博文出自51CTO博主 吴光科 的博客,有任何问题请进入博主页面互动讨论! 博文地址:http://wgkgood.blog.51cto.com/1192594/1641247 随着Linux在企业 ...

  9. Linux运维笔记-日常操作命令总结(1)

    在linux日常运维中,我们平时会用到很多常规的操作命令. 查看服务器的外网ip [root@redis-new01 ~]# curl ifconfig.me [root@redis-new01 ~] ...

随机推荐

  1. TCP服务端

    出处: https://blog.csdn.net/DGH2430284817/article/details/86653294问题描述:       在用socket的通信中,经常会出现这种情况,客 ...

  2. Centos7 基于SVN+Apache+IF.svnadmin实现web管理

    1.简单介绍: iF.SVNAdmin应用程序是您的Subversion授权文件的基于Web的GUI.它基于PHP 5.3,需要安装一个Web服务器(Apache).该应用程序不需要数据库后端或任何类 ...

  3. linux 启动jar包 指定yml配置文件和输入日志文件

    命令为: nohup java -jar project.jar  --spring.config.location=/home/project-conf/application.yml >  ...

  4. C# Winform更换Webbrowse为WebKit

    本人第一次发表博文,内容不精也不是很有水平,请大家多多包涵~ 说到Webbrowser,可能大家都用过这个控件,它默认使用的是旧版本IE内核. 这就导致了一些问题,比如JS的加载不正确,这个问题就足够 ...

  5. C# 序列化和反序列化(xml 文件)

    序列化是将对象保存为文本文件或二进制文件: 反序列化则是读取文件信息,还原为对象: 序列化保存为文本内容,主要是 xml 和 json 两种,这里介绍序列化为 xml 文件的方式. 想要序列化,先要在 ...

  6. 【Java】导入项目时,出现The project cannot be built until build path errors are resolved错误解决方法

    先检查jar包,jar包的地址如果不一样需要remove后重新导入的,右键项目→Build Path. 看额外的jar包有没有×,地址正不正确,要是不正确,remove错误jar包,再点击Add Ex ...

  7. 2019年跨越速递Java工程师笔试题

    1.下面哪个选项可以用于JSP页面之间传递对象(A C) A application B page C session D error  E response 评语:这道题考察的是对JSP内置对象的了 ...

  8. Python 从入门到进阶之路(五)

    之前的文章我们简单介绍了一下 Python 的函数,本篇文章我们来看一下 Python 中的面向对象. Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是 ...

  9. Python 从入门到进阶之路(二)

    之前的文章我们对 Python 语法有了一个简单的认识,接下来我们对 Python 中的 if while for 做一下介绍. 上图为 if 判断语句的流程,无论任何语言,都会涉及到判断问题,if ...

  10. netty源码解析(4.0)-27 ByteBuf内存池:PoolArena-PoolThreadCache

    前面两章分析的PoolChunk和PoolSubpage,从功能上来说已经可以直接拿来用了.但直接使用这个两个类管理内存在高频分配/释放内存场景下会有性能问题,PoolChunk分配内存时算法复杂度最 ...