实施任务控制
1.编写循环和条件任务
1.1 利用循环迭代任务
通过利用循环,我们无需编写多个使用同一模块的任务。
Ansible支持使用loop关键字对一组项目迭代任务。可以配置循环以利用列表中的各个项目、列表中各个文件的内容、生成的数字序列或更为复杂的结构来重复任务。
1.2 简单循环
简单循环对一组项目迭代任务。loop关键字添加到任务中,将应对其迭代任务的项目列表取为值。循环变量item保存每个迭代过程中使用的值。
playbook中使用循环:
---
- name: loop
hosts: all
gather_facts: no
tasks:
- name: 启动服务
service:
name: "{{ item }}"
state: started
loop:
- httpd
- mariadb
使用变量方式编写:
---
- name: 构建lamp
hosts: 192.168.237.168
gather_facts: no
vars_files: //指定变量文件位置
- /etc/ansible/playbook/vars/service_vars.yml
tasks:
- name: "{{ install_service }}" //引用变量
yum:
name: "{{ item }}" //循环
state: present
loop: "{{ install_service }}"
- name: "{{ start_service }}"
service:
name: "{{ item }}" //循环
state: started
enabled: yes
loop: "{{ start_service }}"
1.3 循环散列或字典列表
loop列表不需要是简单值列表。在以下示例中,列表中的每个项实际上是散列或字典。示例中的每个散列或字典具有两个键,即name和groups,当前item循环变量中每个键的值可以分别通过item.name和item.groups变量来检索。
---
- name: loop
hosts: all
gather_facts: no
tasks:
- name: create user
user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
loop:
- name: akl
group: za
- name: weien
group: cx
[root@localhost ansible]# ansible-playbook playbook/loop.yml
PLAY [loop] *********************************************************************************************
TASK [create user] **************************************************************************************
changed: [localhost] => (item={'name': 'akl', 'groups': 'za'})
changed: [localhost] => (item={'name': 'weien', 'groups': 'cx'})
PLAY RECAP **********************************************************************************************
localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
1.4 较早样式的循环关键字
在Ansible2.5之前,大多数playbook使用不同的循环语法。提供了多个循环关键字,前缀为whth_,后面跟Ansible查找插件的名称。
较早样式的Ansible循环
循环关键字 | 描述 |
---|---|
with_items | 行为与简单列表的loop关键字相同,例如字符串列表或散列/字典列表。 但与loop不同的是,如果为with_items提供了列表的列表,它们将被扁平化为单级列表。循环变量item保存每次迭代过程中使用的列表项。 |
with_file | 此关键字需要控制节点文件名列表。循环变量item在每次迭代过程中保存文件列表中相应文件的内容。 |
with_sequence | 此关键字不需要列表,而是需要参数来根据数字序列生成值列表。 循环变量item在每次迭代过程中保存生成的序列中的一个生成项的值。 |
示例:
vars:
data:
- user0
- user1
- user2
tasks:
- name: "with_items"
debug:
msg: "{{ item }}"
with_items: "{{ data }}"
1.5 将Register变量与Loop一起使用
register关键字也可以捕获循环任务的输出。以下代码片段显示了循环任务中register变量的结构:
---
- name: Loop Register Test
gather_facts: no
hosts: all
tasks:
- name: Looping Echo Task
shell: "echo This is my item: {{ item }}"
loop:
- one
- two
register: echo_results # 注册echo_results变量
- name: Show echo_results variable //显示变量echo_results
debug:
var: echo_results # echo_results变量的内容显示在屏幕上
[root@localhost ansible]# ansible-playbook loop.yml
PLAY [Loop Register Test] *******************************************************************************
TASK [Looping Echo Task] ********************************************************************************
changed: [localhost] => (item=one)
changed: [localhost] => (item=two)
......
}
},
"item": "one",
"rc": 0,
"start": "2021-07-27 08:40:00.624946",
"stderr": "",
"stderr_lines": [],
"stdout": "This is my item: one",
"stdout_lines": [
"This is my item: one"
......
"item": "two",
"rc": 0,
"start": "2021-07-27 08:40:00.946702",
"stderr": "",
"stderr_lines": [],
"stdout": "This is my item: two",
"stdout_lines": [
"This is my item: two"
}
}
PLAY RECAP **********************************************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
在上面的例子中,results键包含一个列表。在下面,修改了playbook,使第二个任务迭代此列表:
---
- name: Loop Register Test
gather_facts: no
hosts: all
tasks:
- name: Looping Echo Task
shell: "echo This is my item: {{ item }}"
loop:
- one
- two
register: echo_results
- name: Show stdout from the previous task.
debug:
msg: "STDOUT from previous task: {{ item.stdout }}"
loop: "{{ echo_results['results'] }}"
[root@localhost ansible]# ansible-playbook loop_register.yml
PLAY [Loop Register Test] *******************************************************************************
TASK [Looping Echo Task] ********************************************************************************
changed: [192.168.237.168] => (item=one)
changed: [192.168.237.168] => (item=two)
TASK [Show stdout from the previous task.] **************************************************************
ok: [192.168.237.168] => (item={'cmd': 'echo This is my item: one', 'stdout': 'This is my item: one'......
'item': 'one', 'ansible_loop_var': 'item'}) => {
"msg": "STDOUT from previous task: This is my item: one"
}
ok: [192.168.237.168] => (item={'cmd': 'echo This is my item: two', 'stdout': 'This is my item: two'......
'item': 'two', 'ansible_loop_var': 'item'}) => {
"msg": "STDOUT from previous task: This is my item: two"
}
PLAY RECAP **********************************************************************************************
192.168.237.168 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0
2.有条件地运行任务
Ansible可使用conditionals在符合特定条件时执行任务或play。例如,可以利用一个条件在Ansible安装或配置服务前确定受管主机上的可用内存。
以下场景说明了在Ansible中使用条件的情况:
- 可以在变量中定义硬限制(如min_memory)并将它与受管主机上的可用内存进行比较。
- Ansible可以捕获并评估命令的输出,以确定某一任务在执行进一步操作前是否已经完成。例如,如果某一程序失败,则将路过批处理。
- 可以利用Ansible事实来确定受管主机网络配置,并决定要发送的模板文件(如,网络绑定或中继)。
- 可以评估CPU的数量,来确定如何正确调节某一Web服务器。
- 将注册的变量与预定义的变量进行比较,以确定服务是否已更改。例如,测试服务配置文件的MD5检验以和查看服务是否已更改。
2.1 条件任务语法
when语句用于有条件地运行任务。它取要测试的条件为值。如果条件满足,则运行任务。如果条件不满足,则跳过任务。
可以测试的一个最简单条件是某一布尔变量是True还是False。以下示例中的when语句导致任务仅在test_task为True时运行:
---
- name: Simple Boolean Task Demo
hosts: all
vars:
test_task: true //定义变量为真 true
tasks:
- name: install httpd
yum:
name: httpd
state: present
when: test_task //如果变量为真就卸载,如果不为真就跳过
以下示例测试test_service变量是否具有值。若有值,则将test_service的值用作要安装的软件包的名称。如果未定义test_service变量,则跳过任务且不显示错误。
---
- name: Simple Boolean Task Demo
hosts: "*"
vars:
test_service: httpd //如果没定义变量,则会直接跳过任务
tasks:
- name: "{{ test_server }} package is installed"
yum:
name: "{{ test_server }}"
state: present
when: test_service is 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变量是在Gathering Facts任务期间确定的事实,用于标识托管主机的操作系统分支。变量supported_distros由playbook创建,包含该playbook支持的操作系统分发列表。如果ansible_distribution的值在supported_distros列表中,则条件通过且任务运行。
---
- name: Demonstrale the "in" keyword
hosts: all
vars:
supported_distros: //定义变量包含的操作系统列表
- redHat //区分大小写,所以此任务会跳过
- CentOS
tasks:
- name: Install httpd using yum, where supported
yum:
name: httpd
state: present
when: ansible_distribution in supported_distros //判断事实中的操作系统是否在定义的变量supported_distros中,是则安装httpd,否则跳过
//运行playbook
[root@localhost ansible]# ansible-playbook test.yml
PLAY [Demonstrale the "in" keyword] *********************************************************************
TASK [Gathering Facts] **********************************************************************************
ok: [192.168.237.168]
TASK [Install httpd using yum, where supported] *********************************************************
skipping: [192.168.237.168] //跳过
PLAY RECAP **********************************************************************************************
192.168.237.168 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
//将RedHat改回,在运行则成功安装服务
[root@localhost ansible]# ansible-playbook test.yml
PLAY [Demonstrale the "in" keyword] *********************************************************************
TASK [Gathering Facts] **********************************************************************************
ok: [192.168.237.168]
TASK [Install httpd using yum, where supported] *********************************************************
ok: [192.168.237.168]
PLAY RECAP **********************************************************************************************
192.168.237.168 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
注意when语句的缩进。由于when语句不是模块变量,它必须通过缩进到任务的最高级别,放置在模块的外面。
2.2 测试多个条件
一个when语句可用于评估多个条件。使用and和or关键字组合条件,并使用括号分组条件。
or:任意条件为真满足条件语句
//计算机上运行的系统是RedHat或者CentOS则满足条件语句
when: ansible_distribution == "RedHat" or ansible_distribution == "CentOS"
and:两个条件都为真则满足条件
//系统为红帽企业Linux7.5,并且安装的内核是指定版本,则满足条件语句
when: ansible_distribution_version == "7.5" and ansible_kernel == "3.10.0-327.el7.x86_64"
when关键字还支持使用列表来描述条件列表。向when关键字提供列表时,将使用and运算组合所有条件。提高可读性
when:
- ansible_distribution_version == "7.5"
- ansible_kernel == "3.10.0-327.el7.x86_64"
通过使用括号分组条件,可以表达更复杂的条件语句。
例如,如果计算机上运行的是红帽企业Linux7或Fedora28,则下述条件语句得到满足。此示例使用(>)大于字符,这样长条件就可以在playbook中分成多行,以便于阅读。
when: >
( ansible_distribution == "Redhat" and
ansible_distribution_major_version == "7" )
or
( ansible_distribution == "Fedora" and
ansible_distribution_major_version == "28" )
2.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
对某个任务结合使用when和loop时,将对每个项检查when语句。
3.实施处理程序
3.1 ansible处理程序
Ansible模块设计为具有幂等性。这表示,在正确编写的playbook中,playbook及其任务可以运行多次而不会改变受管主机,除非需要进行更改使受管主机进入所需的状态。
处理程序可视为非活动任务,只有在使用notify
语句显式调用时才会被触发。
在下列代码片段中,只有配置文件更新并且通知了该任务,restart apache处理程序才会重启Apache服务器:
//restart apache处理程序只有在template任务通知已发生更改时才会触发
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语句指出该任务需要触发的处理程序
- restart apache # 要运行的处理程序的名称
handlers: # handlers关键字表示处理程序任务列表的开头
- name: restart apache # 被任务调用的处理程序的名称
service: # 用于该处理程序的模块
name: httpd
state: restarted
//只有在配置文件有改动的时候才会触发处理程序。
一个任务可以在其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
3.2 使用处理程序的好处
- 处理程序始终按照play的handlers部分指定的顺序运行。它们不按在任务中由notify语句列出的顺序运行,或按任务通知它们的顺序运行。
- 处理程序通常在相关play中的所有其他任务完成后运行。playbook的tasks部分中某一任务调用的处理程序,将等到tasks下的所有任务都已处理后才会运行。
- 处理程序名称存在于各play命名空间中。如果两个处理程序被错误地给予相同的名称,则仅会运行一个。
- 即使有多个任务通知处理程序,该处理程序依然仅运行一次。如果没有任务通知处理程序,它就不会运行。
- 如果包含notify语句的任务没有报告changed结果(例如,软件包已安装并且任务报告ok),则处理程序不会获得通知。处理程序将被跳过,直到有其他任务通知它。只有相关任务报告了changed状态,Ansible才会通知处理程序。
处理程序用于在任务对受管主机进行更改时执行额外操作。它们不应用作正常任务的替代。
4.处理任务失败
4.1 管理play中的任务错误
Ansible评估任务的返回代码,从而确定任务是成功还是失败。通常而言,当任务失败时,Ansible将立即在该主机上中止play的其余部分并且跳过所有后续任务。
有时候我们不想让他停止,想让它继续执行后的任务,等任务都执行玩后,回过头了再修改错误。
Ansible有多种功能可用于管理任务错误。
4.2 忽略任务失败
默认情况下,任务失败时play会中止。不过,可以通过忽略失败的任务来覆盖此行为。可以在任务中使用ignore_errors
关键字来实现此目的。
//安装一个不存在的包
- name: installed notapkg
yum:
name: notapkg
state: latest
ignore_errors: yes //忽略错误继续执行playbook
4.3 任务失败后强制执行处理程序
通常而言,如果任务失败并且play在该主机上中止,则收到play中早前任务通知的处理程序将不会运行。如果在play中设置force_handlers: yes
关键字,则即使play因为后续任务失败而中止也会调用被通知的处理程序。
---
- hosts: all
force_handlers: yes //强制执行
tasks:
- name: a task which always notifies its handler
command: /bin/true
notify: restart the database //触发处理程序,重启mariadb
- name: a task which fails because the package doesn't exist
yum:
name: notapkg //因为没有notapkg软件包,所有这里会出错
state: latest
handlers: //处理程序任务列表开始
- name: restart the database //被调用的处理程序名
service:
name: mariadb
state: restarted
//当yum模块执行失败后,继续执行处理程序。因为启动了force_handlers强制执行
4.4 指定任务失败条件
可以在任务中使用failed_when
关键字来指定表示任务已失败的条件。这通常与命令模块搭配使用,这些模块可能成功执行了某一命令,但命令的输出可能指示了失败。
例如,可以运行输出错误消息的脚本,并使用该消息定义任务的失败状态。下列代码片段演示了如何在任务中使用failed_when关键字:
tasks:
- name: Run user creation script
shell: /usr/local/bin/create_users.sh
register: command_result
failed_when: "'Password missing' in command_result.stdout"
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"
我们可以使用fail模块为任务提供明确的失败消息。此方法还支持延迟失败,允许在运行中间任务以完成或回滚其他更改。
4.5 指定何时任务报告 “Changed” 结果
当任务对受管主机进行了更改时,会报告 changed 状态并通知处理程序。如果任务不需要进行更改,则会报告ok并且不通知处理程序。
changed_when
关键字可用于控制任务在何时报告它已进行了更改。
[root@localhost ansible]# cat test.yml
---
- name: start service
hosts: all
tasks:
- name: start httpd
service:
name: httpd
state: started
changed_when: true
[root@localhost ansible]# ansible-playbook test.yml
PLAY [start service] ************************************************************************************
TASK [Gathering Facts] **********************************************************************************
ok: [192.168.237.168]
TASK [start httpd] **************************************************************************************
changed: [192.168.237.168]
PLAY RECAP **********************************************************************************************
192.168.237.168 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
//将changed_when: true改成changed_when: false,此时报的是ok
[root@localhost ansible]# ansible-playbook test.yml
PLAY [start service] ************************************************************************************
TASK [Gathering Facts] **********************************************************************************
ok: [192.168.237.168]
TASK [start httpd] **************************************************************************************
ok: [192.168.237.168]
PLAY RECAP **********************************************************************************************
192.168.237.168 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
4.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"
//如果在事实中 使用的是Redhat系统则执行block(块)中的任务
通过块,也可结合rescue和always语句来处理错误。如果块中的任何任务失败,则执行其rescue块中的任务来进行恢复。在block子句中的任务以及rescue子句中的任务(如果出现故障)运行之后,always子句中的任务运行。总结:
- block:定义要运行的主要任务
- rescue:定义要在block子句中定义的任务失败时运行的任务
- always:定义始终都独立运行的任务,不论block和rescue子句中定义的任务是成功还是失败
tasks:
- name: Upgrade DB
block: //block模块中如果存在when条件,则也会被应用到rescue和always
- name: upgrade the database
shell:
cmd: /usr/local/lib/upgrade-database //升级数据库,如果运行失败则执行rescue模块
rescue:
- name: revert the database upgrade
shell:
cmd: /usr/local/lib/revert-database
always:
- name: always restart the database
service:
name: mariadb
state: restarted