实施任务控制
1、 循环语句
通过利用循环,我们无需编写多个使用同一模块的任务。
1.1 循环任务
简单循环
[root@ansible ansible]# vim playbook/test.yml
[root@ansible ansible]# cat playbook/test.yml
---
- name: xunhuan
gather_facts: no
hosts: 192.168.47.129
tasks:
- name: 创建用户 {{ item }}
user:
name: "{{ item }}"
state: present
loop:
- wjj2
- wjj3
- wjj4
- wjj5
- wjj6
- wjj7
- wjj8
- wjj9
- wjj10
[root@ansible ansible]# ansible-playbook playbook/test.yml
PLAY [xunhuan] *****************************************************************
TASK [创建用户 {{ item }}] *********************************************************
changed: [192.168.47.129] => (item=wjj2)
changed: [192.168.47.129] => (item=wjj3)
changed: [192.168.47.129] => (item=wjj4)
changed: [192.168.47.129] => (item=wjj5)
changed: [192.168.47.129] => (item=wjj6)
changed: [192.168.47.129] => (item=wjj7)
changed: [192.168.47.129] => (item=wjj8)
changed: [192.168.47.129] => (item=wjj9)
changed: [192.168.47.129] => (item=wjj10)
PLAY RECAP *********************************************************************
192.168.47.129 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@apache ~]# id wjj2
uid=1003(wjj2) gid=1003(wjj2) 组=1003(wjj2)
[root@apache ~]# id wjj3
uid=1004(wjj3) gid=1004(wjj3) 组=1004(wjj3)
[root@apache ~]# id wjj4
uid=1005(wjj4) gid=1005(wjj4) 组=1005(wjj4)
[root@apache ~]# id wjj5
uid=1006(wjj5) gid=1006(wjj5) 组=1006(wjj5)
[root@apache ~]# id wjj6
uid=1007(wjj6) gid=1007(wjj6) 组=1007(wjj6)
[root@apache ~]# id wjj7
uid=1008(wjj7) gid=1008(wjj7) 组=1008(wjj7)
[root@apache ~]# id wjj8
uid=1009(wjj8) gid=1009(wjj8) 组=1009(wjj8)
[root@apache ~]# id wjj9
uid=1010(wjj9) gid=1010(wjj9) 组=1010(wjj9)
[root@apache ~]# id wjj10
uid=1011(wjj10) gid=1011(wjj10) 组=1011(wjj10)
将变量放入列表
[root@ansible ansible]# vi playbook/vars/user.yml
[root@ansible ansible]# cat playbook/vars/user.yml
user:
- wjj2
- wjj3
- wjj4
- wjj5
- wjj6
- wjj7
- wjj8
- wjj9
- wjj10
[root@ansible ansible]# vim playbook/test.yml
[root@ansible ansible]# cat playbook/test.yml
---
- name: xunhuan
gather_facts: no
hosts: 192.168.47.129
vars_files:
- vars/user.yml
tasks:
- name: 创建用户 {{ item }}
user:
name: "{{ item }}"
state: absent
loop: "{{ user }}"
1.2 循环散列或字典列表
[root@ansible ansible]# vim playbook/test.yml
[root@ansible ansible]# cat playbook/test.yml
---
- name: xunhuan
gather_facts: no
hosts: 192.168.47.129
vars_files:
- vars/user.yml
tasks:
- name: 创建用户 {{ item }}
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
state: present
loop:
- name: wang1
uid: 2000
- name: wang2
uid: 2050
- name: wang3
uid: 2060
[root@ansible ansible]# ansible-playbook playbook/test.yml
PLAY [xunhuan] *****************************************************************
TASK [创建用户 {{ item }}] *********************************************************
changed: [192.168.47.129] => (item={'name': 'wang1', 'uid': 2000})
changed: [192.168.47.129] => (item={'name': 'wang2', 'uid': 2050})
changed: [192.168.47.129] => (item={'name': 'wang3', 'uid': 2060})
PLAY RECAP *********************************************************************
192.168.47.129 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
将变量放入列表
[root@ansible ansible]# vi playbook/vars/user.yml
[root@ansible ansible]# cat playbook/vars/user.yml
user:
- name: wjj11
uid: 3000
- name: wjj12
uid: 2010
- name: wjj13
uid: 2020
[root@ansible ansible]# vim playbook/test.yml
[root@ansible ansible]# cat playbook/test.yml
---
- name: xunhuan
gather_facts: no
hosts: 192.168.47.129
vars_files:
- vars/user.yml
tasks:
- name: 创建用户 {{ item }}
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
state: present
loop: "{{ user }}"
[root@ansible ansible]# ansible-playbook playbook/test.yml
PLAY [xunhuan] *****************************************************************
TASK [创建用户 {{ item }}] *********************************************************
changed: [192.168.47.129] => (item={'name': 'wjj11', 'uid': 3000})
ok: [192.168.47.129] => (item={'name': 'wjj12', 'uid': 2010})
ok: [192.168.47.129] => (item={'name': 'wjj13', 'uid': 2020})
PLAY RECAP *********************************************************************
192.168.47.129 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
1.3 老样式的循环关键字
较早样式的Ansible循环
循环关键字 | 描述 |
---|---|
with_items | 行为与简单列表的loop关键字相同,例如字符串列表或散列/字典列表。 但与loop不同的是,如果为with_items提供了列表的列表,它们将被扁平化为单级列表。 循环变量item保存每次迭代过程中使用的列表项。 |
with_file | 此关键字需要控制节点文件名列表。循环变量item在每次迭代过程中保存文件列表中相应文件的内容。 |
with_sequence | 此关键字不需要列表,而是需要参数来根据数字序列生成值列表。 循环变量item在每次迭代过程中保存生成的序列中的一个生成项的值 |
[root@ansible ansible]# vim playbook/test.yml
[root@ansible ansible]# cat playbook/test.yml
---
- name: xunhuan
gather_facts: no
hosts: 192.168.47.129
vars_files:
- vars/user.yml
tasks:
- name: 创建用户 {{ item }}
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
state: absent
with_items: "{{ user }}" //早些版本的循环方式
[root@ansible ansible]# ansible-playbook playbook/test.yml
PLAY [xunhuan] *****************************************************************
TASK [创建用户 {{ item }}] *********************************************************
changed: [192.168.47.129] => (item={'name': 'wjj11', 'uid': 3000})
changed: [192.168.47.129] => (item={'name': 'wjj12', 'uid': 2010})
changed: [192.168.47.129] => (item={'name': 'wjj13', 'uid': 2020})
PLAY RECAP *********************************************************************
192.168.47.129 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
建议使用loop关键字编写循环
1.4 将注册变量(register)与loop一起使用
[root@ansible ansible]# cat playbook/wjj.yml
---
- hosts: 192.168.47.129
gather_facts: no
tasks:
- name: nini
command: "echo hello {{ item }},hello."
loop:
- lisa
- mike
- nana
register: result
- debug:
var: result
[root@ansible ansible]# ansible-playbook playbook/wjj.yml
PLAY [192.168.47.129] **********************************************************
TASK [nini] ********************************************************************
changed: [192.168.47.129] => (item=lisa)
changed: [192.168.47.129] => (item=mike)
changed: [192.168.47.129] => (item=nana)
TASK [debug] *******************************************************************
ok: [192.168.47.129] => {
"result": {
"changed": true,
"msg": "All items completed",
"results": [
2、 条件判断(任务)
任务是YAML散列/字典,when语句只是任务中的又一个键,就如任务的名称以及它所使用的模块一样。通常的惯例是将可能存在的任何when关键字放在任务名称和模块(及模块参数)的后面
2.1 条件判断使用场景
- 可以在变量中定义硬限制(如min_memory)并将它与受管主机上的可用内存进行比较。
- Ansible可以捕获并评估命令的输出,以确定某一任务在执行进一步操作前是否已经完成。例如,如果某一程序失败,则将路过批处理。
- 可以利用Ansible事实来确定受管主机网络配置,并决定要发送的模板文件(如,网络绑定或中继)。
- 可以评估CPU的数量,来确定如何正确调节某一Web服务器。
- 将注册的变量与预定义的变量进行比较,以确定服务是否已更改。例如,测试服务配置文件的MD5检验以和查看服务是否已更改。
2.2 条件判断语法
when语句用于有条件地运行任务。它取要测试的条件为值。如果条件满足,则运行任务。如果条件不满足,则跳过任务。
测试的一个最简单条件是某一布尔变量是True还是False
注:由于when语句不是模块变量,它必须通过缩进到任务的最高级别,放置在模块的外面
[root@ansible ansible]# cat playbook/test.yml
---
- hosts: 192.168.47.129
gather_facts: no
vars: //定义变量
power: true // 如果为false,则跳过任务,不执行,也不报错
tasks:
- name: wjj
yum:
name: vsftpd
state: present
when: power //查看power这个变量的值是否为真,为真就条件执行,为假(空)条件不成立则跳过
[root@ansible ansible]# ansible-playbook playbook/test.yml
PLAY [192.168.47.129] *****************************************************************************************************
TASK [wjj] *************************************************************************************************************
changed: [192.168.47.129]
PLAY RECAP ****************************************************************************************************************
192.168.47.129 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@apache ~]# rpm -qa|grep vsftpd
vsftpd-3.0.3-31.el8.x86_64
测试my_service变量是否具有值。若有值,则将my_service的值用作要安装的软件包的名称。如果未定义my_service变量,则跳过任务且不显示错误
[root@ansible ansible]# cat playbook/test.yml
---
- hosts: 192.168.47.129
gather_facts: no
vars:
tasks:
- name: wjj
yum:
name: "{{ service_name }}"
state: present
when: service_name is defined
[root@ansible ansible]# ansible-playbook playbook/test.yml
PLAY [192.168.47.129] **********************************************************
TASK [wjj] *********************************************************************
skipping: [192.168.47.129] //变量没有被定义,引用了不存在的变量,skipping意为跳过
PLAY RECAP *********************************************************************
192.168.47.129 : ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
ansible_distribution变量是在Gathering Facts任务期间确定的事实,用于标识托管主机的操作系统分支。变量supported_distros由playbook创建,包含该playbook支持的操作系统分发列表。如果ansible_distribution的值在supported_distros列表中,则条件通过且任务运行。
[root@ansible ansible]# ansible all -m setup|less //获取事实
"ansible_distribution": "RedHat",
"ansible_distribution_file_parsed": true,
"ansible_distribution_file_path": "/etc/redhat-release",
"ansible_distribution_file_search_string": "Red Hat",
"ansible_distribution_file_variety": "RedHat",
"ansible_distribution_major_version": "8",
"ansible_distribution_release": "Ootpa",
"ansible_distribution_version": "8.2",
"ansible_dns": {
"nameservers": [
"8.8.8.8"
[root@ansible ansible]# cat playbook/test.yml
---
- hosts: 192.168.47.129
vars: // 定义变量
platform: //平台
- RedHat //区分大小写
- centos
tasks:
- name: wjj
yum:
name: vsftpd
state: present
when: ansible_facts['distribution'] in platform //用ansible_facts里面的distribution获取的值是否在platform变量里面
[root@ansible ansible]# ansible-playbook playbook/test.yml
PLAY [192.168.47.129] **********************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.47.129]
TASK [wjj] *********************************************************************
changed: [192.168.47.129]
PLAY RECAP *********************************************************************
192.168.47.129 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@apache ~]# rpm -qa|grep vsftpd
vsftpd-3.0.3-31.el8.x86_64
2.3 判断多个条件
使用and运算符组合多个条件语句
所有条件必须成立,否则执行失败。
when:
- ansible_distribution_version == "7.5"
- ansible_kernel == "3.10.0-327.el7.x86_64"
使用or运算符组合多个条件语句
通过使用括号分组条件,可以表达更复杂的条件语句。
多个条件只需要有一个条件成立就会执行
when: > //换行
( ansible_distribution == "Redhat" and
ansible_distribution_major_version == "7" )
or
( ansible_distribution == "Fedora" and
ansible_distribution_major_version == "28" )
2.4 循环语句和条件任务
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
组合使用条件和注册变量
---
- 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
when: result.rc == 0 # 评估Postfix任务的输出。如果systemctl命令的退出代码为0,\
# 则Postfix激活并且此任务重启httpd服务
3、 ansible处理程序
3.1 处理程序
ansible模块设计具有幂等性。执行多遍playbook是不会有影响的,只有更改了配置才会发生改变
[root@ansible playbook]# cd files/
[root@ansible files]# scp 192.168.47.129:/etc/vsftpd/vsftpd.conf .
vsftpd.conf 100% 5098 2.2MB/s 00:00
[root@ansible files]# ls
[root@ansible files]# vsftpd.conf //修改配置文件
12 anonymous_enable=YES //把no改为yes,只有把配置文件改了,handlers和notify写了,这三个都满足才会执行下面任务
13 #
[root@ansible playbook]# cat test.yml
---
- hosts: 192.168.47.129
gather_facts: no
tasks:
- name: wjj //安装
yum:
name: vsftpd
state: present
- name: config //配置
copy:
src: files/vsftpd.conf
dest: /etc/vsftpd/vsftpd.conf
notify: //通知restart vsftpd,只有通知了才会去触发,触发了才会被执行
- restart vsftpd //要运行的处理程序的名称
- name: service //启动服务
service:
name: vsftpd
state: started
handlers: //handlers级别与tasks平级,
- name: restart vsftpd //被任务调用的处理程序的名称
service: //用于该处理程序的模块
name: vsftpd
state: restarted
[root@ansible playbook]# ansible-playbook test.yml
PLAY [192.168.47.129] **********************************************************
TASK [wjj] *********************************************************************
ok: [192.168.47.129]
TASK [config] ******************************************************************
changed: [192.168.47.129]
TASK [service] *****************************************************************
changed: [192.168.47.129]
RUNNING HANDLER [restart vsftpd] ***********************************************
changed: [192.168.47.129]
PLAY RECAP *********************************************************************
192.168.47.129 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
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: //并不是谁先通知谁先执行,而是看handlers写的顺序,谁在前面谁先执行
- 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将立即在该主机上中止play的其余部分并且跳过所有后续任务。
4.2 忽略任务失败
默认情况下,任务失败时play会中止。不过,可以通过忽略失败的任务来覆盖此行为。可以在任务中使用ignore_errors关键字来实现此目的。
[root@ansible playbook]# cat test.yml
---
- hosts: 192.168.47.129
gather_facts: no
tasks:
- name: wjj
yum:
name: vsftpd23
state: present
ignore_errors: yes
- name: config
copy:
src: files/vsftpd.conf
dest: /etc/vsftpd/vsftpd.conf
notify:
- restart vsftpd
- name: service
service:
name: vsftpd
state: started
handlers:
- name: restart vsftpd
service:
name: vsftpd
state: restarted
[root@ansible playbook]# ansible-playbook test.yml
PLAY [192.168.47.129] **********************************************************
TASK [wjj] *********************************************************************
fatal: [192.168.47.129]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"}, "changed": false, "failures": ["No package vsftpd23 available."], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []}
...ignoring vsftpd23是不存在的,直接执行就一定会报错,但是加上ignore_errors: yes命令,就不会报错,会直接忽略错误的那一部分执行下一部分
TASK [config] ******************************************************************
ok: [192.168.47.129]
TASK [service] *****************************************************************
ok: [192.168.47.129]
PLAY RECAP *********************************************************************
192.168.47.129 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
4.3 任务失败后强制执行处理程序
配置强制执行handlers处理程序的命令:force_handlers
handlers前面的有部分错了,则不会中止任务而是会先跳过,先执行handlers部分
注: 必须先通知,不然如果前面执行失败了,来不及通知,任务就会被中止执行失败。错误必须在通知之后发生的
[root@ansible files]# vsftpd.conf //修改配置文件
12 anonymous_enable=NO //把yes改为no,就算写了强制执行handlers,把通知写在了错误之前,不改配置文件也会报错
13 #
[root@ansible playbook]# cat test.yml
---
- hosts: 192.168.47.129
gather_facts: no
force_handlers: yes
tasks:
- name: wjj
yum:
name: vsftpd
state: present
ignore_errors: yes
- name: config
copy:
src: files/vsftpd.conf
dest: /etc/vsftpd/vsftpd.conf
notify:
- restart vsftpd
- name: service
service:
name: vsftpd1
state: started
handlers:
- name: restart vsftpd
service:
name: vsftpd
state: restarted
[root@ansible playbook]# ansible-playbook test.yml
PLAY [192.168.47.129] **********************************************************
TASK [wjj] *********************************************************************
ok: [192.168.47.129]
TASK [config] ******************************************************************
changed: [192.168.47.129]
TASK [service] *****************************************************************
fatal: [192.168.47.129]: FAILED! => {"changed": false, "msg": "Could not find the requested service vsftpd1: host"}
RUNNING HANDLER [restart vsftpd] ***********************************************
changed: [192.168.47.129]
PLAY RECAP *********************************************************************
192.168.47.129 : ok=3 changed=2 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
4.4 指定任务失败条件
可以在任务中使用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" // 如果脚本输出结果里面出现了一个Password missing就说明任务执行失败了
fail模块强制任务失败
当fail发生失败时,when条件如果成立,就会被认为失败了,失败了之后打印一句话
[root@ansible playbook]# cat test.yml
---
- hosts: 192.168.47.129
gather_facts: no
tasks:
- name: wjj
command: echo "hello wjj"
register: result
- name: nihao
fail:
msg: "任务执行失败"
when: "'wjj' in result['stdout']"
[root@ansible playbook]# ansible-playbook test.yml
PLAY [192.168.47.129] **********************************************************
TASK [wjj] *********************************************************************
changed: [192.168.47.129]
TASK [nihao] *******************************************************************
fatal: [192.168.47.129]: FAILED! => {"changed": false, "msg": "任务执行失败"}
PLAY RECAP *********************************************************************
192.168.47.129 : ok=1 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
4.5 指定何时任务报告“changed”结果
如果是false,不管上面执行成功或者是改了东西还是没有没改,都报告ok
如果是true,把上面执行的都报告changed
//没改配置文件,直接重启
[root@ansible playbook]# cat test.yml
---
- hosts: 192.168.47.129
gather_facts: no
tasks:
- name: wjj
copy:
src: files/vsftpd.conf
dest: /etc/vsftpd/vsftpd.conf
changed_when: true
[root@ansible playbook]# ansible-playbook test.yml
PLAY [192.168.47.129] **********************************************************
TASK [wjj] *********************************************************************
changed: [192.168.47.129]
PLAY RECAP *********************************************************************
192.168.47.129 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
//改了配置文件,再重启
[root@ansible playbook]# cat test.yml
---
- hosts: 192.168.47.129
gather_facts: no
tasks:
- name: wjj
copy:
src: files/vsftpd.conf
dest: /etc/vsftpd/vsftpd.conf
changed_when: false //不报告changed状态,就用false
[root@ansible playbook]# ansible-playbook test.yml
PLAY [192.168.47.129] **********************************************************
TASK [wjj] *********************************************************************
ok: [192.168.47.129]
PLAY RECAP *********************************************************************
192.168.47.129 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
4.6 块和错误处理
block块中的任务执行失败了,则会执行rescue块中的任务;block块中的任务执行成功了,则不会执行rescue块中的任务。但always块中的任务始终执行,不管前面哪个模块成功错误,都不会妨碍到always块。
- **block:**定义要运行的主要任务
- **rescue:**定义要在block子句中定义的任务失败时运行的任务
- **always:**定义始终都独立运行的任务,不论block和rescue子句中定义的任务是成功还是失败
tasks:
- name: Upgrade DB
block:
- name: upgrade the database
shell:
cmd: /usr/local/lib/upgrade-database
rescue:
- name: revert the database upgrade
shell:
cmd: /usr/local/lib/revert-database
always:
- name: always restart the database
service:
name: mariadb
state: restarted