编写循环和条件任务
利用循环迭代任务
通过利用循环,我们无需编写多个使用同一模块的任务。例如,他们不必编写五个任务来确保存在五个用户,而是只需编写一个任务来对含有五个用户的列表迭代,从而确保它们都存在。
Ansible支持使用loop关键字对一组项目迭代任务。可以配置循环以利用列表中的各个项目、列表中各个文件的内容、生成的数字序列或更为复杂的结构来重复任务。
简单循环
简单循环对一组项目迭代任务。loop关键字添加到任务中,将应对其迭代任务的项目列表取为值。循环变量item保存每个迭代过程中使用的值。
如果不用循环:
---
- hosts: 192.168.72.4
vars_files:
- password
tasks:
- name: mkdir /var/www/html/wcl
shell: mkdir /var/www/html/wcl
- name: mkdir /var/www/html/wei
shell: mkdir /var/www/html/wei
使用简单的循环:
---
- hosts: 192.168.72.4
vars_files:
- password
tasks:
- name: mkdir
shell: mkdir /var/www/html/{{ item }}
loop:
- wcl
- wei
还有另一种写法:
---
- hosts: 192.168.72.4
vars_files:
- password
vars:
dirvars:
- wcl
- wei
tasks:
- name: mkdir
shell: mkdir /var/www/html/{{ item }}
loop: "{{ dirvars }}"
循环散列或字典列表
loop列表不需要是简单值列表。在以下示例中,列表中的每个项实际上是散列或字典。示例中的每个散列或字典具有两个键,即name和groups,当前item循环变量中每个键的值可以分别通过item.name和item.groups变量来检索。
---
- hosts: 192.168.72.4
vars_files:
- password
tasks:
- name: user loop test
user:
name: "{{ item.name }}"
state: present
group: "{{ item.group }}"
loop:
- name: chap
group: chap
- name: rebel
group: rebel
较早样式的循环关键字
在Ansible2.5之前,大多数playbook使用不同的循环语法。提供了多个循环关键字,前缀为whth_,后面跟Ansible查找插件的名称。这种循环语法在现有playbook中很常见,但在将来的某个时候可能会被弃用。
较早样式的Ansible循环
循环关键字 | 描述 |
---|---|
with_items | 行为与简单列表的loop关键字相同,例如字符串列表或散列/字典列表。但与loop不同的是,如果为with_items提供了列表的列表,它们将被扁平化为单级列表。循环变量item保存每次迭代过程中使用的列表项。 |
with_file | 此关键字需要控制节点文件名列表。循环变量item在每次迭代过程中保存文件列表中相应文件的内容。 |
with_sequence | 此关键字不需要列表,而是需要参数来根据数字序列生成值列表。循环变量item在每次迭代过程中保存生成的序列中的一个生成项的值。 |
with_file循环playbook演示:
---
- hosts: 192.168.72.4
vars_files:
- password
vars:
data:
- Ionia
- Zaun
- Noxus
- Piltwoff
- Shurima
- Icathia
tasks:
- name: with_file test
debug:
msg: "{{ item }}"
with_items: "{{ data }}"
将Register变量与Loop一起使用
register关键字也可以捕获循环任务的输出。以下代码片段显示了循环任务中register变量的结构:
register和loop配合使用
---
- hosts: 192.168.72.4
vars_files:
- password
tasks:
- name: register and loop
shell: "echo Ionia {{ item }}"
loop:
- High conservation
- Being immortal
register: result
- name: print result
debug:
var: result
另一种写法:
---
- hosts: 192.168.72.4
vars_files:
- password
tasks:
- name: register and loop
shell: "echo Ionia {{ item }}"
loop:
- High conservation
- Being immortal
register: result
- name: print result
debug:
msg: >
"Send out the message : {{ item }}"
loop: "{{ result['results'] }}"
有条件地运行任务
Ansible可使用conditionals在符合特定条件时执行任务或play。例如,可以利用一个条件在Ansible安装或配置服务前确定受管主机上的可用内存。
我们可以利用条件来区分不同的受管主机,并根据它们所符合的条件来分配功能角色。Playbook变量、注册的变量和Ansible事实都可通过条件来进行测试。可以使用比较字符串、数字数据和布尔值的运算符。
以下场景说明了在Ansible中使用条件的情况:
- 可以在变量中定义硬限制(如min_memory)并将它与受管主机上的可用内存进行比较。
- Ansible可以捕获并评估命令的输出,以确定某一任务在执行进一步操作前是否已经完成。例如,如果某一程序失败,则将路过批处理。
- 可以利用Ansible事实来确定受管主机网络配置,并决定要发送的模板文件(如,网络绑定或中继)。
- 可以评估CPU的数量,来确定如何正确调节某一Web服务器。
将注册的变量与预定义的变量进行比较,以确定服务是否已更改。例如,测试服务配置文件的MD5检验以和查看服务是否已更改。
条件任务语法
when语句用于有条件地运行任务。它取要测试的条件为值。如果条件满足,则运行任务。如果条件不满足,则跳过任务。
简单的条件语句案例:
- 这个条件语句的意思是当my_server生效时才安装httpd;如果设置成False那么my_server并不生效
---
- name: when test
hosts: 192.168.72.5
vars_files:
- host_vars/vars/password
vars:
my_server: False
tasks:
- name: {{ my_server }} is installed
yum:
name: httpd
when: my_server
测试可执行性:
[root@localhost ~]# ansible-playbook /server/test.yml -C
PLAY [when test] ***************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [httpd is installed] ******************************************************
skipping: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
#可以看到,这里测试执行时直接跳过了
这里跳过的原因是因为要安装httpd服务的条件是my_server变量必须存在
,而将my_server设置成False
后,my_server变量不生效就不满足when的条件所以即使这个playbook语法没有问题也可以执行,但其中包含的任务是不能被执行的所以直接跳过了。
如果将False改为True:
---
- name: when test
hosts: 192.168.72.5
vars_files:
- host_vars/vars/password
vars:
my_server: True
tasks:
- name: httpd is installed
yum:
name: httpd
state: present
when: my_server
执行测试
[root@localhost ~]# ansible-playbook /server/test.yml -C
PLAY [when test] ***************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [httpd is installed] ******************************************************
changed: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
#此时的状态变成了changed而不是skipped
当状态变为changed证明任务状态已经发生改变,httpd服务已经符合了安装条件
还有另一种写法:
---
- hosts: 192.168.72.5
tasks:
- name: ensure {{ my_server }} is installed
yum:
name: "{{ my_server }}"
state: present
when: my_server is defined
需要注意的是,这里的my_server变量依旧是没有被定义的
[root@localhost ~]# ansible-playbook /server/test.yml -C
PLAY [192.168.72.5] ************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [ensure {{ my_server }} is installed] *************************************
skipping: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
#所以在执行测试时也跳过了任务
如果给定义my_server而不赋值:
---
- hosts: 192.168.72.5
vars:
my_server:
tasks:
- name: ensure {{ my_server }} is installed
yum:
name: "{{ my_server }}"
state: present
when: my_server is defined #这里的条件是如果变量my_server被定义则执行安装httpd的任务
执行测试:
[root@localhost ~]# ansible-playbook /server/test.yml -C
PLAY [192.168.72.5] ************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [ensure is installed] ****************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: TypeError: 'NoneType' object is not iterable
fatal: [192.168.72.5]: FAILED! => {"changed": false, "module_stderr": "Shared connection to 192.168.72.5 closed.\r\n", "module_stdout": "Traceback (most recent call last):\r\n File \"/root/.ansible/tmp/ansible-tmp-1599373016.328495-2845-12211457626778/AnsiballZ_dnf.py\", line 102, in <module>\r\n _ansiballz_main()\r\n File \"/root/.ansible/tmp/ansible-tmp-1599373016.328495-2845-12211457626778/AnsiballZ_dnf.py\", line 94, in _ansiballz_main\r\n invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\r\n File \"/root/.ansible/tmp/ansible-tmp-1599373016.328495-2845-12211457626778/AnsiballZ_dnf.py\", line 40, in invoke_module\r\n runpy.run_module(mod_name='ansible.modules.packaging.os.dnf', init_globals=None, run_name='__main__', alter_sys=True)\r\n File \"/usr/lib64/python3.6/runpy.py\", line 205, in run_module\r\n return _run_module_code(code, init_globals, run_name, mod_spec)\r\n File \"/usr/lib64/python3.6/runpy.py\", line 96, in _run_module_code\r\n mod_name, mod_spec, pkg_name, script_name)\r\n File \"/usr/lib64/python3.6/runpy.py\", line 85, in _run_code\r\n exec(code, run_globals)\r\n File \"/tmp/ansible_dnf_payload_3myff24x/ansible_dnf_payload.zip/ansible/modules/packaging/os/dnf.py\", line 1318, in <module>\r\n File \"/tmp/ansible_dnf_payload_3myff24x/ansible_dnf_payload.zip/ansible/modules/packaging/os/dnf.py\", line 1305, in main\r\n File \"/tmp/ansible_dnf_payload_3myff24x/ansible_dnf_payload.zip/ansible/modules/packaging/os/dnf.py\", line 323, in __init__\r\n File \"/tmp/ansible_dnf_payload_3myff24x/ansible_dnf_payload.zip/ansible/module_utils/yumdnf.py\", line 85, in __init__\r\nTypeError: 'NoneType' object is not iterable\r\n", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}
PLAY RECAP *********************************************************************
192.168.72.5 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
#在执行测试时会这样报错
如果定义的变量,却不给变量赋值。那么这个playbook是不能成功执行的
如果我们给变量赋值:
---
- hosts: 192.168.72.5
vars:
my_server: httpd
tasks:
- name: ensure {{ my_server }} is installed
yum:
name: "{{ my_server }}"
state: present
when: my_server is defined
#这里指定my_server为httpd
测试执行:
[root@localhost ~]# ansible-playbook /server/test.yml -C
PLAY [192.168.72.5] ************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [ensure httpd is installed] ***********************************************
changed: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
#执行测试成功
如果这样编写playbook:
---
- hosts: 192.168.72.5
tasks:
- name: ensure httpd is installed
yum:
name: httpd
state: present
when: my_server is defined
这里的条件是如果my_server变量为被定义则执行安装httpd的任务。在条件语句中只要满足when的条件就执行任务
这里需要注意的是只要在playbook中加入了:
vars:
my_server:
这样就已经相当于定义了my_server只不过此时my_server的值为空
,此时仍可以安装httpd服务即使my_server的值为空
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [192.168.72.5] *****************************************************************************************************************************************
TASK [Gathering Facts] **************************************************************************************************************************************
ok: [192.168.72.5]
TASK [ensure httpd is installed] ****************************************************************************************************************************
changed: [192.168.72.5]
PLAY RECAP **************************************************************************************************************************************************
192.168.72.5 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
#安装成功
- 由此可以得知,when的条件语句有两种写法:
- 将变量作为条件任务的触发器,使用True/False控制条件的触发
- 在playbook中定义变量并直接作为需要实施的任务,通过控制变量是否被定义(
my_server is defined/my_server is not defined
)来控制条件的触发
下表显示了在处理条件时可使用的一些运算:
示例条件
操作 | 示例 |
---|---|
等于(值为字符串 | ansible_machine == “x86_64” |
等于(值为数字) | max_memory == 512 |
小于 | min_memory < 128 |
大于 | min_memory > 256 |
小于等于 | min_memory <= 256 |
大于等于 | min_memory >= 512 |
不等于 | min_memory != 512 |
变量存在 | min_memory is defined |
变量不存在 | min_memory is not defined |
布尔变量是True。1、True或yes的求值为True | memory_available |
布尔变量是False。0、False或no的求值为False | not memory_available |
第一个变量的值存在,作为第二个变量的列表中的值 | ansible_distribution in supported_distros |
举一个ansible_distribution in supported_distros
类型的例子:
---
- hosts: 192.168.72.5
vars:
opreate_system:
- x86_64
- x86_32
tasks:
- name: httpd installed
yum:
name: httpd
state: present
when: ansible_facts['architecture'] in opreate_system
这里的条件语句意思是,如果ansible_facts中的ansible_architecture的值能在opreate_system变量中找到符合的值,则安装httpd服务,opreate_system是自己指定的变量
测试执行:
[root@localhost ~]# ansible-playbook /server/test.yml -C
PLAY [192.168.72.5] ************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [httpd installed] *********************************************************
ok: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
#执行成功
注意when语句的缩进。由于when语句不是模块变量,它必须通过缩进到任务的最高级别,放置在模块的外面。
任务是YAML散列/字典,when语句只是任务中的又一个键,就如任务的名称以及它所使用的模块一样。通常的惯例是将可能存在的任何when关键字放在任务名称和模块(及模块参数)的后面。
测试多个条件
一个when语句可用于评估多个条件。使用and和or关键字组合条件,并使用括号分组条件。
---
- hosts: 192.168.72.5
tasks:
- name: httpd installed
yum:
name: httpd
state: present
when: ansible_facts['architecture'] == 'x86_64' and ansible_facts['bios_version'] == '6.00'
这里的条件语句表示:受管主机ansible_facts下的ansible_architecture必须等于“x86_64”、ansible_bios_version必须等于“6.00”且这两个条件同时满足
,才会执行安装httpd的服务
执行测试:
[root@localhost ~]# ansible-playbook /server/test.yml -C
PLAY [192.168.72.5] ************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [httpd installed] *********************************************************
changed: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
#测试执行成功
如果有一个条件不符合:
---
- hosts: 192.168.72.5
tasks:
- name: httpd installed
yum:
name: httpd
state: present
when: ansible_facts['architecture'] == 'x86_64' and ansible_facts['bios_version'] == '5.00'
这里将bios_version改为"5.00"
执行测试:
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [192.168.72.5] *****************************************************************************************************************************************
TASK [Gathering Facts] **************************************************************************************************************************************
ok: [192.168.72.5]
TASK [httpd installed] **************************************************************************************************************************************
skipping: [192.168.72.5]
PLAY RECAP **************************************************************************************************************************************************
192.168.72.5 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
#可以看到它直接跳过了执行任务
上述的playbook的另一种写法:
when关键字还支持使用列表来描述条件列表。向when关键字提供列表时,将使用and运算组合所有条件。
---
- hosts: 192.168.72.5
tasks:
- name: httpd installed
yum:
name: httpd
state: present
when:
- ansible_facts['architecture'] == 'x86_64'
- ansible_facts['bios_version'] == '6.00'
执行测试:
[root@localhost ~]# ansible-playbook /server/test.yml -C
PLAY [192.168.72.5] ************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [httpd installed] *********************************************************
changed: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
#测试执行成功
这种格式提高了可读性,而可读性是良好编写Ansible Playbook的关键目标。
- 还通过使用括号分组条件,可以表达更复杂的条件语句。
---
- hosts: 192.168.72.5
tasks:
- name: httpd installed
yum:
name: httpd
state: present
when: >
ansible_facts['architecture'] == 'x86_64'
or
ansible_facts['bios_version'] == '5.00'
执行测试:
[root@localhost ~]# ansible-playbook /server/test.yml -C
PLAY [192.168.72.5] ************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [httpd installed] *********************************************************
changed: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
#测试执行成功
可以看出,这种写法的条件语句只需要满足多个条件中的一个就满足了条件,就能执行安装任务。
- 所以,可以将上述的几种方法配合使用:
---
- hosts: 192.168.72.5
tasks:
- name: httpd installed
yum:
name: httpd
state: present
when: >
( ansible_facts['architecture'] == "x86_64" and
ansible_facts['bios_version'] == "6.00" )
or
( ansible_facts['bios_version'] == "5.00" and
ansible_facts['architecture'] == "x86_32" )
括号内的条件必须两个同时满足,而括号外的两个条件整体两个满足一个即可满足条件语句要求
执行测试:
[root@localhost ~]# ansible-playbook /server/test.yml -C
PLAY [192.168.72.5] ************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [httpd installed] *********************************************************
changed: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
#测试执行成功
循环语句和多条件判断语句
---
- hosts: 192.168.72.5
tasks:
- name: httpd installed
yum:
name: httpd
state: present
loop: "{{ ansible_facts['mounts'] }}"
when:
- item.mount == '/'
- item.size_available > 88420000
这里需要满足item.mount和item.size_available > 88420000这两个条件才能触发安装httpd的任务
执行测试:
[root@localhost ~]# ansible-playbook /server/test.yml -C
PLAY [192.168.72.5] ************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [httpd status] ************************************************************
ok: [192.168.72.5] => (item={'mount': '/', 'device': '/dev/mapper/rhel-root', 'fstype': 'xfs', 'options': 'rw,seclabel,relatime,attr2,inode64,noquota', 'size_total': 18238930944, 'size_available': 15615492096, 'block_size': 4096, 'block_total': 4452864, 'block_available': 3812376, 'block_used': 640488, 'inode_total': 8910848, 'inode_available': 8842243, 'inode_used': 68605, 'uuid': '680f4588-3234-499f-b45d-130db66c7297'})
skipping: [192.168.72.5] => (item={'mount': '/boot', 'device': '/dev/nvme0n1p1', 'fstype': 'xfs', 'options': 'rw,seclabel,relatime,attr2,inode64,noquota', 'size_total': 1063256064, 'size_available': 878952448, 'block_size': 4096, 'block_total': 259584, 'block_available': 214588, 'block_used': 44996, 'inode_total': 524288, 'inode_available': 523987, 'inode_used': 301, 'uuid': '5bb18042-166e-4fc7-95fe-3ab2fd539d33'})
PLAY RECAP *********************************************************************
192.168.72.5 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0
#执行测试成功
组合使用注册变量和条件判断语句
---
- hosts: 192.168.72.5
tasks:
- name: httpd status
command: /usr/bin/systemctl is-active httpd #查看httpd服务状态
ignore_errors: yes #忽略报错,报错后直接跳过不终止任务
register: result #将以上任务的结果注册为变量
- name: httpd restarted
service:
name: httpd
state: restarted
when: result == 0 #如果注册变量的返回值为0则执行重启httpd的任务
执行测试:
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [192.168.72.5] ************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [httpd status] ************************************************************
changed: [192.168.72.5]
TASK [httpd restarted] *********************************************************
skipping: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0
#执行成功
实施处理程序
ansible处理程序
Ansible模块设计为具有幂等性。这表示,在正确编写的playbook中,playbook及其任务可以运行多次而不会改变受管主机,除非需要进行更改使受管主机进入所需的状态。
但在时候,在任务确实更改系统时,可能需要运行进一步的任务。例如,更改服务配置文件时可能要求重新加载该服务以便使其更改的配置生效。
处理程序是响应由其他任务触发的通知的任务。仅当任务在受管主机上更改了某些内容时,任务才通知其处理程序。每个处理程序具有全局唯一的名称,在playbook中任务块的末尾触发。如果没有任务通过名称通知处理程序,处理程序就不会运行。如果一个或多个任务通知处理程序,处理程序就会在play中的所有其他任务完成后运行一次。因为处理程序就是任务,所以可以在处理程序中使用他们将用于任何其他任务的模块。通常而言,处理程序被用于重新引导主机和重启服务。
处理程序可视为非活动任务,只有在使用notify语句显式调用时才会被触发。在下列代码片段中,只有配置文件更新并且通知了该任务,restart apache处理程序才会重启Apache服务器:
---
- hosts: 192.168.72.5
tasks:
- name: httpd installed
yum:
name: httpd
state: present
- name: provides configure
template:
src: files/httpd.conf
dest: /etc/httpd/conf/httpd.conf
notify: # notify语句指出该任务需要触发一个处理程序
- restarted apache # 要运行的处理程序的名称(这里写的必须与下面handlers中- name:选项后的要一样)
- name: start service
service:
name: httpd
state: started
handlers: # handlers关键字表示处理程序任务列表的开头
- name: restarted apache # 被任务调用的处理程序的名称
service: # 用于该处理程序的模块
name: httpd
state: restarted
需要注意的是handlers的缩进应该与tasks对齐
执行测试:
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [192.168.72.5] ************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [httpd installed] *********************************************************
changed: [192.168.72.5]
TASK [provides configure] ******************************************************
ok: [192.168.72.5]
TASK [start service] ***********************************************************
changed: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
将模板文件中的ServerName
选项取消注释,而此时受控主机上的httpd.conf文件中的SererName
没有取消注释,通过这个差异来出发handlers测试是否可以出发重启httpd服务:
重新执行:
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [192.168.72.5] ************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [httpd installed] *********************************************************
changed: [192.168.72.5]
TASK [provides configure] ******************************************************
changed: [192.168.72.5]
TASK [start service] ***********************************************************
changed: [192.168.72.5]
RUNNING HANDLER [restarted apache] *********************************************
changed: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# 可以看到httpd服务的安装、提供的模板状态、httpd服务的启动和httpd服务重启状态都发生了改变
受控主机上的文件也取消了注释
ServerName www.example.com:80
handlers注意事项:
- handlers 只会在等playbook中所有任务执行完毕后执行
- handlers 如果碰到很多一样的notify请求,它只会执行一次
- 如果playbook中,只写了handlers而没有指定notify则handlers不会执行任何list中任何任务
使用处理程序的好处
使用处理程序时需要牢记几个重要事项:
- 处理程序始终按照play的handlers部分指定的顺序运行。它们不按在任务中由notify语句列出的顺序运行,或按任务通知它们的顺序运行。
- 处理程序通常在相关play中的所有其他任务完成后运行。playbook的tasks部分中某一任务调用的处理程序,将等到tasks下的所有任务都已处理后才会运行。
- 处理程序名称存在于各play命名空间中。如果两个处理程序被错误地给予相同的名称,则仅会运行一个。
- 即使有多个任务通知处理程序,该处理程序依然仅运行一次。如果没有任务通知处理程序,它就不会运行。
- 如果包含notify语句的任务没有报告changed结果(例如,软件包已安装并且任务报告ok),则处理程序不会获得通知。处理程序将被跳过,直到有其他任务通知它。只有相关任务报告了changed状态,Ansible才会通知处理程序。
处理程序用于在任务对受管主机进行更改时执行额外操作。它们不应用作正常任务的替代。
处理任务失败
管理play中的任务错误
Ansible评估任务的返回代码,从而确定任务是成功还是失败。通常而言,当任务失败时,Ansible将立即在该主机上中止play的其余部分并且跳过所有后续任务。
但有些时候,可能希望即使在任务失败时也继续执行play。例如,或许预期待定任务有可能会失败,并且希望通过有条件地运行某项其他任务来修复。
Ansible有多种功能可用于管理任务错误。
忽略任务失败
默认情况下,任务失败时play会中止。不过,可以通过忽略失败的任务来覆盖此行为。可以在任务中使用ignore_errors关键字来实现此目的。
下列代码片段演示了如何在任务中使用ignore_errors,以便在任务失败时也继续在主机上执行playbook。例如,如果notapkg软件包不存在,则yum模块将失败,但若将ignore_errors设为yes,则执行将继续。
- 没有加ignore_errors时
---
- name: test ignore_errors
hosts: all
tasks:
- name: install a package
yum:
name: noapackage #安装不存在的软件包
state: present
- name: install httpd
yum:
name: httpd
state: present
如果playbook中没有ignore_errors选项,遇到了错误整个任务就会停止运行;即使后面的任务没有问题也会停止在出错的位置
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [test ignore_errors] ******************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [install a package] *******************************************************
fatal: [192.168.72.5]: FAILED! => {"changed": false, "failures": ["noapackage 没有能够与之匹配的软件包: noapackage"], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []}
PLAY RECAP *********************************************************************
192.168.72.5 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
- 加入ignore_errors时
---
- name: test ignore_errors
hosts: all
tasks:
- name: install a package
yum:
name: noapackage
state: present
ignore_errors: yes
- name: install httpd
yum:
name: httpd
state: present
忽略了出错的任务,保证后面的任务也能执行
[root@localhost ~]# ansible-playbook /server/test.yml -C
PLAY [test ignore_errors] ******************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [install a package] *******************************************************
fatal: [192.168.72.5]: FAILED! => {"changed": false, "failures": ["noapackage 没有能够与之匹配的软件包: noapackage"], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []}
...ignoring
TASK [install httpd] ***********************************************************
changed: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
任务失败强制执行
force_handlers必须在playbook中有handlers处理程序时才能生效
- 没有加force_handlers时
---
- name: test force_handlers
hosts: all
tasks:
- name: echo hello world
shell: echo "hello world" >> /root/wcl
notify: httpd restarted
- name: install a package which isn't exist
yum:
name: 1111 #安装一个不存在的软件包
state: present
handlers:
- name: httpd restarted
service:
name: httpd
state: restarted
任务报错后直接终止
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [test force_handlers] *****************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [echo hello world] ********************************************************
changed: [192.168.72.5]
TASK [install a package which isn't exist] *************************************
fatal: [192.168.72.5]: FAILED! => {"changed": false, "failures": ["1111 没有能够与之匹配的软件包: 1111"], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []}
RUNNING HANDLER [httpd restarted] **********************************************
PLAY RECAP *********************************************************************
192.168.72.5 : ok=2 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 rescued=0 ignored=0
受管主机的httpd服务仍然是关闭状态
[root@www ~]# systemctl status httpd
● httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; ve>
Active: inactive (dead)
Docs: man:httpd.service(8)
9月 08 21:38:04 www.yukino.com systemd[1]: Starting The Apache HTTP Se>
9月 08 21:38:04 www.yukino.com systemd[1]: Started The Apache HTTP Ser>
9月 08 21:38:04 www.yukino.com httpd[3009]: Server configured, listeni>
9月 08 21:56:53 www.yukino.com systemd[1]: Stopping The Apache HTTP Se>
9月 08 21:56:54 www.yukino.com systemd[1]: Stopped The Apache HTTP Ser>
9月 08 21:57:00 www.yukino.com systemd[1]: Starting The Apache HTTP Se>
9月 08 21:57:00 www.yukino.com httpd[4969]: Server configured, listeni>
9月 08 21:57:00 www.yukino.com systemd[1]: Started The Apache HTTP Ser>
9月 08 21:57:54 www.yukino.com systemd[1]: Stopping The Apache HTTP Se>
9月 08 21:57:55 www.yukino.com systemd[1]: Stopped The Apache HTTP Ser>
lines 1-15/15 (END)
- 加入force_handlers后
---
- name: test force_handlers
hosts: all
force_handlers: yes #加入force_handlers
tasks:
- name: echo hello world
shell: echo "hello world" >> /root/wcl
notify: httpd restarted
- name: install a package which isn't exist
yum:
name: 1111
state: present
handlers:
- name: httpd restarted
service:
name: httpd
state: restarted
虽然还是有报错,但是重启服务的状态发生了变化
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [test force_handlers] *****************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [echo hello world] ********************************************************
changed: [192.168.72.5]
TASK [install a package which isn't exist] *************************************
fatal: [192.168.72.5]: FAILED! => {"changed": false, "failures": ["1111 没有能够与之匹配的软件包: 1111"], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []}
RUNNING HANDLER [httpd restarted] **********************************************
changed: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=3 changed=2 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
受管主机的httpd服务也重启成功
[root@www ~]# systemctl status httpd
● httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; ve>
Active: active (running) since Tue 2020-09-08 22:02:52 EDT; 36s ago
Docs: man:httpd.service(8)
Main PID: 5918 (httpd)
Status: "Running, listening on: port 80"
Tasks: 213 (limit: 11333)
Memory: 24.5M
CGroup: /system.slice/httpd.service
├─5918 /usr/sbin/httpd -DFOREGROUND
├─5919 /usr/sbin/httpd -DFOREGROUND
├─5920 /usr/sbin/httpd -DFOREGROUND
├─5921 /usr/sbin/httpd -DFOREGROUND
└─5922 /usr/sbin/httpd -DFOREGROUND
9月 08 22:02:51 www.yukino.com systemd[1]: Starting The Apache HTTP Se>
9月 08 22:02:52 www.yukino.com httpd[5918]: Server configured, listeni>
9月 08 22:02:52 www.yukino.com systemd[1]: Started The Apache HTTP Ser>
lines 1-18/18 (END)
指定任务失败条件
可以在任务中使用failed_when关键字来指定表示任务已失败的条件。这通常与命令模块搭配使用,这些模块可能成功执行了某一命令,但命令的输出可能指示了失败。
例如,可以运行输出错误消息的脚本,并使用该消息定义任务的失败状态。下列代码片段演示了如何在任务中使用failed_when关键字:
创建一个有错误的脚本:
[root@localhost ~]# cat /server/files/test.sh
#! /bin/bash
ls aabbbccdd
ls
使用script模块让脚本文件在受管主机上执行
---
- hosts: all
tasks:
- name: test failed_when
script: files/test.sh
然而即使脚本中有错误,在执行过程中也不会报错;因为脚本文件的最后一个任务是成功的所以它视为整个脚本文件是成功的
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [all] *********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [test failed_when] ********************************************************
changed: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
这里用register模块将之前的结果注册成变量,再用debug模块显示出来
---
- hosts: all
tasks:
- name: test failed_when
script: files/test.sh
register: result
- debug:
var: result
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [all] *********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [test failed_when] ********************************************************
changed: [192.168.72.5]
TASK [debug] *******************************************************************
ok: [192.168.72.5] => {
"result": {
"changed": true,
"failed": false,
"rc": 0,
"stderr": "Shared connection to 192.168.72.5 closed.\r\n",
"stderr_lines": [
"Shared connection to 192.168.72.5 closed."
],
"stdout": "ls: 无法访问'aabbbccdd': 没有那个文件或目录\r\n111 anaconda-ks.cfg wcl\r\n",
"stdout_lines": [
"ls: 无法访问'aabbbccdd': 没有那个文件或目录",
"111 anaconda-ks.cfg wcl"
]
}
}
PLAY RECAP *********************************************************************
192.168.72.5 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
可以看出在脚本执行过程中是有错误的,但是playbook没有将错误显示出来;这时可以运用failed_when
---
- hosts: all
tasks:
- name: test failed_when
script: files/test.sh
register: result
failed_when: "'没有那个文件或目录' in result.stdout"
#这里由于我的虚拟机选择的语言是中文,所以这里的报错写的是中文;如果是选择的语言是英文,那么这里应该写成'No such file or directory'
通过这种方式能检测脚本文件中是否存在错误
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [all] *********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [test failed_when] ********************************************************
fatal: [192.168.72.5]: FAILED! => {"changed": true, "failed_when_result": true, "rc": 0, "stderr": "Shared connection to 192.168.72.5 closed.\r\n", "stderr_lines": ["Shared connection to 192.168.72.5 closed."], "stdout": "ls: 无法访问'aabbbccdd': 没有那个文件或目录\r\n111 anaconda-ks.cfg wcl\r\n", "stdout_lines": ["ls: 无法访问'aabbbccdd': 没有那个文件或目录", "111 anaconda-ks.cfg wcl"]}
PLAY RECAP *********************************************************************
192.168.72.5 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
fail模块也可以自定义报错
---
- hosts: all
tasks:
- name: test failed_when
script: files/test.sh
register: result
ignore_errors: yes #这里必须加上忽略错误否则将不会执行后面的任务
- name: errors
file:
msg: "别敲了,出错了"
failed_when: "'没有那个文件或目录' in result.stdout"
#也可以将failed_when改成when,结果相同
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [all] *********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [test failed_when] ********************************************************
changed: [192.168.72.5]
TASK [errors] ******************************************************************
fatal: [192.168.72.5]: FAILED! => {"changed": false, "failed_when_result": true, "msg": "别敲了,出错了"}
PLAY RECAP *********************************************************************
192.168.72.5 : ok=2 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
指定何时任务报告 “Changed” 结果
当任务对托管主机进行了更改时,会报告 changed 状态并通知处理程序。如果任务不需要进行更改,则会报告ok并且不通知处理程序。
changed_when关键字可用于控制任务在何时报告它已进行了更改。例如,下一示例中的shell模块将用于获取供后续任务使用的Kerberos凭据。它通常会在运行时始终报告changed。为抵制这种更改,应设置changed_when: false,以便它仅报告ok或failed。
---
- hosts: all
tasks:
template:
src: files/wcl
dest: /root
changed_when: False
[root@localhost ~]# echo "#" >> /server/files/wcl #修改模板文件,使playbook发生状态变化
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [all] *********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [test changed_when] *******************************************************
ok: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
可以看到,即使修改了模板文件;但由于changed_when的值是False,所有的changed状态都不生效
- 将changed_when改为True
---
- hosts: all
tasks:
- name: test changed_when
template:
src: files/wcl
dest: /tmp/wcl
changed_when: True
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [all] *********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [test changed_when] *******************************************************
changed: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Ansible块和错误处理
在playbook中,块是对任务进行逻辑分组的子句,可用于控制任务的执行方式。例如,任务块可以含有when关键字,以将某一条件应用到多个任务:
通过block模块将两个任务连成一个整体,使when条件对两个任务同时生效;如果不使用block模块,那么这个when条件只会对start httpd这个任务生效
---
- name: block exampl
hosts: all
tasks:
- name: block
block:
- name: install httpd
yum:
name: httpd
state: present
- name: start httpd
service:
name: httpd
state: started
when: ansible_distribution == "RedHat"
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [block exampl] ************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [install httpd] ***********************************************************
changed: [192.168.72.5]
TASK [start httpd] *************************************************************
changed: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
通过块,也可结合rescue和always语句来处理错误。如果块中的任何任务失败,则执行其rescue块中的任务来进行恢复。在block子句中的任务以及rescue子句中的任务(如果出现故障)运行之后,always子句中的任务运行。总结:
- block:定义要运行的主要任务
- rescue:定义要在block子句中定义的任务失败时运行的任务
- always:定义始终都独立运行的任务,不论block和rescue子句中定义的任务是成功还是失败
以下示例演示了如何在playbook中实施块。即使block子句中定义的任务失败,rescue和always子句中定义的任务也会执行。
---
- hosts: all
tasks:
- name: test block rescue always
block:
- name: test block
command: echo "hello world"
- name: test failed
command: ls aabbcc
rescue:
- name: test rescue
command: echo "rescue successfully"
always:
- name: test always
command: echo "always successfully"
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [all] *********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [test block] **************************************************************
changed: [192.168.72.5]
TASK [test failed] *************************************************************
changed: [192.168.72.5]
TASK [test always] *************************************************************
changed: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
在block下的when语句对于rescue和always同样有效,rescue和always中的任务必须同样符合block中的when语句才能执行。因为block、rescue、always是一个整体
---
- hosts: all
tasks:
- name: test block rescue always
block:
- name: test block
command: echo "hello world"
- name: test failed
command: ls
when: ansible_distribution == "RedHat"
rescue:
- name: test rescue
command: echo "rescue successfully"
always:
- name: test always
command: echo "always successfully"
[root@localhost ~]# ansible-playbook /server/test.yml
PLAY [all] *********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.72.5]
TASK [test block] **************************************************************
changed: [192.168.72.5]
TASK [test failed] *************************************************************
changed: [192.168.72.5]
TASK [test always] *************************************************************
changed: [192.168.72.5]
PLAY RECAP *********************************************************************
192.168.72.5 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0