Ansible 日常使用技巧
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操作结果的间隔时长。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
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时并没有实现并发)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
[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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
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模块来完成的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
[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模块来判断是否往下继续执行或者中断。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
远端机器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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
|
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的执行效率和saltstack(基于zeromq消息队列的方式)相比要慢的多的多,特别是被控节点量很大的时候。但是ansible发展到现在,它的效率得到了极大的改善。在被控节点不太多的时候,默认的设置已经够快。即使被控节点数量巨大的时候,也可以通过一些优化去极大的提高ansible的执行效率。所以在使用 Ansible 的过程中,当管理的服务器数量增加时,不得不面对一个无法避免的问题执行效率慢,这里列出一些解决办法。
优化一: 关闭gathering facts功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
如果观察过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 |
优化二: 开启 SSH pipelining
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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 |
优化三:开启SSH长连接 (ControlPersist特性)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
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 机器即可。 |
优化四: 开启accelerate模式 [ 注意:这个只针对centos6系统 ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
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没有性能瓶颈的情况下,不推荐使用这种优化措施! |
优化五: 设置facts缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
如果细心的话, 就会发现执行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 的运行。 |
优化六: Ansible取消交互
1
2
3
4
5
6
7
8
9
10
11
12
|
[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 |
优化七:ansible的-t选项,提高ansible执行效率
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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执行效率会有所提升。 |