块、救援与收尾三段式
把一组任务包成块后,可以给它配两段处理逻辑:救援段在块内任一任务失败时执行,收尾段不论成功失败都会执行。这相当于给一组操作加上异常处理与收尾动作:前半段做变更,救援段做回滚或记录失败,收尾段做清理。它让“失败之后怎么办”从依赖人工判断变成剧本里的固定流程。
基本信息
| 结构 | 块、救援、收尾三段,救援与收尾可只写其中之一 |
|---|---|
| 救援触发 | 块内任一任务在该主机上失败时执行 |
| 收尾触发 | 无论块是否失败都执行,用于清理与记录 |
| 变量可见性 | 块内任务注册的变量在救援段可见,即使该任务失败也已写入 |
| 失败主机 | 救援段成功完成后,该主机继续参与同一 play 的后续任务 |
| 嵌套 | 块可嵌套,内层救援先处理,外层处理未被恢复的失败 |
典型使用场景
- 带回滚的发布:新版本部署失败时自动切回上一版并恢复配置
- 配置变更保护:改配置前后做校验,校验不通过就恢复备份文件
- 临时资源清理:无论成败都删除临时目录与临时账号
- 失败留证:救援段收集日志、快照关键状态,便于事后分析
- 可降级组件:可选组件安装失败时记录并继续,不阻塞主流程
- 批量变更隔离:把高风险操作包成一个单元,失败只影响该单元
回滚与清理写法
回滚与清理写法
- name: 带回滚的发布
block:
- name: 备份当前版本
ansible.builtin.copy:
src: /opt/app/current
dest: /opt/app/previous
remote_src: true
mode: "0755"
- name: 切换新版本
ansible.builtin.file:
src: "/opt/app/releases/{{ app_version }}"
dest: /opt/app/current
state: link
register: switch_result
- name: 校验新版本可用
ansible.builtin.command: /opt/app/current/bin/app --selftest
changed_when: false
rescue:
- name: 回滚到上一版
ansible.builtin.file:
src: /opt/app/previous
dest: /opt/app/current
state: link
- name: 记录失败信息
ansible.builtin.debug:
msg: "发布失败,已回滚;失败详情 {{ switch_result.msg | default('见上方输出') }}"
- name: 回滚后仍然报错,避免整体被误判为成功
ansible.builtin.fail:
msg: "发布失败且已回滚,请人工确认"
always:
- name: 清理临时目录,无论成败
ansible.builtin.file:
path: /tmp/app-deploy
state: absent三段职责
| 段落 | 执行时机 | 典型内容 |
|---|---|---|
| 块 | 正常流程 | 变更动作与校验 |
| 救援 | 块内失败时 | 回滚、降级、记录失败信息 |
| 收尾 | 始终执行 | 清理临时资源、写状态标记 |
| 嵌套内层 | 内层失败时 | 局部处理,例如单项降级 |
| 嵌套外层 | 内层未恢复时 | 统一回滚整批变更 |
救援段成功执行后,该主机会继续参与同一 play 的后续任务,这是“局部失败不再扩散”的基础。但如果只是吞掉错误而不上报,整体会被判定为成功——需要显式中止时,在救援段末尾主动报错。
救援段自己要足够健壮:回滚动作引用了不存在的备份、清理动作依赖已被删掉的对象,都会让救援段失败,此时该主机整体失败且可能留在中间状态。救援段的实现应尽量简单、只依赖确定存在的资源。
什么时候值得用
当一组操作满足“失败后必须有人做点什么”(回滚、清理、留证)时,用三段式把这些动作固化成流程;如果失败后本来就应该停下来等人工处理,救援段只写记录与显式失败即可,不必强行设计自动回滚——不可靠的自动回滚比不回滚更危险。