1. 管理机密
1.1 Ansible Vault
当我们写的 playbook 中涉及敏感信息,如:密码。这时为了防止敏感信息泄露,就可以使用 vault 进行加密。
1.2 ansible-vault 命令行操作
1.2.1 创建加密文件
创建新的加密文件ansible-vault create
[root@ansible ~]# ansible-vault create xx.yml
New Vault password:
Confirm New Vault password:
[root@ansible ~]# cat xx.yml
$ANSIBLE_VAULT;1.1;AES256
35613766303866333262313936376263636436306436366138313762663939363563316362303431
3762643965663231626562346331633230366139616563370a393863356366616437323064303361
66366239386136336238303238303963376364656663633165663635626532376265313464373266
3731313263333562390a353231323436303162373332326336343333386338333264613232393266
6635
可以创建加密文件来保存加密文件的密码,
[root@ansible ~]# cat .qq
redhat
[root@ansible ~]# ansible-vault create --vault-password-file=.qq 111.yml
[root@ansible ~]# cat 111.yml
$ANSIBLE_VAULT;1.1;AES256
1.2.2 查看加密文件
可以使用ansible-vault view filename
命令查看Ansible Vault加密的文件
[root@ansible ~]# ansible-vault view xx.yml
Vault password:
---
- name: create user
hosts: 192.168.172.142
tasks:
- name:
user:
name: jerry
uid: 2222
--vault-password-file
指向存放密码的文件,可以不用手动输密码
[root@ansible ~]# ansible-vault view --vault-password-file=.qq xx.yml
---
- name: create user
hosts: 192.168.172.142
tasks:
- name:
user:
name: jerry
uid: 2222
1.2.3 编辑现有的加密文件
要编辑现有的加密文件,Ansible Vault提供了ansible-vault edit filename
命令。此命令将文件解密为一个临时文件,并允许编辑。保存时,它将复制其内容并删除临时文件。
[root@ansible ~]# ansible-vault edit xx.yml
Vault password:
//不用手动输入密码
[root@ansible ~]# ansible-vault edit --vault-password-file=.qq xx.yml
1.2.4 加密现有的文件
ansible-vault encrypt filename
命令加密已存在的文件。此命令可取多个欲加密文件的名称作为参数。
[root@ansible ~]# ansible-vault encrypt anaconda-ks.cfg
New Vault password:
Confirm New Vault password:
Encryption successful
[root@ansible ~]# cat anaconda-ks.cfg
$ANSIBLE_VAULT;1.1;AES256
使用–output=OUTPUT_FILE选项,可将加密文件保存为新的名称。只能通过–output选项使用一个输入文件。
1.2.5 解密现有的文件
现有的加密文件可以通过ansible-vault decrypt filename命令永久解密。在解密单个文件时,可使用--output选项以其他名称保存解密的文件。
```shell
[root@ansible ~]# ansible-vault decrypt anaconda-ks.cfg
Vault password:
Decryption successful
[root@ansible ~]# cat anaconda-ks.cfg
#version=RHEL8
ignoredisk --only-use=nvme0n1
autopart --type=lvm
# Partition clearing information
clearpart --none --initlabel
......
1.2.6 更改加密文件的密码
使用ansible-vault rekey filename命令更改加密文件的密码。此命令可一次性更新多个数据文件的密钥。
[root@ansible ~]# ansible-vault rekey xx.yml
Vault password:
New Vault password:
Confirm New Vault password:
Rekey successful
1.3 playbook和ansible vault
要运行可访问通过Ansible Vault加密的文件的playbook,需要向ansible-playbook命令提供加密密码。如果不提供密码,playbook将返回错误:
[root@ansible ~]# ansible-playbook xx.yml
ERROR! Attempting to decrypt but no vault secrets found
可使用–vault-password-file选项指定以纯文本存储加密密码的文件。密码应当在该文件中存储为一行字符串。由于该文件包含敏感的纯文本密码,因此务必要通过文件权限和其他安全措施对其加以保护。
[root@ansible ~]# ansible-playbook --vault-password-f
ile=.qq xx.yml
PLAY [create user] **********************************
***************************
TASK [Gathering Facts] ******************************
***************************
ok: [192.168.172.142]
TASK [user] *****************************************
***************************
changed: [192.168.172.142]
PLAY RECAP ******************************************
***************************
192.168.172.142 : ok=2 changed=1 unr
eachable=0 failed=0 skipped=0 rescued=0 i
gnored=0
[root@lamp ~]# id jerry
uid=2222(jerry) gid=2222(jerry) groups=2222(jerry)
2. 管理事实
主控机连接被控机后,被控机上对应生成的变量称之为事实。
为受管主机收集的一些事实可能包括:
- 主机名称
- 内核版本
- 网络接口
- IP地址
- 操作系统版本
- 各种环境变量
- CPU数量
- 提供的或可用的内存
- 可用磁盘空间
查看为受管主机收集的事实的一种方式是,运行一个收集事实并使用debug模块显示ansible_facts变量值的简短playbook。
[root@ansible ~]# cat oo.yml
---
- hosts: 192.168.172.142
tasks:
- name: view facts
debug:
var: ansible_facts
[root@ansible ~]# ansible-playbook oo.yml
PLAY [192.168.172.142] *********************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.172.142]
TASK [view facts] **************************************************************
ok: [192.168.172.142] => {
"ansible_facts": {
......
debug模块的msg可以打印自定义信息。
[root@ansible ~]# cat oo.yml
---
- hosts: 192.168.172.142
tasks:
- name: view facts
debug:
msg:
"{{ ansible_facts['default_ipv4']['address'] }}"
[root@ansible ~]# ansible-playbook oo.yml
PLAY [192.168.172.142] *******************************************************************************
TASK [Gathering Facts] *******************************************************************************
ok: [192.168.172.142]
TASK [view facts] ************************************************************************************
ok: [192.168.172.142] => {
"msg": "192.168.172.142"
}
PLAY RECAP *******************************************************************************************
192.168.172.142 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
为了提升系统处理效率,可关闭事实。
关闭事实gather_facts: no
2.1 Ansible事实示例
事实 | 变量 |
---|---|
短主机名 | ansible_facts[‘hostname’] |
完全限定域名 | ansible_facts[‘fqdn’] |
IPv4地址 | ansible_facts[‘default_ipv4’][‘address’] |
所有网络接口的名称列表 | ansible_facts[‘interfaces’] |
/dev/vda1磁盘分区的大小 | ansible_facts[‘devices’][‘vda’][‘partitions’][‘vda1’][‘size’] |
DNS服务器列表 | ansible_facts[‘dns’][‘nameservers’] |
当前运行的内核版本 | ansible_facts[‘kernel’] |
在playbook中使用事实时,Ansible将事实的变量名动态替换为对应的值:
[root@ansible ~]# cat oo.yml
---
- hosts: 192.168.172.142
tasks:
- name: view facts
debug:
msg:
The IPV4 address of {{ ansible_facts["all_ipv4_addresses"] }}
[root@ansible ~]# ansible-playbook oo.yml
PLAY [192.168.172.142] *********************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.172.142]
TASK [view facts] **************************************************************
ok: [192.168.172.142] => {
"msg": "The IPV4 address of ['192.168.172.142']"
}
PLAY RECAP *********************************************************************
192.168.172.142 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
2.2 创建自定义事实
除了使用系统捕获的事实外,我们还可以自定义事实,并将其本地存储在每个受管主机上。这些事实整合到setup模块在受管主机上运行时收集的标准事实列表中。它们让受管主机能够向Ansible提供任意变量,以用于调整play的行为。
自定义事实可以在静态文件中定义,格式可为INI文件或采用JSON。它们也可以是生成JSON输出的可执行脚本,如同动态清单脚本一样。
有了自定义事实,我们可以为受管主机定义特定的值,供play用于填充配置文件或有条件地运行任务。动态自定义事实允许在play运行时以编程方式确定这些事实的值,甚至还可以确定提供哪些事实。
默认情况下,setup模块从各受管主机的/etc/ansible/facts.d目录下的文件和脚本中加载自定义事实。各个文件或脚本的名称必须以.fact结尾才能被使用。动态自定义事实脚本必须输出JSON格式的事实,而且必须是可执行文件。
//在受管主机上创建/etc/ansble/facts.d
[root@lamp facts.d]# cat 00.fact
[packages]
web_package = httpd
db_package = mariadb-server
[users]
user1 = aaa
user2 = bbb
[root@ansible ~]# cat oo.yml
---
- hosts: 192.168.172.142
tasks:
- name:
debug:
var: ansible_facts["ansible_local"]
[root@ansible ~]# ansible-playbook oo.yml
PLAY [192.168.172.142] *********************************************************
TASK [Gathering Facts] *********************************************************
[WARNING]: error loading fact - please check content
ok: [192.168.172.142]
TASK [debug] *******************************************************************
ok: [192.168.172.142] => {
"ansible_facts[\"ansible_local\"]": {
"00": {
"packages": {
"db_package": "mariadb-server",
"web_package": "httpd"
},
"users": {
"user1": "aaa",
"user2": "bbb"
}
},
"000": "error loading fact - please check content"
}
}
PLAY RECAP *********************************************************************
192.168.172.142 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
同样的事实可能以JSON格式提供。以下JSON事实等同于以上示例中INI格式指定的事实。JSON数据可以存储在静态文本文件中,或者通过可执行脚本输出到标准输出:
{
"packages": {
"web_package": "httpd",
"db_package": "mariadb-server"
},
"users": {
"user1": "joe",
"user2": "jane"
}
}
自定义事实文件不能采用playbook那样的YAML格式。JSON格式是最为接近的等效格式。
自定义事实由setup模块存储在ansible_facts.ansible_local变量中。
事实按照定义它们的文件的名称来整理。例如,假设前面的自定义事实由受管主机上保存为/etc/ansible/facts.d/custom.fact的文件生成。在这种情况下,ansible_facts.ansible_local[‘custom’][‘users’][‘user1’]的值为joe。
自定义事实的使用方式与playbook中的默认事实相同:
---
- hosts: all
tasks:
- name: Prints various Ansible facts
debug:
msg: >
The package to install on {{ ansible_facts['fqdn'] }}
is {{ ansible_facts['ansible_local']['cutstom']['packages']['web_package'] }}
2.3 使用魔法变量
一些变量并非事实或通过setup模块配置,但也由Ansible自动设置。这些魔法变量也可用于获取与特定受管主机相关的信息。
最常用的有四个:
魔法变量 | 说明 |
---|---|
hostvars | 包含受管主机的变量,可以用于获取另一台受管主机的变量的值。 如果还没有为受管主机收集事实,则它不会包含该主机的事实。 |
group_names | 列出当前受管主机所属的所有组 |
groups | 列出清单中的所有组和主机 |
inventory_hostname | 包含清单中配置的当前受管主机的主机名称。 因为各种原因有可能与事实报告的主机名称不同 |
3. 循环
3.1 编写循环任务
Ansible支持使用loop关键字对一组项目迭代任务。可以配置循环以利用列表中的各个项目、列表中各个文件的内容、生成的数字序列或更为复杂的结构来重复任务。
3.1.1 简单循环
简单循环对一组项目迭代任务。loop关键字添加到任务中,将应对其迭代任务的项目列表取为值。循环变量item保存每个迭代过程中使用的值。
[root@ansible ~]# cat oo.yml
---
- hosts: 192.168.172.142
tasks:
- name:
service:
name: "{{ item }}"
state: started
loop:
- httpd
- mariadb
- php-fpm
[root@ansible ~]# ansible-playbook oo.yml
PLAY [192.168.172.142] *********************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.172.142]
TASK [service] *****************************************************************
ok: [192.168.172.142] => (item=httpd)
ok: [192.168.172.142] => (item=mariadb)
ok: [192.168.172.142] => (item=php-fpm)
PLAY RECAP *********************************************************************
192.168.172.142 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
将loop循环的列表提前赋值给一个变量,然后在循环语句中调用:
[root@ansible ~]# cat oo.yml
run:
- httpd
- mariadb
[root@ansible ~]# cat xx.yml
---
- hosts: 192.168.172.142
vars_files:
- oo.yml
tasks:
- name:
service:
name: "{{ item }}"
state: started
loop: "{{ run }}"
[root@ansible ~]# ansible-playbook xx.yml
PLAY [192.168.172.142] *********************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.172.142]
TASK [service] *****************************************************************
ok: [192.168.172.142] => (item=httpd)
ok: [192.168.172.142] => (item=mariadb)
PLAY RECAP *********************************************************************
192.168.172.142 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
3.1.2 循环散列或字典列表
loop列表不需要是简单值列表。在以下示例中,列表中的每个项实际上是散列或字典。示例中的每个散列或字典具有两个键,即name和groups,当前item循环变量中每个键的值可以分别通过item.name和item.groups变量来检索。
[root@ansible ~]# cat xx.yml
---
- hosts: 192.168.172.142
tasks:
- name: Users exist and are in the correct groups
user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
loop:
- name: aaa
groups: root
- name: bbb
groups: root
- name: ccc
groups: root
[root@ansible ~]# ansible-playbook xx.yml
PLAY [192.168.172.142] *********************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.172.142]
TASK [Users exist and are in the correct groups] *******************************
changed: [192.168.172.142] => (item={'name': 'aaa', 'groups': 'root'})
changed: [192.168.172.142] => (item={'name': 'bbb', 'groups': 'root'})
ok: [192.168.172.142] => (item={'name': 'ccc', 'groups': 'root'})
PLAY RECAP *********************************************************************
192.168.172.142 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
3.1.3 较早样式的循环关键字
较早样式的Ansible循环
循环关键字 | 描述 |
---|---|
with_items | 行为与简单列表的loop关键字相同,例如字符串列表或散列/字典列表。 但与loop不同的是,如果为with_items提供了列表的列表, 它们将被扁平化为单级列表。循环变量item保存每次迭代过程中使用的列表项。 |
with_file | 此关键字需要控制节点文件名列表。循环变量item在每次迭代过程中保存文件列表中相应文件的内容。 |
with_sequence | 此关键字不需要列表,而是需要参数来根据数字序列生成值列表。循环变量item在每次迭代过程中保存生成的序列中的一个生成项的值。 |
3.1.4 将Register变量与Loop一起使用
register关键字也可以捕获循环任务的输出。
[root@ansible ~]# cat oo.yml
---
- hosts: 192.168.172.142
gather_facts: no
tasks:
- name:
shell: "echo hello {{ item }}"
loop:
- aaa
- bbb
- ccc
- ddd
register: results
- debug:
var: results
[root@ansible ~]# ansible-playbook oo.yml
PLAY [192.168.172.142] *********************************************************
TASK [shell] *******************************************************************
changed: [192.168.172.142] => (item=aaa)
changed: [192.168.172.142] => (item=bbb)
changed: [192.168.172.142] => (item=ccc)
changed: [192.168.172.142] => (item=ddd)
TASK [debug] *******************************************************************
ok: [192.168.172.142] => {
"results": {
"changed": true,
"msg": "All items completed",
"results": [
{
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"ansible_loop_var": "item",
"changed": true,
"cmd": "echo hello aaa",
"delta": "0:00:00.011488",
"end": "2021-07-26 20:44:21.222259",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "echo hello aaa",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"stdin_add_newline": true,
"strip_empty_ends": true,
"warn": true
}
},
"item": "aaa",
"rc": 0,
"start": "2021-07-26 20:44:21.210771",
"stderr": "",
"stderr_lines": [],
"stdout": "hello aaa",
"stdout_lines": [
"hello aaa"
]
},
{
"ansible_loop_var": "item",
"changed": true,
"cmd": "echo hello bbb",
"delta": "0:00:00.009031",
"end": "2021-07-26 20:44:22.424240",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "echo hello bbb",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"stdin_add_newline": true,
"strip_empty_ends": true,
"warn": true
}
},
"item": "bbb",
"rc": 0,
"start": "2021-07-26 20:44:22.415209",
"stderr": "",
"stderr_lines": [],
"stdout": "hello bbb",
"stdout_lines": [
"hello bbb"
]
},
{
"ansible_loop_var": "item",
"changed": true,
"cmd": "echo hello ccc",
"delta": "0:00:00.006415",
"end": "2021-07-26 20:44:23.367842",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "echo hello ccc",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"stdin_add_newline": true,
"strip_empty_ends": true,
"warn": true
}
},
"item": "ccc",
"rc": 0,
"start": "2021-07-26 20:44:23.361427",
"stderr": "",
"stderr_lines": [],
"stdout": "hello ccc",
"stdout_lines": [
"hello ccc"
]
},
{
"ansible_loop_var": "item",
"changed": true,
"cmd": "echo hello ddd",
"delta": "0:00:00.008976",
"end": "2021-07-26 20:44:24.472214",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "echo hello ddd",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"stdin_add_newline": true,
"strip_empty_ends": true,
"warn": true
}
},
"item": "ddd",
"rc": 0,
"start": "2021-07-26 20:44:24.463238",
"stderr": "",
"stderr_lines": [],
"stdout": "hello ddd",
"stdout_lines": [
"hello ddd"
]
}
]
}
}
PLAY RECAP *********************************************************************
192.168.172.142 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
4. 有条件的运行任务
Ansible可使用conditionals在符合特定条件时执行任务或play。例如,可以利用一个条件在Ansible安装或配置服务前确定受管主机上的可用内存。
我们可以利用条件来区分不同的受管主机,并根据它们所符合的条件来分配功能角色。Playbook变量、注册的变量和Ansible事实都可通过条件来进行测试。可以使用比较字符串、数字数据和布尔值的运算符。
以下场景说明了在Ansible中使用条件的情况:
- 可以在变量中定义硬限制(如min_memory)并将它与受管主机上的可用内存进行比较。
- Ansible可以捕获并评估命令的输出,以确定某一任务在执行进一步操作前是否已经完成。例如,如果某一程序失败,则将路过批处理。
- 可以利用Ansible事实来确定受管主机网络配置,并决定要发送的模板文件(如,网络绑定或中继)。
- 可以评估CPU的数量,来确定如何正确调节某一Web服务器。
- 将注册的变量与预定义的变量进行比较,以确定服务是否已更改。例如,测试服务配置文件的MD5检验以和查看服务是否已更改。
4.1 条件任务语法
when语句用于有条件地运行任务。它取要测试的条件为值。如果条件满足,则运行任务。如果条件不满足,则跳过任务。
可以测试的一个最简单条件是某一布尔变量是True还是False。以下示例中的when语句导致任务仅在run_my_task为True时运行:
[root@ansible ~]# cat test.yml
---
- hosts: http
vars:
run_my_task: true
tasks:
- name:
yum:
name: vsftpd
when: run_my_task
[root@ansible ~]# ansible-playbook test.yml
PLAY [http] ********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.172.142]
TASK [yum] *********************************************************************
changed: [192.168.172.142]
PLAY RECAP *********************************************************************
192.168.172.142 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
以下示例测试my_service变量是否具有值。若有值,则将my_service的值用作要安装的软件包的名称。如果未定义my_service变量,则跳过任务且不显示错误。
[root@ansible ~]# cat test.yml
---
- hosts: http
gather_facts: no
vars:
my_service: vsftpd
tasks:
- name: "{{ my_service }} package is installed"
yum:
name: "{{ my_service }}"
state: latest
when: my_service is defined
[root@ansible ~]# ansible-playbook test.yml
PLAY [http] ********************************************************************
TASK [vsftpd package is installed] *********************************************
ok: [192.168.172.142]
PLAY RECAP *********************************************************************
192.168.172.142 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 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 |
在示例中,ansible_distribution变量是在Gathering Facts任务期间确定的事实,用于标识托管主机的操作系统分支。变量supported_distros由playbook创建,包含该playbook支持的操作系统分发列表。如果ansible_distribution的值在supported_distros列表中,则条件通过且任务运行。
[root@ansible ~]# cat test.yml
---
- hosts: http
gather_facts: yes
vars:
supported_distros:
- RedHat
- Fedora
tasks:
- name:
yum:
name: vsftpd
state: absent
when: ansible_distribution in supported_distros
[root@ansible ~]# ansible-playbook test.yml
PLAY [http] ********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.172.142]
TASK [yum] *********************************************************************
changed: [192.168.172.142]
PLAY RECAP *********************************************************************
192.168.172.142 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
4.2 测试多个条件
一个when语句可用于评估多个条件。使用and和or关键字组合条件,并使用括号分组条件。
如果任一条件为真时满足条件语句,则应当使用or语句。例如,如果计算机上运行的是红帽企业linux或Fedora,则下述条件得到满足:
[root@ansible ~]# cat test.yml
---
- hosts: http
vars:
my_service: vsftpd
tasks:
- name:
yum:
name: "{{ my_service }}"
state: present
when: ansible_distribution == "Redhat" or ansible_distribution == "Fedora"
[root@ansible ~]# ansible-playbook test.yml
PLAY [http] ********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.172.142]
TASK [yum] *********************************************************************
skipping: [192.168.172.142]
PLAY RECAP *********************************************************************
192.168.172.142 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
使用and运算时,两个条件都必须为真,才能满足整个条件语句。
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" )
5. 组合循环和有条件任务
- 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语句。
---
- name: Restart HTTPD if Postfix is Running
hosts: http
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服务
6. 实施处理程序
6.1 ansible处理程序
使用notify语句来调用一个任务的触发,只有当任务的结果为change时,才会调用任务
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
在上面的例子中,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
6.2 调用使用处理程序的好处
使用处理程序时需要牢记几个重要事项:
- 处理程序始终按照play的handlers部分指定的顺序运行。它们不按在任务中由notify语句列出的顺序运行,或按任务通知它们的顺序运行。
- 处理程序通常在相关play中的所有其他任务完成后运行。playbook的tasks部分中某一任务调用的处理程序,将等到tasks下的所有任务都已处理后才会运行。
- 处理程序名称存在于各play命名空间中。如果两个处理程序被错误地给予相同的名称,则仅会运行一个。
- 即使有多个任务通知处理程序,该处理程序依然仅运行一次。如果没有任务通知处理程序,它就不会运行。
- 如果包含notify语句的任务没有报告changed结果(例如,软件包已安装并且任务报告ok),则处理程序不会获得通知。处理程序将被跳过,直到有其他任务通知它。只有相关任务报告了changed状态,Ansible才会通知处理程序。
处理程序用于在任务对受管主机进行更改时执行额外操作。它们不应用作正常任务的替代。
7. 处理任务失败
7.1 管理play中的任务错误
Ansible评估任务的返回代码,从而确定任务是成功还是失败。通常而言,当任务失败时,Ansible将立即在该主机上中止play的其余部分并且跳过所有后续任务。
但有些时候,可能希望即使在任务失败时也继续执行play。例如,或许预期待定任务有可能会失败,并且希望通过有条件地运行某项其他任务来修复。
Ansible有多种功能可用于管理任务错误。
7.2 忽略任务失败
默认情况下,任务失败时play会中止。不过,可以通过忽略失败的任务来覆盖此行为。可以在任务中使用ignore_errors关键字来实现此目的。
[root@ansible ~]# cat test.yml
---
- hosts: http
gather_facts: no
tasks:
- name:
yum:
name: snfuxhzmc
state: present
ignore_errors: yes
[root@ansible ~]# ansible-playbook test.yml
PLAY [http] ********************************************************************
TASK [yum] *********************************************************************
fatal: [192.168.172.142]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"}, "changed": false, "failures": ["No package snfuxhzmc available."], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []}
...ignoring
PLAY RECAP *********************************************************************
192.168.172.142 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
7.3 任务失败后强制执行处理程序
通常而言,如果任务失败并且play在该主机上中止,则收到play中早前任务通知的处理程序将不会运行。如果在play中设置force_handlers: yes关键字,则即使play因为后续任务失败而中止也会调用被通知的处理程序。
---
- hosts: http
force_handlers: yes
tasks:
- name: a task which always notifies its handler
command: /bin/true
notify: restart the database
- name: a task which fails because the package doesn't exist
yum:
name: bbbbbynumnbctvc
state: latest
handlers:
- name: restart the database
service:
name: mariadb
state: restarted
请记住,处理程序会在任务报告changed结果时获得通知,而在任务报告ok或failed结果时不会获得通知。
7.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"
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"
7.5 指定何时任务报告 “Changed” 结果
当任务对托管主机进行了更改时,会报告 changed 状态并通知处理程序。如果任务不需要进行更改,则会报告ok并且不通知处理程序。
changed_when关键字可用于控制任务在何时报告它已进行了更改。
[root@ansible ~]# cat test.yml
---- hosts: http
tasks:
- name:
yum:
name: vsftpd
state: absent
changed_when: yes
[root@ansible ~]# ansible-playbook test.yml
PLAY [http] ********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.172.142]
TASK [yum] *********************************************************************
changed: [192.168.172.142]
PLAY RECAP *********************************************************************
192.168.172.142 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
7.6 Ansible块和错误处理
在playbook中,块是对任务进行逻辑分组的子句,可用于控制任务的执行方式。
- name: block example
hosts: web
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"
通过块,也可结合rescue和always语句来处理错误。如果块中的任何任务失败,则执行其rescue块中的任务来进行恢复。在block子句中的任务以及rescue子句中的任务(如果出现故障)运行之后,always子句中的任务运行。总结:
- block:定义要运行的主要任务.
- rescue:定义要在block子句中定义的任务失败时运行的任务.
- always:定义始终都独立运行的任务,不论block和rescue子句中定义的任务是成功还是失败.
以下示例演示了如何在playbook中实施块。
[root@ansible ~]# cat test.yml
---
- hosts: http
tasks:
- name: test
block:
- shell: echo 'hello' > /root/xx
rescue:
- shell: echo 'yes' > /root/xx
always:
- shell: echo 'no' >> /root/xx
[root@ansible ~]# ansible-playbook test.yml
PLAY [http] ********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.172.142]
TASK [shell] *******************************************************************
changed: [192.168.172.142]
TASK [shell] *******************************************************************
changed: [192.168.172.142]
PLAY RECAP *********************************************************************
192.168.172.142 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@apache ~]# cat xx
hello
no