跳转至

Ansible剧本2

一. 异常中断

1. 异常中断基础

1) 简介

对于ansible-playbook来说,默认情况下多任务列表在执行过程中,只要有其中一部分任务执行失败,那么整个任务就意外中断了

对于有些业务场景来说,某个子动作运行失败不影响整个任务的效果,所以我们希望有一种方式能够避免掉这种异常终止任务的发送

对于异常中断的详细,根据业务承受能力不同主要有两种处理方式:强制正常和忽略错误

2) 强制正常

playbook的任务能否按顺序执行下去,主要根据的是人物之间的指令的执行状态返回码来判断。对于一个命令来说如果正常执行,他的状态返回码为0.命令执行失败的话,状态返回码非0.我们可以结合逻辑流程语句中的"||"来规避这种场景

3) 忽略错误

playbook还有一种专门在处理任务列表意外终端的属性值ignore_errors,从本质来说他还是基于命令状态返回码来判断任务是否异常。只不过他采取的处理措施是ansible-playbook内置属性值。它存在的意义就是,他能容忍某些任务出现异常的情况。

不论哪种方法来避免任务以外的中断,前提都是,我们指定有可能发送意外中断的任务是不影响大局的

2. 异常中断定制实践

1) 定制playbook文件

vi /root/playbook/nginxinstall-playbook.yaml
- 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. 指定执行任务

执行部分标签的任务:

ansible-playbook nginxinstall-playbook.yaml --tags "创建用户"

表示只执行标签为 创建用户 的任务,其余任务不执行。

5. 跳过指定任务

跳过部分标签的任务:

ansible-playbook nginxinstall-playbook.yaml --skip-tags "清理原仓库"

表示跳过标签为 清理原仓库 的任务,其他任务照常执行。

6. 适用场景总结

标签的作用主要有三点:

  • 只执行某一类任务,方便调试
  • 跳过不需要的任务,节省时间
  • 对大型剧本进行分组管理,结构更清晰

7. 重点理解

标签不是任务的必需属性,它只是给任务增加了一层“分类管理”的能力。

所以在操作剧本时,标签就像是给任务贴上了“用途标签”,让我们可以在执行时更灵活地控制流程。

8. 一句话总结

标签就是给 Ansible 任务打分类,之后可以通过 --tags 或 --skip-tags 来控制执行哪些任务、跳过哪些任务。