06 实施任务控制
作者:互联网
1. 编写循环和条件任务
1.1 利用循环迭代任务
通过利用循环,我们无需编写多个使用同一模块的任务。例如,他们不必编写五个任务来确保存在五个用户,而是只需编写一个任务来对含有五个用户的列表迭代,从而确保它们都存在。
Ansible支持使用loop关键字对一组项目迭代任务。可以配置循环以利用列表中的各个项目、列表中各个文件的内容、生成的数字序列或更为复杂的结构来重复任务。
1.1.1 简单循环
简单循环对一组项目迭代任务。loop关键字添加到任务中,将应对其迭代任务的项目列表取为值。循环变量item保存每个迭代过程中使用的值。
请思考以下代码片段,它使用两次user模块来创建用户:
[root@localhost httpd]# vim test.yml --- - hosts: all tasks: - name: create user1 #创建用户1 user: name: user1 # 名字 state: present #状态创建 - name: create user2 user: name: user2 state: present [root@localhost httpd]# ansible-playbook test.yml # 运行 PLAY [all] ***************************************************************************************************** TASK [Gathering Facts] #找事实 ***************************************************************************************** ok: [web01.example.com] TASK [create user1] # 创建第一个******************************************************************************************** changed: [web01.example.com] TASK [create user2] # 创建第2个******************************************************************************************** changed: [web01.example.com] PLAY RECAP ***************************************************************************************************** web01.example.com : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@web01 facts.d]# id user1 uid=1003(user1) gid=1003(user1) 组=1003(user1) # 受控主机查看新创建的2个用户 [root@web01 facts.d]# id user2 uid=1004(user2) gid=1004(user2) 组=1004(user2)
这两个任务可以重新编写为使用一个简单循环,从而只需一个任务来创建2个用户:
[root@localhost httpd]# vim test.yml --- - hosts: all gather_facts: no tasks: - name: create {{ item }} user: name: '{{ item }}' # item是循环 state: present loop: - user1 # 第一次循环是1 - user2 # 第二次循环是2 - user3 # 第三次循环是3 [root@localhost httpd]# ansible-playbook test.yml PLAY [all] ***************************************************************************************************** TASK [create {{ item }}] *************************************************************************************** ok: [web01.example.com] => (item=user1) # 创建1,2,3 ok: [web01.example.com] => (item=user2) changed: [web01.example.com] => (item=user3) PLAY RECAP ***************************************************************************************************** web01.example.com : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@web01 facts.d]# id user1 # 受管主机查看 uid=1003(user1) gid=1003(user1) 组=1003(user1) [root@web01 facts.d]# id user2 uid=1004(user2) gid=1004(user2) 组=1004(user2) [root@web01 facts.d]# id user3 uid=1005(user3) gid=1005(user3) 组=1005(user3)
可以通过一个变量提供loop所使用的列表。在以下示例中,变量mail_services含有需要处于运行状态的服务的列表。
[root@localhost httpd]# vim test.yml --- - hosts: all vars: # 加一个变量 users: - user1 - user2 - user3 gather_facts: no tasks: - name: create {{ item }} user: name: '{{ item }}' state: present loop: "{{ users }}" # 循环这个变量 [root@localhost httpd]# ansible-playbook test.yml PLAY [all] ***************************************************************************************************** TASK [create {{ item }}] *************************************************************************************** ok: [web01.example.com] => (item=user1) # 成功创建 ok: [web01.example.com] => (item=user2) ok: [web01.example.com] => (item=user3) PLAY RECAP ***************************************************************************************************** web01.example.com : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
1.1.2 循环散列或字典列表
loop列表不需要是简单值列表。在以下示例中,列表中的每个项实际上是散列或字典。示例中的每个散列或字典具有两个键,即name和groups,当前item循环变量中每个键的值可以分别通过item.name和item.groups变量来检索。
--- - hosts: all gather_facts: no tasks: - name: create {{ item }} user: name: '{{ item.name }}' # 取用户名字 uid: '{{ item.uid }}' # 取uid 值比较多就要item后面加.加上值 state: present loop: - name: user10 uid: 2000 - name: user20 uid: 2500 [root@localhost httpd]# ansible-playbook test.yml PLAY [all] ***************************************************************************************************** TASK [create {{ item }}] *************************************************************************************** changed: [web01.example.com] => (item={'name': 'user10', 'uid': 2000}) changed: [web01.example.com] => (item={'name': 'user20', 'uid': 2500}) # 用户和uid都创建成功 PLAY RECAP ***************************************************************************************************** web01.example.com : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@web01 facts.d]# id user10 # 受管主机查看 uid=2000(user10) gid=2000(user10) 组=2000(user10) [root@web01 facts.d]# id user20 uid=2500(user20) gid=2500(user20) 组=2500(user20)
这一示例中结果是用户jane存在且为组wheel的成员,并且用户joe存在且为组root的成员。
1.1.3 较早样式的循环关键字
在Ansible2.5之前,大多数playbook使用不同的循环语法。提供了多个循环关键字,前缀为whth_,后面跟Ansible查找插件的名称。这种循环语法在现有playbook中很常见,但在将来的某个时候可能会被弃用。
较早样式的Ansible循环
循环关键字 | 描述 |
---|---|
with_items | 行为与简单列表的loop关键字相同,例如字符串列表或散列/字典列表。 但与loop不同的是,如果为with_items提供了列表的列表, 它们将被扁平化为单级列表。循环变量item保存每次迭代过程中使用的列表项。 |
with_file | 此关键字需要控制节点文件名列表。循环变量item在每次迭代过程中保存文件列表中相应文件的内容。 |
with_sequence | 此关键字不需要列表,而是需要参数来根据数字序列生成值列表。 循环变量item在每次迭代过程中保存生成的序列中的一个生成项的值。 |
playbook中的with_items的示例如下所示:
--- - hosts: all vars: # 定义变量 users: - user10 - user20 gather_facts: no tasks: - name: delete user # 删除用户 user: name: '{{ item }}' state: absent # 状态删除 with_items: - "{{ users }}" [root@localhost httpd]# vim test.yml [root@localhost httpd]# ansible-playbook test.yml PLAY [all] ***************************************************************************************************** TASK [delete user] ********************************************************************************************* changed: [web01.example.com] => (item=user10) changed: [web01.example.com] => (item=user20) PLAY RECAP ***************************************************************************************************** web01.example.com : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@web01 ~]# id user10 id: “user10”:无此用户 [root@web01 ~]# id user20 id: “user20”:无此用户
从Ansible2.5开始,建议使用loop关键字编写循环。
1.1.4 将Register变量与Loop一起使用
register关键字也可以捕获循环任务的输出。以下代码片段显示了循环任务中register变量的结构:
--- - hosts: all gather_facts: no tasks: - name: print infomation shell: "echo This is my item: {{ item }}" # 打印 loop: #循环 - one # 打印这两个 - two [root@localhost httpd]# ansible-playbook test.yml PLAY [all] ***************************************************************************************************** TASK [print infomation] **************************************************************************************** changed: [web01.example.com] => (item=one) changed: [web01.example.com] => (item=two) #执行成功,但是看不到 PLAY RECAP ***************************************************************************************************** web01.example.com : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@localhost httpd]# vim test.yml --- - hosts: all gather_facts: no tasks: - name: print infomation shell: "echo This is my item: {{ item }} >> /tmp/abc" # 如果想看到效果可以打印到/tmp/abc里面去,但是是要追加的方式 loop: - one - two [root@web01 ~]# cat /tmp/abc # 受管主机查看 This is an example of a long string, that will become a single sentence once folded. This is my item: one This is my item: two --- - hosts: all gather_facts: no tasks: - name: print infomation shell: "echo This is my item: {{ item }}" loop: - one - two register: result # 打印的结果注册成一个变量叫result - debug: var: result # 用debug模块打印这个变量 [root@localhost httpd]# ansible-playbook test.yml PLAY [all] ***************************************************************************************************** TASK [print infomation] **************************************************************************************** changed: [web01.example.com] => (item=one) changed: [web01.example.com] => (item=two) TASK [debug] *************************************************************************************************** ok: [web01.example.com] => { "result": { "changed": true, "msg": "All items completed", "results": [ { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "ansible_loop_var": "item", "changed": true, "cmd": "echo This is my item: one", "delta": "0:00:00.005557", "end": "2022-06-07 23:08:21.440667", "failed": false, "invocation": { "module_args": { "_raw_params": "echo This is my item: one", # 打印结果 "_uses_shell": true, "argv": null, "chdir": null, "creates": null, "executable": null, "removes": null, "stdin": null, "stdin_add_newline": true, "strip_empty_ends": true, "warn": false } }, "item": "one", "rc": 0, "start": "2022-06-07 23:08:21.435110", "stderr": "", "stderr_lines": [], "stdout": "This is my item: one", "stdout_lines": [ "This is my item: one" ] }, { "ansible_loop_var": "item", "changed": true, "cmd": "echo This is my item: two", "delta": "0:00:00.004377", "end": "2022-06-07 23:08:22.065807", "failed": false, "invocation": { "module_args": { "_raw_params": "echo This is my item: two", # 打印结果2 "_uses_shell": true, "argv": null, "chdir": null, "creates": null, "executable": null, "removes": null, "stdin": null, "stdin_add_newline": true, "strip_empty_ends": true, "warn": false } }, "item": "two", "rc": 0, "start": "2022-06-07 23:08:22.061430", "stderr": "", "stderr_lines": [], "stdout": "This is my item: two", "stdout_lines": [ "This is my item: two" ] } ] } } PLAY RECAP ***************************************************************************************************** web01.example.com : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
在上面的例子中,results键包含一个列表。在下面,修改了playbook,使第二个任务迭代此列表:
--- - hosts: all gather_facts: no tasks: - name: print infomation shell: "echo This is my item: {{ item }}" loop: - one - two register: result # 是上面打印出来的整体 - name: test debug: msg: "{{ item.stdout }}" # 打印stdout对应那句话 loop: "{{ result['results'] }}" # 循环result整体里面的results列表 [root@localhost httpd]# ansible-playbook test.yml PLAY [all] ***************************************************************************************************** TASK [print infomation] **************************************************************************************** changed: [web01.example.com] => (item=one) changed: [web01.example.com] => (item=two) TASK [test] **************************************************************************************************** ok: [web01.example.com] => (item={'cmd': 'echo This is my item: one', 'stdout': 'This is my item: one', 'stderr': '', 'rc': 0, 'start': '2022-06-07 23:27:42.609575', 'end': '2022-06-07 23:27:42.613838', 'delta': '0:00:00.004263', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'echo This is my item: one', '_uses_shell': True, 'warn': False, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['This is my item: one'], 'stderr_lines': [], 'ansible_facts': {'discovered_interpreter_python': '/usr/libexec/platform-python'}, 'failed': False, 'item': 'one', 'ansible_loop_var': 'item'}) => { "msg": "This is my item: one" # 第1个 } ok: [web01.example.com] => (item={'cmd': 'echo This is my item: two', 'stdout': 'This is my item: two', 'stderr': '', 'rc': 0, 'start': '2022-06-07 23:27:43.325210', 'end': '2022-06-07 23:27:43.329232', 'delta': '0:00:00.004022', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'echo This is my item: two', '_uses_shell': True, 'warn': False, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['This is my item: two'], 'stderr_lines': [], 'failed': False, 'item': 'two', 'ansible_loop_var': 'item'}) => { "msg": "This is my item: two" # 第2个 } PLAY RECAP ***************************************************************************************************** web01.example.com : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
1.2 有条件地运行任务
Ansible可使用conditionals在符合特定条件时执行任务或play。例如,可以利用一个条件在Ansible安装或配置服务前确定受管主机上的可用内存。
我们可以利用条件来区分不同的受管主机,并根据它们所符合的条件来分配功能角色。Playbook变量、注册的变量和Ansible事实都可通过条件来进行测试。可以使用比较字符串、数字数据和布尔值的运算符。
以下场景说明了在Ansible中使用条件的情况:
- 可以在变量中定义硬限制(如min_memory)并将它与受管主机上的可用内存进行比较。
- Ansible可以捕获并评估命令的输出,以确定某一任务在执行进一步操作前是否已经完成。例如,如果某一程序失败,则将路过批处理。
- 可以利用Ansible事实来确定受管主机网络配置,并决定要发送的模板文件(如,网络绑定或中继)。
- 可以评估CPU的数量,来确定如何正确调节某一Web服务器。
- 将注册的变量与预定义的变量进行比较,以确定服务是否已更改。例如,测试服务配置文件的MD5检验以和查看服务是否已更改。
1.2.1 条件任务语法
when语句用于有条件地运行任务。它取要测试的条件为值。如果条件满足,则运行任务。如果条件不满足,则跳过任务。
可以测试的一个最简单条件是某一布尔变量是True还是False。以下示例中的when语句导致任务仅在run_my_task为True时运行:
[root@localhost httpd]# vim test.yml --- - hosts: all vars: # 设置一个变量 power: false # 为假 gather_facts: no tasks: - name: print infomation dnf: name: httpd # 删除http state: absent when: power # 前提条件是设置的变量 [root@localhost httpd]# ansible-playbook test.yml # 运行 PLAY [all] ************************************************************************************ TASK [print infomation] *********************************************************************** skipping: [web01.example.com] # 跳过了,没执行,应为为假不执行 PLAY RECAP ************************************************************************************ web01.example.com : ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 --- - hosts: all vars: power: True # 改为真 gather_facts: no tasks: - name: print infomation dnf: name: httpd state: absent when: power [root@localhost httpd]# ansible-playbook test.yml PLAY [all] ************************************************************************************ TASK [print infomation] *********************************************************************** changed: [web01.example.com] # # 改为真执行了, PLAY RECAP ************************************************************************************ web01.example.com : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
以下示例测试my_service变量是否具有值。若有值,则将my_service的值用作要安装的软件包的名称。如果未定义my_service变量,则跳过任务且不显示错误。
[root@localhost httpd]# vim test.yml --- - hosts: all vars: asdfasdf: httpd # 定义变量 gather_facts: no tasks: - name: install packages dnf: name: "{{ asdfasdf }}" state: present when: asdfasdf is not defined #条件判断没有定义就执行,定义了就不装 [root@localhost httpd]# ansible-playbook -C test.yml PLAY [all] ************************************************************************************ TASK [install packages] *********************************************************************** skipping: [web01.example.com] # 直接跳过了,条件不成立 PLAY RECAP ************************************************************************************ web01.example.com : ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
下表显示了在处理条件时可使用的一些运算:
示例条件
操作 | 示例 |
---|---|
等于(值为字符串) | 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 |
[root@localhost httpd]# vim test.yml --- - hosts: all vars: # 定义变量 asdfasdf: # 列表里的内容 - 123 - 456 - 789 gather_facts: no tasks: - name: install packages debug: msg: "hello world" when: 123 in asdfasdf # 条件判断,如果123在asdfasdf列表里就打印,反之不打印 [root@localhost httpd]# ansible-playbook -C test.yml PLAY [all] ************************************************************************************ TASK [install packages] *********************************************************************** ok: [web01.example.com] => { "msg": "hello world" } PLAY RECAP ************************************************************************************ web01.example.com : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
上表中的最后一个条目初看起来有些令人困惑。下例演示了它的工作原理。
在示例中,ansible_distribution变量是在Gathering Facts任务期间确定的事实,用于标识托管主机的操作系统分支。变量supported_distros由playbook创建,包含该playbook支持的操作系统分发列表。如果ansible_distribution的值在supported_distros列表中,则条件通过且任务运行。
--- - name: Demonstrale the "in" keyword hosts: 172.16.103.129 gather_facts: yes vars: supported_distros: # 定义变量必须在这两个系统里运行 - RedHat - Fedora tasks: - name: Install httpd using yum, where supported yum: # 安装软件 name: httpd state: present when: ansible_distribution in supported_distros # 条件任务满足时才运行
注意when语句的缩进。由于when语句不是模块变量,它必须通过缩进到任务的最高级别,放置在模块的外面。
任务是YAML散列/字典,when语句只是任务中的又一个键,就如任务的名称以及它所使用的模块一样。通常的惯例是将可能存在的任何when关键字放在任务名称和模块(及模块参数)的后面。
1.2.2 测试多个条件
一个when语句可用于评估多个条件。使用and和or关键字组合条件,并使用括号分组条件。
如果任一条件为真时满足条件语句,则应当使用or语句。例如,如果计算机上运行的是红帽企业linux或Fedora,则下述条件得到满足:
when: ansible_distribution == "Redhat" or ansible_distribution == "Fedora"
使用and运算时,两个条件都必须为真,才能满足整个条件语句。例如,如果远程主机是红帽企业Linux7.5主机,并且安装的内核是指定版本,则将满足以下条件:
when: ansible_distribution_version == "7.5" and ansible_kernel == "3.10.0-327.el7.x86_64"
when关键字还支持使用列表来描述条件列表。向when关键字提供列表时,将使用and运算组合所有条件。下面的示例演示了使用and运算符组合多个条件语句的另一方式:
when: - ansible_distribution_version == "7.5" - ansible_kernel == "3.10.0-327.el7.x86_64"
这种格式提高了可读性,而可读性是良好编写Ansible Playbook的关键目标。
通过使用括号分组条件,可以表达更复杂的条件语句。例如,如果计算机上运行的是红帽企业Linux7或Fedora28,则下述条件语句得到满足。此示例使用大于字符,这样长条件就可以在playbook中分成多行,以便于阅读。
when: > ( ansible_distribution == "Redhat" and ansible_distribution_major_version == "7" ) or ( ansible_distribution == "Fedora" and ansible_distribution_major_version == "28" )
1.3 组合循环和有条件任务
循环和条件可以组合使用。
在下例中,yum模块将安装mariadb-server软件包,只要/上挂载的文件系统具有超过300MB的可用空间。ansible_mounts事实是一组字典,各自代表一个已挂载文件系统的相关事实。循环迭代列表中每一字典,只有找到了代表两个条件都为真的已挂载文件系统的字典时,条件语句才得到满足。
- name: install mariadb-server if enough space on root yum: name: mariadb-server state: latest loop: "{{ ansible_mounts }}" # 循环 when: item.mount == "/" and item.size_available > 300000000 # 挂载点等于/就是要看/分区并且可用大小大于300000000
对某个任务结合使用when和loop时,将对每个项检查when语句。
下面是组合使用条件和注册变量的另一个示例。
--- - name: Restart HTTPD if Postfix is Running hosts: 172.16.103.129 tasks: - name: Get Postfix server status command: /usr/bin/systemctl is-active postfix # Postfix是否在运行? ignore_errors: yes # 如果没有运行,直接跳过 register: result # 将模块的结果信息保存在名为result的变量中 - name: Restart Apache HTTPD based on Postfix status service: name: httpd state: restarted # result的变量rc等于0 就重启apache when: result.rc == 0
2. 实施处理程序
2.1 ansible处理程序
Ansible模块设计为具有幂等性。这表示,在正确编写的playbook中,playbook及其任务可以运行多次而不会改变受管主机,除非需要进行更改使受管主机进入所需的状态。
但在时候,在任务确实更改系统时,可能需要运行进一步的任务。例如,更改服务配置文件时可能要求重新加载该服务以便使其更改的配置生效。
处理程序是响应由其他任务触发的通知的任务。仅当任务在受管主机上更改了某些内容时,任务才通知其处理程序。每个处理程序具有全局唯一的名称,在playbook中任务块的末尾触发。如果没有任务通过名称通知处理程序,处理程序就不会运行。如果一个或多个任务通知处理程序,处理程序就会在play中的所有其他任务完成后运行一次。因为处理程序就是任务,所以可以在处理程序中使用他们将用于任何其他任务的模块。通常而言,处理程序被用于重新引导主机和重启服务。
处理程序可视为非活动任务,只有在使用notify语句显式调用时才会被触发。在下列代码片段中,只有配置文件更新并且通知了该任务,restart apache处理程序才会重启Apache服务器:
tasks: - name: copy demo.example.conf configuratioon template # 通知处理程序的任务 template: src: /var/lib/templates/demo.example.conf.template dest: /etc/httpd/conf.d/demo.example.conf notify: # notify语句,发生改变就触发,都是ok就不触发 - restart apache # 要运行的处理程序的名称 handlers: # handlers关键字表示处理程序任务列表的开头 - name: restart apache # 被任务调用的处理程序的名称 service: # 用于该处理程序的模块 name: httpd state: restarted
在上面的例子中,restart apache处理程序只有在template任务通知已发生更改时才会触发。一个任务可以在其notify部分中调用多个处理程序。Ansible将notify语句视为数组,并且迭代处理程序名称:
tasks: - name: copy demo.example.conf configuration template template: src: /var/lib/templates/demo.exammple.conf.template dest: /etc/httpd/conf.d/demo.example.conf notify: - restart mysql - restart apache handlers: - name: restart mysql service: name: mariadb state: restarted - name: restart apache service: name: httpd state: restarted
2.2 使用处理程序的好处
使用处理程序时需要牢记几个重要事项:
- 处理程序始终按照play的handlers部分指定的顺序运行。它们不按在任务中由notify语句列出的顺序运行,或按任务通知它们的顺序运行。
- 处理程序通常在相关play中的所有其他任务完成后运行。playbook的tasks部分中某一任务调用的处理程序,将等到tasks下的所有任务都已处理后才会运行。
- 处理程序名称存在于各play命名空间中。如果两个处理程序被错误地给予相同的名称,则仅会运行一个。
- 即使有多个任务通知处理程序,该处理程序依然仅运行一次。如果没有任务通知处理程序,它就不会运行。
- 如果包含notify语句的任务没有报告changed结果(例如,软件包已安装并且任务报告ok),则处理程序不会获得通知。处理程序将被跳过,直到有其他任务通知它。只有相关任务报告了changed状态,Ansible才会通知处理程序。
处理程序用于在任务对受管主机进行更改时执行额外操作。它们不应用作正常任务的替代。
3. 处理任务失败
3.1 管理play中的任务错误
Ansible评估任务的返回代码,从而确定任务是成功还是失败。通常而言,当任务失败时,Ansible将立即在该主机上中止play的其余部分并且跳过所有后续任务。
但有些时候,可能希望即使在任务失败时也继续执行play。例如,或许预期待定任务有可能会失败,并且希望通过有条件地运行某项其他任务来修复。
Ansible有多种功能可用于管理任务错误。
3.2 忽略任务失败
默认情况下,任务失败时play会中止。不过,可以通过忽略失败的任务来覆盖此行为。可以在任务中使用ignore_errors关键字来实现此目的。
下列代码片段演示了如何在任务中使用ignore_errors,以便在任务失败时也继续在主机上执行playbook。例如,如果notapkg软件包不存在,则yum模块将失败,但若将ignore_errors设为yes,则执行将继续。
[root@localhost httpd]# vim test1.yml --- - hosts: all gather_facts: no tasks: - name: install a package # 第一个任务安装一个不存在的包 dnf: name: asdfasdf state: present - name: print info # 第二个任务打印以下内容 debug: msg: | Hello, How are you How old are you. [root@localhost httpd]# ansible-playbook test1.yml PLAY [all] ************************************************************************************ TASK [install a package] ********************************************************************** fatal: [web01.example.com]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"}, "changed": false, "failures": ["asdfasdf 未找到匹配的参数: asdfasdf"], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []} # 报错说没有那个包,第二个打印的任务也没有执行了 PLAY RECAP ************************************************************************************ web01.example.com : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 --- - hosts: all gather_facts: no ignore_errors: yes # 加上跳过错误 tasks: - name: install a package dnf: name: asdfasdf state: present - name: print info debug: msg: | Hello, How are you How old are you. [root@localhost httpd]# ansible-playbook test1.yml PLAY [all] ************************************************************************************ TASK [install a package] ********************************************************************** fatal: [web01.example.com]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"}, "changed": false, "failures": ["asdfasdf 未找到匹配的参数: asdfasdf"], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []} ...ignoring # 报错了但是跳过 TASK [print info] ***************************************************************************** ok: [web01.example.com] => { "msg": "Hello, How are you\nHow old are you.\n" # 执行了第二个任务 } PLAY RECAP ************************************************************************************ web01.example.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
3.3 任务失败后强制执行处理程序
通常而言,如果任务失败并且play在该主机上中止,则收到play中早前任务通知的处理程序将不会运行。如果在play中设置force_handlers: yes关键字,则即使play因为后续任务失败而中止也会调用被通知的处理程序。
下列代码片段演示了如何在play中使用force_handlers关键字,以便在任务失败时也强制执行相应的处理程序:
--- - hosts: all gather_facts: no tasks: - name: print info # 打印以下内容 debug: msg: | Hello, How are you How old are you. notify: # 触发 - restart the apache # 重启apache - name: install a package # 设置的一个错误任务 dnf: name: asdfasdf state: present handlers: # 处理程序 - name: restart the apache service: name: httpd state: restarted [root@localhost httpd]# ansible-playbook test1.yml PLAY [all] ************************************************************************************ TASK [print info] ***************************************************************************** ok: [web01.example.com] => { "msg": "Hello, How are you\nHow old are you.\n" # 第一个任务执行成功 } TASK [install a package] ********************************************************************** fatal: [web01.example.com]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"}, "changed": false, "failures": ["asdfasdf 未找到匹配的参数: asdfasdf"], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []} # 第二个任务失败,所以没有执行handlers PLAY RECAP ************************************************************************************ web01.example.com : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 [root@localhost httpd]# vim test1.yml --- - hosts: all force_handlers: yes # 加上强制执行handlers gather_facts: no tasks: - name: print info command: echo "hello world" #换一给任务,刚才那个已经执行过,由于幂等性不具备触发条件 notify: - restart the apache - name: install a package dnf: name: asdfasdf state: present handlers: - name: restart the apache service: name: httpd state: restarted [root@localhost httpd]# ansible-playbook test1.yml PLAY [all] ************************************************************************************ TASK [print info] ***************************************************************************** changed: [web01.example.com] TASK [install a package] ********************************************************************** fatal: [web01.example.com]: FAILED! => {"changed": false, "failures": ["asdfasdf 未找到匹配的参数: asdfasdf"], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []} #任务失败 RUNNING HANDLER [restart the apache] ********************************************************** changed: [web01.example.com] # 还是执行了handlers PLAY RECAP ************************************************************************************ web01.example.com : ok=2 changed=2 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
请记住,处理程序会在任务报告changed结果时获得通知,而在任务报告ok或failed结果时不会获得通知。
3.4 指定任务失败条件
可以在任务中使用failed_when关键字来指定表示任务已失败的条件。这通常与命令模块搭配使用,这些模块可能成功执行了某一命令,但命令的输出可能指示了失败。
例如,可以运行输出错误消息的脚本,并使用该消息定义任务的失败状态。下列代码片段演示了如何在任务中使用failed_when关键字:
tasks: - name: Run user creation script # 运行一个脚本 shell: /usr/local/bin/create_users.sh # 一个sh的脚本 register: command_result # 把脚本的结果注册成一个变量 failed_when: "'Password missing' in command_result.stdout" # 看脚本里面有没有任务失败,来定义这个脚本成功或者失败 [root@web01 ~]# vi test.sh # 随便写一个脚本 #!/bin/bash ls date pwd cd sdadasfads # 错误的任务其他的都是正确的 pwd echo $PATH echo hehe [root@web01 ~]# chmod +x test.sh # 给执行权限 [root@web01 ~]# ls anaconda-ks.cfg test.sh [root@web01 ~]# ./test.sh #执行 anaconda-ks.cfg test.sh 2022年 06月 12日 星期日 01:23:19 CST /root ./test.sh:行7: sdadasfads: 未找到命令 /root /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin hehe [root@web01 ~]# echo $? # 有错误的任务但是还是显示成功了,它只一脚本最后一个任务的结果为结果,其实这个脚本时有问题的 0
fail模块也可用于强制任务失败。上面的场景也可以编写为两个任务:
tasks: - name: Run user creation script shell: /usr/local/bin/create_users.sh # 执行脚本 register: command_result # 注册结果 ignore_errors: yes # 忽略错误 - name: Report script failure # 打印 fail: msg: "The password is missing in the output" # 打印的信息 when: "'Password missing' in command_result.stdout" # 结果里有Password missing的时候打印
我们可以使用fail模块为任务提供明确的失败消息。此方法还支持延迟失败,允许在运行中间任务以完成或回滚其他更改。
3.5 指定何时任务报告 “Changed” 结果
当任务对托管主机进行了更改时,会报告 changed 状态并通知处理程序。如果任务不需要进行更改,则会报告ok并且不通知处理程序。
changed_when关键字可用于控制任务在何时报告它已进行了更改。例如,下一示例中的shell模块将用于获取供后续任务使用的Kerberos凭据。它通常会在运行时始终报告changed。为抵制这种更改,应设置changed_when: false,以便它仅报告ok或failed。
- name: get Kerberos credentials as "admin" # 执行这个任务 shell: echo "{{ krb_admin_pass }}" | kinit -f admin changed_when: false # 设置changed_when: false,任务不会包好changed,只会报告ok或failed
以下示例使用shell模块,根据通过已注册变量收集的模块的输出来报告changed:
tasks: - shell: cmd: /usr/local/bin/upgrade-database # 用shell模块执行这个任务 register: command_result changed_when: "'Success' in command_result.stdout" # 里面有Success说明执行成功,有改变 notify: - restart_database # 就执行这个 handlers: # 反之没有,处理程序就不运行 - name: restart_database service: name: mariadb state: restarted
3.6 Ansible块和错误处理
在playbook中,块是对任务进行逻辑分组的子句,可用于控制任务的执行方式。例如,任务块可以含有when关键字,以将某一条件应用到多个任务:
- name: block example hosts: 172.16.103.129 tasks: - name: installing and configuring Yum versionlock plugin block: # 块 - name: package needed by yum yum: name: yum-plugin-versionlock # 安装软件包 state: present - name: lock version of tadata lineinfile: dest: /etc/yum/pluginconf.d/versionlock.list # 确保有这一行 line: tzdata-2020j-1 state: present # 要在红帽里执行 when: ansible_distribution == "Redhat" # when和block平级,它定义上面两个任务,如果没有block两个任务就要定义两个when
通过块,也可结合rescue和always语句来处理错误。如果块中的任何任务失败,则执行其rescue块中的任务来进行恢复。在block子句中的任务以及rescue子句中的任务(如果出现故障)运行之后,always子句中的任务运行。总结:
- block:定义要运行的主要任务
- rescue:定义要在block子句中定义的任务失败时运行的任务
- always:定义始终都独立运行的任务,不论block和rescue子句中定义的任务是成功还是失败
以下示例演示了如何在playbook中实施块。即使block子句中定义的任务失败,rescue和always子句中定义的任务也会执行。
tasks: - name: Upgrade DB block: - name: upgrade the database shell: cmd: /usr/local/lib/upgrade-database rescue: # 如果block的任务失败rescue就执行,成功就rescue不执行,always不论上面两个成功与否都执行 - name: revert the database upgrade shell: cmd: /usr/local/lib/revert-database always: - name: always restart the database service: name: mariadb state: restarted
block中的when条件也会应用到其rescue和always子句(若存在)。
标签:06,实施,changed,item,任务,web01,example,name 来源: https://www.cnblogs.com/sunyiming023654/p/16350341.html