实施任务控制务
文章目录
1. 利用循环迭代任务
通过利用循环,我们无需编写多个使用同一模块的任务。例如,他们不必编写五个任务来确保存在五个用户,而是只需编写一个任务来对含有五个用户的列表迭代,从而确保它们都存在。
Ansible支持使用loop关键字对一组项目迭代任务。可以配置循环以利用列表中的各个项目、列表中各个文件的内容、生成的数字序列或更为复杂的结构来重复任务。
1.1 简单循环
简单循环对一组项目迭代任务。loop关键字添加到任务中,将应对其迭代任务的项目列表取为值。循环变量item保存每个迭代过程中使用的值。
请思考以下代码片段,它使用两次service模块来确保两个网络服务处于运行状态:
[root@server playbook]# cat testing.yml
---
- hosts: httpd
tasks:
- name: runing httpd
service:
name: httpd
state: started
- name: runing php
service:
name: php-fpm
state: started
[root@server playbook]#
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [httpd] ****************************************************************************
TASK [Gathering Facts] ******************************************************************
ok: [192.168.58.20]
TASK [runing httpd] *********************************************************************
ok: [192.168.58.20]
TASK [runing php] ***********************************************************************
ok: [192.168.58.20]
PLAY RECAP ******************************************************************************
192.168.58.20 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
这两个任务可以重新编写为使用一个简单循环,从而只需一个任务来确保两个服务都在运行:
[root@server playbook]# cat testing.yml
---
- hosts: httpd
tasks:
- name: runing serive
service:
name: "{{ item }}"
state: started
loop:
- httpd
- php-fpm
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [httpd] ****************************************************************************
TASK [Gathering Facts] ******************************************************************
ok: [192.168.58.20]
TASK [runing serive] ********************************************************************
ok: [192.168.58.20] => (item=httpd)
ok: [192.168.58.20] => (item=php-fpm)
PLAY RECAP ******************************************************************************
192.168.58.20 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
可以通过一个变量提供loop所使用的列表。在以下示例中,变量start_service含有需要处于运行状态的服务的列表。
[root@server playbook]# cat testing.yml
---
- hosts: httpd
vars:
start_service:
- httpd
- php-fpm
tasks:
- name: runing serivce
service:
name: "{{ item }}"
state: started
loop: "{{ start_service }}"
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [httpd] ****************************************************************************
TASK [Gathering Facts] ******************************************************************
ok: [192.168.58.20]
TASK [runing serivce] *******************************************************************
ok: [192.168.58.20] => (item=httpd)
ok: [192.168.58.20] => (item=php-fpm)
PLAY RECAP ******************************************************************************
192.168.58.20 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
1.2 循环散列或字典列表
loop列表不需要是简单值列表。在以下示例中,列表中的每个项实际上是散列或字典。示例中的每个散列或字典具有两个键,即name和groups,当前item循环变量中每个键的值可以分别通过item.name和item.groups变量来检索。
[root@server playbook]# cat testing.yml
---
- hosts: httpd
tasks:
- name: create groups
group:
name: "{{ item }}"
state: present
system: yes
loop:
- jery
- tom
- name: user exist and are in the correct groups
user:
name: "{{ item.name }}"
state: present
system: yes
groups: "{{ item.groups }}"
loop:
- name: jony
groups: wu
- name: marry
groups: yifan
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [httpd] ****************************************************************************
TASK [Gathering Facts] ******************************************************************
ok: [192.168.58.20]
TASK [create groups] ********************************************************************
ok: [192.168.58.20] => (item=jery)
ok: [192.168.58.20] => (item=tom)
TASK [user exist and are in the correct groups] *****************************************
changed: [192.168.58.20] => (item={'name': 'jony', 'groups': 'wu'})
changed: [192.168.58.20] => (item={'name': 'marry', 'groups': 'yifan'})
PLAY RECAP ******************************************************************************
192.168.58.20 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
这一示例中结果是用户alice存在且为组jery的成员,并且用户joe存在且为组tom的成员
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的示例如下所示:
[root@server playbook]# cat testing.yml
---
- hosts: httpd
vars:
data:
- user one
- user two
- user three
tasks:
- name: "with_items"
debug:
msg: "{{ item }}"
with_items: "{{ data }}"
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [httpd] ****************************************************************************
TASK [Gathering Facts] ******************************************************************
ok: [192.168.58.20]
TASK [with_items] ***********************************************************************
ok: [192.168.58.20] => (item=user one) => {
"msg": "user one"
}
ok: [192.168.58.20] => (item=user two) => {
"msg": "user two"
}
ok: [192.168.58.20] => (item=user three) => {
"msg": "user three"
}
PLAY RECAP ******************************************************************************
192.168.58.20 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
1.4 将Register变量与Loop一起使用
register关键字也可以捕获循环任务的输出。以下代码片段显示了循环任务中register变量的结构:
[root@server playbook]# cat testing.yml
---
- hosts: httpd
gather_facts: no
tasks:
- name: loop echo
shell: "echo This is testing {{ item }}"
loop:
- hello
- hi
register: echo_results
- name: show echo_results
debug:
var: echo_results
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [httpd] ****************************************************************************
TASK [loop echo] ************************************************************************
changed: [192.168.58.20] => (item=hello)
changed: [192.168.58.20] => (item=hi)
TASK [show echo_results] ****************************************************************
ok: [192.168.58.20] => {
"echo_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 This is testing hello",
"delta": "0:00:00.003894",
"end": "2021-07-27 07:47:44.770668",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "echo This is testing hello",
"_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": "hello",
"rc": 0,
"start": "2021-07-27 07:47:44.766774",
"stderr": "",
"stderr_lines": [],
"stdout": "This is testing hello",
"stdout_lines": [
"This is testing hello"
]
},
{
"ansible_loop_var": "item",
"changed": true,
"cmd": "echo This is testing hi",
"delta": "0:00:00.003040",
"end": "2021-07-27 07:47:45.173967",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "echo This is testing hi",
"_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": "hi",
"rc": 0,
"start": "2021-07-27 07:47:45.170927",
"stderr": "",
"stderr_lines": [],
"stdout": "This is testing hi",
"stdout_lines": [
"This is testing hi"
]
}
]
}
}
在上面的例子中,results键包含一个列表。在下面,修改了playbook,使第二个任务迭代此列表:
[root@server playbook]# cat testing.yml
---
- hosts: httpd
gather_facts: no
tasks:
- name: loop echo
shell: "echo This is testing {{ item }} "
loop:
- hello
- hi
register: echo_results
- name: show echo_results
debug:
msg: "STDOUT from previous task: {{ item.stdout }}"
loop: "{{ echo_results['results'] }}"
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [http ] *************************************************************************************
TASK [loop echo] **************************************************************************************
changed: [httpd] => (item=hello)
changed: [httpd] => (item=hi)
TASK [show echo_results variable] *****************************************************************************
ok: [httpd] => (item={'cmd': 'echo This is testing ', 'stdout': 'echo This is testing hello', 'stderr': '', 'rc': 0, 'start': '2021-07-25 18:48:22.158506', 'end': '2021-07-25 18:48:22.160898', 'delta': '0:00:00.002392', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'echo This is testing hello ', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['echo This is testing hello'], 'stderr_lines': [], 'ansible_facts': {'discovered_interpreter_python': '/usr/libexec/platform-python'}, 'failed': False, 'item': 'one', 'ansible_loop_var': 'item'}) => {
"msg": "STDOUT from previous task: echo This is testing hello"
}
ok: [httpd] => (item={'cmd': 'echo This is testing hi ', 'stdout': 'echo This is testing hi', 'stderr': '', 'rc': 0, 'start': '2021-07-25 18:48:22.412420', 'end': '2021-07-25 18:48:22.415124', 'delta': '0:00:00.002704', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'echo This is testing hi ', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['echo This is testing hi'], 'stderr_lines': [], 'failed': False, 'item': 'two', 'ansible_loop_var': 'item'}) => {
"msg": "STDOUT from previous task: echo This is testing hi"
}
PLAY RECAP ****************************************************************************************************
httpd : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
2. 有条件地运行任务
Ansible可使用conditionals在符合特定条件时执行任务或play。例如,可以利用一个条件在Ansible安装或配置服务前确定受管主机上的可用内存。
我们可以利用条件来区分不同的受管主机,并根据它们所符合的条件来分配功能角色。Playbook变量、注册的变量和Ansible事实都可通过条件来进行测试。可以使用比较字符串、数字数据和布尔值的运算符。
以下场景说明了在Ansible中使用条件的情况:
- 可以在变量中定义硬限制(如min_memory)并将它与受管主机上的可用内存进行比较。
- Ansible可以捕获并评估命令的输出,以确定某一任务在执行进一步操作前是否已经完成。例如,如果某一程序失败,则将路过批处理。
- 可以利用Ansible事实来确定受管主机网络配置,并决定要发送的模板文件(如,网络绑定或中继)。
- 可以评估CPU的数量,来确定如何正确调节某一Web服务器。
- 将注册的变量与预定义的变量进行比较,以确定服务是否已更改。例如,测试服务配置文件的MD5检验以和查看服务是否已更改。
2.1 条件任务语法
when语句用于有条件地运行任务。它取要测试的条件为值。如果条件满足,则运行任务。如果条件不满足,则跳过任务。
可以测试的一个最简单条件是某一布尔变量是True还是False。以下示例中的when语句导致任务仅在run_my_task为True时运行:
[root@server playbook]# cat testing.yml
---
- name: Task
hosts: httpd
vars:
run_my_task: True
tasks:
- name: install httpd
yum:
name: httpd
when: run_my_task
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [Task] *******************************************************************************
TASK [Gathering Facts] ****************************************************************************************
ok: [httpd]
TASK [install httpd] *****************************************************************************
ok: [httpd]
PLAY RECAP ****************************************************************************************************
httpd : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
以下示例测试my_service变量是否具有值。若有值,则将my_service的值用作要安装的软件包的名称。如果未定义my_service变量,则跳过任务且不显示错误。
[root@ansible opt]# vim site1.yaml
---
- name: Simple Boolean Task Demo
hosts: httpd
vars:
my_service: httpd
tasks:
- name: "{{ my_service }} package is installed "
yum:
name: "{{ my_service }} "
when: my_service is defined
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [Task] *******************************************************************************
TASK [Gathering Facts] ****************************************************************************************
ok: [httpd]
TASK [install httpd] *****************************************************************************
ok: [httpd]
PLAY RECAP ****************************************************************************************************
httpd : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
3. handlers
handlers Ansible模块设计为具有幂等性。这表示,在正确编写的playbook中,playbook及其任务可以运行多次而不会改变受管主机,除非需要进行更改使受管主机进入所需的状态。
但在时候,在任务确实更改系统时,可能需要运行进一步的任务。例如,更改服务配置文件时可能要求重新加载该服务以便使其更改的配置生效。
处理程序是响应由其他任务触发的通知的任务。仅当任务在受管主机上更改了某些内容时,任务才通知其处理程序。每个处理程序具有全局唯一的名称,在playbook中任务块的末尾触发。如果没有任务通过名称通知处理程序,处理程序就不会运行。如果一个或多个任务通知处理程序,处理程序就会在play中的所有其他任务完成后运行一次。因为处理程序就是任务,所以可以在处理程序中使用他们将用于任何其他任务的模块。通常而言,处理程序被用于重新引导主机和重启服务。
修改httpd配置文件,将端口改成8080端口
[root@server playbook]# cat testing.yml
---
- hosts: httpd
tasks:
- name:
lineinfile:
path: /etc/httpd/conf/httpd.conf
regexp: "Listen"
line: "Listen 8080"
- name:
service:
name: httpd
state: restarted
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [httpd] ****************************************************************************
TASK [Gathering Facts] ******************************************************************
ok: [192.168.58.20]
TASK [lineinfile] ***********************************************************************
changed: [192.168.58.20]
TASK [service] **************************************************************************
changed: [192.168.58.20]
PLAY RECAP ******************************************************************************
192.168.58.20 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
查看配置文件#Listen 12.34.56.78:80
Listen 8080
修改testing
[root@server playbook]# cat testing.yml
---
- hosts: httpd
tasks:
- name:
lineinfile:
path: /etc/httpd/conf/httpd.conf
regexp: "Listen"
line: "Listen 8080"
backrefs: yes
notify:
restart httpd
handlers:
- name:
service:
name: httpd
state: restarted
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [httpd] ****************************************************************************
TASK [Gathering Facts] ******************************************************************
ok: [192.168.58.20]
TASK [lineinfile] ***********************************************************************
ok: [192.168.58.20]
PLAY RECAP ******************************************************************************
192.168.58.20 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
发现没有运行重启,因为配置文件没有做出更改,所以不触发
4. 任务失败
4.1忽略任务失败
Ansible 默认会检查命令和模块的返回状态,并进行相应的错误处理,默认是遇到错误就中断 playbook 的执行,这些默认行为都是可以改变的,可以通过 ignore_errors 忽略返回状态码
[root@server playbook]# cat testing.yml
---
- hosts: httpd
tasks:
- name:
yum:
name: teet
state: present
ignore_errors: yes
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [httpd] ****************************************************************************
TASK [Gathering Facts] ******************************************************************
ok: [192.168.58.20]
TASK [yum] ******************************************************************************
fatal: [192.168.58.20]: FAILED! => {"changed": false, "failures": ["No package teet available."], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []}
...ignoring
PLAY RECAP ******************************************************************************
192.168.58.20 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
4.2任务失败后强制执行处理程序
---
- hosts: httpd
force_handlers: yes
tasks:
- name:
command: echo "wr"
notify: restart
- name:
yum:
name: anni
state: present
handlers:
- name: restart
service:
name: httpd
state: restarted
//运行
[root@server playbook]# ansible-playbook testing.yml
PLAY [httpd] *********************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.58.20]
TASK [command] *****************************************************************
changed: [192.168.58.20]
TASK [yum] *********************************************************************
fatal: [192.168.58.20]: FAILED! => {"changed": false, "failures": ["No package anni available."], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []} //
RUNNING HANDLER [restart] ******************************************************
changed: [192.168.58.20]
PLAY RECAP *********************************************************************
192.168.58.20 : ok=3 changed=2 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
指定任务失败条件
//创建一个脚本
#! /bin/bash
ls ok
ls
---
- hosts: httpd
tasks:
- name:
script: ba.sh //指定任务失败条件
register: result
failed_when: "'没有那个文件或目录' in result.stdout"
- debug:
var: result
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [httpd] *********************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.58.20]
TASK [script] ******************************************************************
fatal: [192.168.58.20]: FAILED! => {"changed": true, "failed_when_result": true, "rc": 0, "stderr": "Shared connection to 192.168.58.20 closed.\r\n", "stderr_lines": ["Shared connection to 192.168.58.20 closed."], "stdout": "ls: 无法访问'789': 没有那个文件或目录\r\nanaconda-ks.cfg\r\n", "stdout_lines": ["ls: 无法访问'789': 没有那个文件或目录", "anaconda-ks.cfg"]}
PLAY RECAP *********************************************************************
192.168.58.20 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
也可以使用fail模块来完成
---
- hosts: httpd
tasks:
- name:
script: ba.sh
register: result
- name:
fail:
msg: "wu"
when: "'没有那个文件或目录' in result.stdout"
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [192.168.58.20] *********************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.58.20]
TASK [script] ******************************************************************
changed: [192.168.58.20]
TASK [fail] ********************************************************************
fatal: [192.168.58.20]: FAILED! => {"changed": false, "msg": "wu"} //错误代码
PLAY RECAP *********************************************************************
192.168.58.20 : ok=2 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
4.3 Ansible块和错误处理
在playbook中,块是对任务进行逻辑分组的子句,可用于控制任务的执行方式。也可结合rescue和always语句来处理错误。如果块中的任何任务失败,则执行其rescue块中的任务来进行恢复。在block子句中的任务以及rescue子句中的任务(如果出现故障)运行之后,always子句中的任务运行。
block:定义要运行的主要任务
rescue:定义要在block子句中定义的任务失败时运行的任务
always:定义始终都独立运行的任务,不论block和rescue子句中定义的任务是成功还是失败
[root@server playbook]# cat testing.yml
---
- hosts: httpd
tasks:
- name:
block:
- name: block
shell: echo 'block'
rescue:
- name: rescue
shell: echo 'rescue'
always:
- name: always
shell: echo 'always'
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [httpd] ****************************************************************************
TASK [Gathering Facts] ******************************************************************
ok: [192.168.58.20]
TASK [block] ****************************************************************************
changed: [192.168.58.20]
TASK [always] ***************************************************************************
changed: [192.168.58.20]
PLAY RECAP ******************************************************************************
192.168.58.20 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
当block出错时,resue就会运行
5.文件管理
常用文件模块
参数 | 注释 |
---|---|
blockinfile | 插入、更新或删除由可自定义标记线包围的多行文本块 |
copy | 将文件从本地或远程计算机复制到受管主机上的某个位置。 类似于file模块,copy模块还可以设置文件属性,包括SELinux上下文件。 |
fetch | 此模块的作用和copy模块类似,但以相反方式工作。此模块用于从远程计算机获取文件到控制节点, 并将它们存储在按主机名组织的文件树中。 |
file | 设置权限、所有权、SELinux上下文以及常规文件、符号链接、硬链接和目录的时间戳等属性。 此模块还可以创建或删除常规文件、符号链接、硬链接和目录。其他多个与文件相关的 模块支持与file模块相同的属性设置选项,包括copy模块。 |
lineinfile | 确保特定行位于某文件中,或使用反向引用正则表达式来替换现有行。 此模块主要在用户想要更改文件的某一行时使用。 |
stat | 检索文件的状态信息,类似于Linux中的stat命令。 |
synchronize | 围绕rsync命令的一个打包程序,可加快和简化常见任务。 synchronize模块无法提供对rsync命令的完整功能的访问权限,但确实最常见的调用更容易实施。 用户可能仍需通过run command模块直接调用rsync命令。 |
5.1file模块
在受管主机上创建、复制、编辑和删除文件是用户可以使用Files模块库中的模块实施的常见任务。
[root@server playbook]# cat testing.yml
---
- hosts: httpd
tasks:
- name:
file:
path: /usr/xx
owner: xu
group: xu
mode: 0544
state: touch
setype: samba_share_t
执行
[root@server playbook]# ansible-playbook testing.yml
PLAY [httpd] *********************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.58.20]
TASK [file] ********************************************************************
changed: [192.168.58.20]
PLAY RECAP *********************************************************************
192.168.58.20 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
//查看
[root@server usr]# ls -Z xu
unconfined_u:object_r:samba_share_t:s0 xu
5.2copy
copy模块用于将位于控制节点的文件复制到选定的受管主机。
[root@server playbook]# cat testing.yml
---
- hosts: httpd
tasks:
- name:
copy:
src: /etc/ansible/nu
dest: /opt
查看
[root@server opt]# ls
nu xu
5.3 synchronize
synchronize模块是一个围绕rsync工具的打包程序,它简化了playbook中的常见文件管理任务
---
- hosts: httpd
tasks:
- name:
yum:
name: rsync
state: present
- name:
synchronize:
src: playbook
dest: /etc/ansible/playbook
//查看
[root@server ansible]# ls
ansible.cfg facts.d hosts inventory nu playbook roles
[root@server ansible]# cd playbook/
[root@server playbook]# ls
playbook
[root@server playbook] cd playbook
[root@server playbook]# ls
firewalld.yml for.yml testing.yml