Ansible剧本2¶
一. 异常中断¶
1. 异常中断基础¶
1) 简介¶
对于ansible-playbook来说,默认情况下多任务列表在执行过程中,只要有其中一部分任务执行失败,那么整个任务就意外中断了
对于有些业务场景来说,某个子动作运行失败不影响整个任务的效果,所以我们希望有一种方式能够避免掉这种异常终止任务的发送
对于异常中断的详细,根据业务承受能力不同主要有两种处理方式:强制正常和忽略错误
2) 强制正常¶
playbook的任务能否按顺序执行下去,主要根据的是人物之间的指令的执行状态返回码来判断。对于一个命令来说如果正常执行,他的状态返回码为0.命令执行失败的话,状态返回码非0.我们可以结合逻辑流程语句中的"||"来规避这种场景
3) 忽略错误¶
playbook还有一种专门在处理任务列表意外终端的属性值ignore_errors,从本质来说他还是基于命令状态返回码来判断任务是否异常。只不过他采取的处理措施是ansible-playbook内置属性值。它存在的意义就是,他能容忍某些任务出现异常的情况。
不论哪种方法来避免任务以外的中断,前提都是,我们指定有可能发送意外中断的任务是不影响大局的
2. 异常中断定制实践¶
1) 定制playbook文件¶
- name: 部署nginx
hosts: "2"
become: yes
gather_facts: yes
tasks:
- name: create new user
user: name=nginx-test system=yes uid=82 shell=/sbin/nologin
- name: test new user
shell: getend passwd|grep nginx-test
- name: 禁用 SELinux
ansible.posix.selinux:
state: disabled
- name: 创建用户
user:
name: www
uid: 66
system: yes
state: present
shell: /sbin/nologin
comment: nginx进程维护用户
tags: 创建用户
- name: 清理原仓库
shell: rm -rf /etc/yum.repos.d/*.repo
tags: 清理原仓库
- name: 创建挂载点
file:
path: /media/cdrom/
state: directory
- name: 创建nginx配置文件存放目录
file:
path: /etc/nginx/conf.d/
state: directory
owner: www
group: www
- name: 创建nginx虚拟主机根目录
file:
path: /data/nginx/html/
state: directory
owner: www
group: www
- name: 挂载光盘
mount:
path: /media/cdrom/
src: /dev/sr0
state: mounted
fstype: iso9660
- name: 定义新的yum仓库AppStream
yum_repository:
name: AppStream
description: 光盘仓库之一
file: a.repo
baseurl: file:///media/cdrom/AppStream/
enabled: yes
gpgcheck: no
state: present
- name: 定义新的yum仓库BaseOS
yum_repository:
name: BaseOS
description: 光盘仓库之一
file: a.repo
baseurl: file:///media/cdrom/BaseOS/
enabled: yes
gpgcheck: no
state: present
- name: 安装nginx
dnf:
name: nginx
state: present
update_cache: yes
disable_gpg_check: yes
- name: 复制主配置文件
copy:
src: /root/nginx.conf
dest: /etc/nginx/nginx.conf
force: yes
- name: 复制辅助配置文件
copy:
src: /root/a.conf
dest: /etc/nginx/conf.d/
- name: 写入nginx虚拟主机根目录
copy:
dest: /data/nginx/html/index.html
content: "<h1>Hello Ansible!</h1>"
- name: 启动服务并设置开机自启动
service:
name: nginx
state: started
enabled: yes
注意: 我们在create new user下面添加一个test new user的任务,该任务肯定会失败
2) 此时执行剧本文件¶
[root@my-project playbook]# ansible-playbook /root/playbook/nginxinstall-playbook.yaml
...
TASK [test new user] **********************************************************************************************
fatal: [2]: FAILED! => {"changed": true, "cmd": "getend passwd|grep nginx-test", "delta": "0:00:00.005507", "end": "2026-08-22 09:42:47.379706", "msg": "non-zero return code", "rc": 1, "start": "2026-08-22 09:42:47.374199", "stderr": "/bin/sh:行1: getend:未找到命令", "stderr_lines": ["/bin/sh:行1: getend:未找到命令"], "stdout": "", "stdout_lines": []}
PLAY RECAP ********************************************************************************************************
2 : ok=2 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
3) 之后我们在终端上执行正常命令¶
[root@my-project playbook]# ansible -m shell -a 'getent passwd|grep nginx-test' "2"
2 | CHANGED | rc=0 >>
nginx-test:x:82:82::/home/nginx-test:/sbin/nologin
会发现该任务不影响前面任务正常运行
4) 解决异常,强制正常¶
a. 可以在不重要任务命令后面加上一个逻辑表达式,实现无论该任务运行是否正常,都会不影响后续任务的运行,修改的部分如下
...
tasks:
- name: create new user
user: name=nginx-test system=yes uid=82 shell=/sbin/nologin
- name: test new user
shell: getend passwd|grep nginx-test || /bin/true
- name: 禁用 SELinux
...
TASK [test new user] **********************************************************************************************
changed: [2]
现在就可以看到该任务通过了
b. 测试效果
[root@my-project playbook]# ansible -m shell -a "getent passwd | grep 82" "2"
2 | CHANGED | rc=0 >>
nginx-test:x:82:82::/home/nginx-test:/sbin/nologin
[root@my-project playbook]# ansible -m shell -a "ss -tuanp|grep nginx" "2"
2 | CHANGED | rc=0 >>
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=8943,fd=7),("nginx",pid=8942,fd=7),("nginx",pid=8941,fd=7))
tcp LISTEN 0 511 0.0.0.0:10088 0.0.0.0:* users:(("nginx",pid=8943,fd=6),("nginx",pid=8942,fd=6),("nginx",pid=8941,fd=6))
tcp LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=8943,fd=8),("nginx",pid=8942,fd=8),("nginx",pid=8941,fd=8))
c. 清除环境
[root@my-project docs]# ansible -m service -a "name=nginx state=stopped" "2"
2 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"name": "nginx",
"state": "stopped",
"status": {
...
[root@my-project docs]# ansible -m dnf -a "name=nginx,nginx-common state=absent" "2"
2 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"msg": "",
"rc": 0,
"results": [
"Removed: nginx-2:1.20.1-22.el9_6.2.x86_64"
]
}
[root@my-project docs]# ansible -m file -a "path=/data/webserver state=absent" "2"
2 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"path": "/data/webserver",
"state": "absent"
}
[root@my-project docs]# ansible -m user -a "name=nginx-test state=absent" "2"
2 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"force": false,
"name": "nginx-test",
"remove": false,
"state": "absent"
}
5) 解决异常,忽略错误¶
除了在命令后加上|| /bin/true这个方式强制正常以外,我们还可以在不重要的任务后面增加一个ignore_errors配置项,这样就可以忽略当期项的报错问题
注意
- ignore_errors配置项如果放到tasks字段下,表示针对所有的子任务
- ignore_errors配置项如果放到一个具体子任务下,表示针对的是具体的当前子任务
改写playbook
...
tasks:
- name: create new user
user: name=nginx-test system=yes uid=82 shell=/sbin/nologin
- name: test new user
shell: getend passwd|grep nginx-test
ignore_errors: True
...
随后我们执行命令ansible-playbook /root/playbook/nginxinstall-playbook.yaml查看
TASK [test new user] ************************************************************************************************************************************************************************************
fatal: [2]: FAILED! => {"changed": true, "cmd": "getend passwd|grep nginx-test", "delta": "0:00:00.006635", "end": "2026-08-23 10:16:24.256791", "msg": "non-zero return code", "rc": 1, "start": "2026-08-23 10:16:24.250156", "stderr": "/bin/sh:行1: getend:未找到命令", "stderr_lines": ["/bin/sh:行1: getend:未找到命令"], "stdout": "", "stdout_lines": []}
...ignoring
可以看到对应任务虽然报错但是被忽略了
二. 任务依赖¶
1. 修改文件¶
以如上场景中nginx服务的剧本文件为例我们把a.conf中的端口修改sed -i 's@listen 10088;@listen 10018;@' a.conf
然后重新执行剧本ansible-playbook nginxinstall-playbook.yaml
查看端口占用情况
[root@my-project playbook]# ansible -m shell -a 'ss -tuanp|grep nginx' "2"
2 | CHANGED | rc=0 >>
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=17598,fd=7),("nginx",pid=17597,fd=7),("nginx",pid=17596,fd=7))
tcp LISTEN 0 511 0.0.0.0:10088 0.0.0.0:* users:(("nginx",pid=17598,fd=6),("nginx",pid=17597,fd=6),("nginx",pid=17596,fd=6))
tcp LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=17598,fd=8),("nginx",pid=17597,fd=8),("nginx",pid=17596,fd=8))
[root@my-project playbook]# ansible -m shell -a 'cat /etc/nginx/conf.d/a.conf' "2"
2 | CHANGED | rc=0 >>
server {
listen 10018;
server_name 192.168.100.100;
root /data/nginx/html/;
index index.html;
}
我们看到此时的端口占用依旧是10088,而非10018。但是配置文件改完了
2. 解析原因¶
在我们修改配置文件后再执行playbook.yaml时,即使ansible-playbook发现配置文件发生了变动,但是因为任务列表中的指令没有发生变化,ansible执行的时候就会认为整个过程都没有发生变化,所以并没有真正的重新执行指令
对于这种内容发生变化,服务应该重启的场景在playbook中我们称之为“关系依赖”。在playbook中有两个选项可以满足我们的需要
- notify: 监控我们指定的动作设计的内容是否发生了变化
- handlers: 接收到变化动作后,我们自动执行的其他操作命令
注意:
由于notify涉及到的动作是由handlers创建的。所以在实践过程中,应该先写handlers,再在notify调用
3. 两种解决依赖的方式¶
handlers样式:
- name: 部署nginx
hosts: "2"
become: yes
gather_facts: yes
tasks:
...
handlers:
- name: 重启nginxs
service:
name: nginx
status: restarted
配置详解:
handlers和tasks是同一级别的属性
handlers中定义的一系列触发动作,动作名称必须唯一
每一个触发动作的定义格式与task的动作格式完全一致
notify样式
- name: 部署nginx
hosts: "2"
become: yes
gather_facts: yes
tasks:
...
- name: 复制辅助配置文件
copy:
src: ./a.conf
dest: /etc/nginx/conf.d/
notify: 重启nginx
...
配置详解:
- notify是task的一个子属性
- notify的格式是
notify: handler名称 - notify在执行的时候必须指定存在中的hanlder,两者不能分开
4. 解决以来的配置文件¶
最终的配置文件如下所示
- name: 部署nginx
hosts: "2"
become: yes
gather_facts: yes
tasks:
- name: create new user
user: name=nginx-test system=yes uid=82 shell=/sbin/nologin
- name: test new user
shell: getend passwd|grep nginx-test
ignore_errors: True
- name: 禁用 SELinux
ansible.posix.selinux:
state: disabled
- name: 创建用户
user:
name: www
uid: 66
system: yes
state: present
shell: /sbin/nologin
comment: nginx进程维护用户
tags: 创建用户
- name: 清理原仓库
shell: rm -rf /etc/yum.repos.d/*.repo
tags: 清理原仓库
- name: 创建挂载点
file:
path: /media/cdrom/
state: directory
- name: 创建nginx配置文件存放目录
file:
path: /etc/nginx/conf.d/
state: directory
owner: www
group: www
- name: 创建nginx虚拟主机根目录
file:
path: /data/nginx/html/
state: directory
owner: www
group: www
- name: 挂载光盘
mount:
path: /media/cdrom/
src: /dev/sr0
state: mounted
fstype: iso9660
- name: 定义新的yum仓库AppStream
yum_repository:
name: AppStream
description: 光盘仓库之一
file: a.repo
baseurl: file:///media/cdrom/AppStream/
enabled: yes
gpgcheck: no
state: present
- name: 定义新的yum仓库BaseOS
yum_repository:
name: BaseOS
description: 光盘仓库之一
file: a.repo
baseurl: file:///media/cdrom/BaseOS/
enabled: yes
gpgcheck: no
state: present
- name: 安装nginx
dnf:
name: nginx
state: present
update_cache: yes
disable_gpg_check: yes
- name: 复制主配置文件
copy:
src: ./nginx.conf
dest: /etc/nginx/nginx.conf
force: yes
- name: 复制辅助配置文件
copy:
src: ./a.conf
dest: /etc/nginx/conf.d/
notify: 重启nginx
- name: 写入nginx虚拟主机根目录
copy:
dest: /data/nginx/html/index.html
content: "<h1>Hello Ansible!</h1>"
- name: 启动服务并设置开机自启动
service:
name: nginx
state: started
enabled: yes
handlers:
- name: 重启nginxs
service:
name: nginx
state: restarted
检查文件
[root@my-project playbook]# ansible-playbook nginxinstall-playbook.yaml --syntax-check
playbook: nginxinstall-playbook.yaml
[root@my-project playbook]# ansible-playbook nginxinstall-playbook.yaml -C
PLAY [部署nginx] **************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************
ok: [2]
TASK [create new user] ********************************************************************************************************
ok: [2]
TASK [test new user] **********************************************************************************************************
skipping: [2]
TASK [禁用 SELinux] ***********************************************************************************************************
ok: [2]
TASK [创建用户] ***************************************************************************************************************
ok: [2]
TASK [清理原仓库] *************************************************************************************************************
skipping: [2]
TASK [创建挂载点] *************************************************************************************************************
ok: [2]
TASK [创建nginx配置文件存放目录] **********************************************************************************************
ok: [2]
TASK [创建nginx虚拟主机根目录] ************************************************************************************************
ok: [2]
TASK [挂载光盘] ***************************************************************************************************************
ok: [2]
TASK [定义新的yum仓库AppStream] ***********************************************************************************************
ok: [2]
TASK [定义新的yum仓库BaseOS] **************************************************************************************************
ok: [2]
TASK [安装nginx] **************************************************************************************************************
ok: [2]
TASK [复制主配置文件] *********************************************************************************************************
ok: [2]
TASK [复制辅助配置文件] *******************************************************************************************************
ok: [2]
TASK [写入nginx虚拟主机根目录] ************************************************************************************************
ok: [2]
TASK [启动服务并设置开机自启动] ***********************************************************************************************
ok: [2]
PLAY RECAP ********************************************************************************************************************
2 : ok=15 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
使用命令 ansible-playbook nginxinstall-playbook.yaml 重新执行playbook
[root@my-project playbook]# ansible-playbook nginxinstall-playbook.yaml
...
TASK [复制辅助配置文件] ********************************************************************************************************
changed: [2]
...
RUNNING HANDLER [重启nginx] ****************************************************************************************************
changed: [2]
PLAY RECAP *********************************************************************************************************************
2 : ok=18 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
查看验证
[root@my-project playbook]# ansible -m shell -a 'ss -tuanp|grep nginx' "2"
2 | CHANGED | rc=0 >>
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=35688,fd=7),("nginx",pid=35687,fd=7),("nginx",pid=35686,fd=7))
tcp LISTEN 0 511 0.0.0.0:10018 0.0.0.0:* users:(("nginx",pid=35688,fd=6),("nginx",pid=35687,fd=6),("nginx",pid=35686,fd=6))
tcp LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=35688,fd=8),("nginx",pid=35687,fd=8),("nginx",pid=35686,fd=8))
5. 触发多任务¶
我们可在notify和hanlers部分增加任务来触发多任务,如示例
- hosts: web
remote_user: root
tasks:
...
- name: copy subconfig
copy: src=nginx-define.conf dest=/etc/nginx/conf.d
notify:
- restart nginx
- check nginx
- name: start nginx
service: name=nginx state=started enabled=yes
handlers:
- name: restart nginx
service: name=nginx state=restarted
- name: check nginx
shell: ss -tuanp|grep nginx >/tmp/nginx.test
如上所示,可以利用notify和hanlers实行多任务
三. 标签¶
1. 简介¶
在 Ansible 的剧本中,标签(tags)可以为任务添加一个或多个分类名称,用来标识不同任务的功能模块。
通过标签,我们可以只执行某一类任务,或者跳过某一类任务,从而让剧本更灵活,便于调试和分阶段部署。
2. 标签的使用方式¶
- name: 创建用户
user:
name: www
uid: 66
system: yes
state: present
tags: 创建用户
- name: 清理原仓库
shell: rm -rf /etc/yum.repos.d/*.repo
tags: 清理原仓库
其中 tags 就是给任务添加标签。标签名可以自定义,例如:
- 创建用户
- 清理原仓库
- 安装软件
- 拷贝配置
- 启动服务
3. 常见场景¶
在真实部署中,任务通常会分成多个模块,比如:
- 初始化环境
- 创建用户
- 安装软件
- 配置文件
- 启动服务
- 检查状态
如果我们给这些任务打上不同的标签,那么在执行时就可以按需求控制。
4. 指定执行任务¶
执行部分标签的任务:
表示只执行标签为 创建用户 的任务,其余任务不执行。
5. 跳过指定任务¶
跳过部分标签的任务:
表示跳过标签为 清理原仓库 的任务,其他任务照常执行。
6. 适用场景总结¶
标签的作用主要有三点:
- 只执行某一类任务,方便调试
- 跳过不需要的任务,节省时间
- 对大型剧本进行分组管理,结构更清晰
7. 重点理解¶
标签不是任务的必需属性,它只是给任务增加了一层“分类管理”的能力。
所以在操作剧本时,标签就像是给任务贴上了“用途标签”,让我们可以在执行时更灵活地控制流程。
8. 一句话总结¶
标签就是给 Ansible 任务打分类,之后可以通过
--tags或--skip-tags来控制执行哪些任务、跳过哪些任务。