跳到主要内容

幂等、预览与滚动变更

本章目标:让编排具备生产变更所需的三种能力——多跑无害(幂等)先看再改(check/diff)控制爆炸半径(serial/失败阈值)


1. 幂等:定义与自测

对同一批主机、同一份 playbook:

次数期望
第 1 次可能有若干 changed,达到目标状态
第 2 次几乎全是 ok / skipped,服务不被无故重启
第 N 次与第 2 次相同

自测:

ansible-playbook playbooks/web.yml -l web1 | tee /tmp/run1.log
ansible-playbook playbooks/web.yml -l web1 | tee /tmp/run2.log
# 对比 changed 数量
grep -c changed /tmp/run1.log
grep -c changed /tmp/run2.log

破坏幂等的常见写法

写法问题改法
每轮 state: restarted总是变更handler + 配置 changed 才重启
shell: echo x >> file重复追加lineinfile / template
state: latest 装包版本漂present + 钉版本
用时间戳写文件内容次次不同内容由稳定变量生成
changed_when 的 command总是 changed声明 changed_when: false 或准确条件

用 check_mode 辅助设计

编写角色时,想清楚每个 task 在 check mode 下是否合理。对「必须真实执行」的只读探测:

- name: 读取当前内核参数
ansible.builtin.command: sysctl net.ipv4.ip_forward
register: sysctl_out
changed_when: false
check_mode: false

2. check mode:预览

ansible-playbook playbooks/web.yml --check
ansible-playbook playbooks/web.yml --check --diff

含义:

  • 多数内置模块会模拟「若执行会怎样」
  • 不会保证 100% 与真实 apply 一致(尤其是依赖前序真实产物、下载、自定义脚本)
  • --diff 对 file/template/copy 特别有用,能直接看配置差异

推荐流程:

1. --check --diff -l 一台灰度机
2. 真实 apply -l 同一台
3. 业务/健康检查
4. serial 分批扩大

哪些任务在 check 下不准

  • command/shell 默认常被跳过或结果不可靠
  • 需要先安装包再 template validate 的链路
  • 依赖外部 API 返回值的逻辑

因此:check 通过 ≠ 可以闭眼全量,它是必要但不充分条件。


3. diff:把变更讲给人听

ansible-playbook playbooks/web.yml -l web1 --check --diff

输出中关注:

  • 哪些文件将变更
  • 删除/新增了哪些关键行(upstream、监听端口、鉴权)
  • 是否误扫到本不该管的文件

模板顶部加头注释,方便人工识别:

# {{ ansible_managed }}
# role=nginx source=templates/nginx.conf.j2

ansible_managed 默认含路径与时间等信息,可在 ansible.cfg 定制:

[defaults]
ansible_managed = Managed by Ansible - do not edit on server

4. 滚动与批次:serial

全量并行 reload/restart 是事故温床。

- name: 滚动 Web
hosts: web
become: true
serial: 1
max_fail_percentage: 25
any_errors_fatal: false

roles:
- nginx
写法含义
serial: 1一次一台
serial: "30%"每批约 30%
serial: [1, 2, 5]灰度节奏:先 1,再 2,再 5…
max_fail_percentage失败占比超阈值,中止后续批次
any_errors_fatal: true任一主机失败则整体快速失败

批次间插入验证

- name: 滚动并验证
hosts: web
become: true
serial: 1

roles:
- nginx

tasks:
- name: flush handlers 以便立刻 reload
ansible.builtin.meta: flush_handlers

- name: 本机健康检查
ansible.builtin.uri:
url: "http://127.0.0.1:{{ nginx_listen_port | default(80) }}/healthz"
status_code: 200
register: health
retries: 5
delay: 2
until: health.status == 200

retries 只有和 until 一起使用才会真正轮询。健康检查在次数耗尽后仍失败 → 该主机失败 → 触发 max_fail_percentage 保护,避免继续推坏配置。


5. 限流与并发相关旋钮

# ansible.cfg
[defaults]
forks = 10
# 临时
ansible-playbook playbooks/site.yml --forks 5
旋钮作用
forks全局并行度
serial每 play 批次
throttle(task 级)限制某任务并发
- name: 对外部 API 温和一点
ansible.builtin.uri:
url: https://api.example.com/reload
method: POST
throttle: 3

6. 失败策略与排障

- name: 允许部分失败继续(谨慎)
ansible.builtin.command: /usr/local/bin/optional-check
register: opt
failed_when: false
changed_when: false

- name: 明确失败条件
ansible.builtin.command: /usr/local/bin/must-pass
register: must
failed_when: must.rc != 0

从失败主机续跑:

ansible-playbook playbooks/web.yml --limit @/path/to/playbook.retry

(需未关闭 retry 文件;我们在 cfg 里曾关过 retry_files_enabled,生产可按需打开。)

更推荐:用 -l 显式列出失败主机重跑,并把失败原因记进变更单。


7. 变更窗口检查清单

上线前打印出来逐项勾:

  1. 目标-i-l 是否指向预期环境?
  2. 预览:是否做过 --check --diff
  3. 幂等:灰度机是否二次 apply 无多余 changed?
  4. 批次serial 是否设置?是否有批次间健康检查?
  5. 回滚:Git 是否可 revert?关键文件是否 backup: true
  6. 密钥:Vault 密码是否仅在本窗口可用?
  7. 观察:指标/日志面板是否打开?

8. 完整示例:带护栏的 web play

playbooks/web_safe.yml

---
- name: Safe web rollout
hosts: web
become: true
serial: [1, "30%", "100%"]
max_fail_percentage: 30

pre_tasks:
- name: 拒绝空组误跑
ansible.builtin.assert:
that:
- groups['web'] | length > 0
fail_msg: "web 组为空,停止"

roles:
- role: nginx

tasks:
- name: 立即应用 handler
ansible.builtin.meta: flush_handlers

- name: 健康检查
ansible.builtin.uri:
url: "http://127.0.0.1:{{ nginx_listen_port }}/healthz"
status_code: 200
register: hz
retries: 6
delay: 2
until: hz.status == 200
ansible-playbook playbooks/web_safe.yml -i inventory/staging.ini --check --diff
ansible-playbook playbooks/web_safe.yml -i inventory/staging.ini

9. 练习与验收

练习 A
给当前 nginx 角色连续 apply 两次,统计 changed;若第二次仍有 changed,找出并修掉。

练习 B
改一个监听端口,先 --check --diff 截图/保存输出,再真实 apply。

练习 C
serial: 1 跑两台主机,人为让第一台健康检查失败(改错 port),确认第二台不会被继续变更(配合 max_fail_percentage / 失败策略观察行为)。

验收清单

  • 能解释幂等被破坏的三种典型原因
  • 变更默认走 check → 灰度 → 分批
  • 会为 reload 类变更写健康检查

下一章:Vault 与密钥 —— 护栏齐了,还要把密码从明文仓库里拿掉。