做一个跑两次也安静的 playbook
目标
将同一个 playbook 运行任意多次,并确保从第二次开始不再发生任何变更;同时用日志和报告证明这一点。
为什么重要
幂等性不是“代码好不好看”的问题,而是自动化能否真正投入使用的前提。只有确信第二次执行是安全的,才能通过 cron 定期运行来纠正漂移,也才能在流水线中途失败后放心重跑。反过来,如果 playbook 每次都会重启服务,人们自然会回避它;没人愿意使用的自动化,等于没有自动化。关键手段包括能识别当前状态的模块、shell 命令的保护条件(creates/changed_when),以及仅在发生变更时运行的 handler。尤其要记住,handler 会集中到 play 结束时才执行——如果 play 在中途失败,handler 可能完全不会运行,从而出现“配置已经改变,但服务仍在使用旧配置”的状态。
步骤
- 创建并运行
/root/ans/idem/site.yml,将输出保存到/root/ans/idem/out/run1.txt。首次运行的changed必须不小于 2。 - 再次运行同一个 playbook,将输出保存到
/root/ans/idem/out/run2.txt。这次必须是changed=0。 - 定义名为
reload app的 handler,并在处理配置的 task 中通过notify调用它。handler 运行时必须创建/root/ans/idem/artifacts/reload.marker。 - 第二次运行日志(
run2.txt)中不得出现RUNNING HANDLER,首次运行日志中则必须出现。 - 添加一个
command/shelltask,并设置creates保护条件。该 task 要向/root/ans/idem/artifacts/stamp.txt写入一行;即使运行两次,文件中也只能有一行。 - 对只做查询的 task 至少使用一次
changed_when,对需要自行判定失败的 task 至少使用一次failed_when。 - 使用
--check运行 playbook,并把输出保存到/root/ans/idem/out/check.txt。结果必须为changed=0、failed=0。 - 创建
/root/ans/idem/out/idempotency.json,其中包含四个键:run1_changed(不小于 2)、run2_changed(0)、handler_fired(1 或 true)以及verdict("idempotent")。
参考
- 每个实验都会启动全新的实验 Pod。如果
/root/ans/inventory/hosts.ini不存在,请先重新创建与第一个实验相同的 inventory(web1·web2·db1,ansible_host=127.0.0.1、ansible_port=2222、ansible_user=root,并在[prod:children]中加入 web·db)。结构可以参考/opt/lab/fixtures/ansible/inventory.sample.ini。 - 判定依据是 PLAY RECAP 行中的
changed=N。 - handler 名称与
notify字符串必须完全一致,即使只差一个字符也会被静默忽略。 - 常见错误 1:在模板中加入时间或随机值,导致每次渲染结果都不同。这样会永远产生
changed。 - 常见错误 2:使用
shell向文件追加内容(>>)却不设置保护条件。每次运行都会多出一行。
在首次运行中产生变更
创建并运行 /root/ans/idem/site.yml,将输出保存到 /root/ans/idem/out/run1.txt。首次运行的 changed 必须不小于 2。
用几个用于创建目录、文件和配置的 task 就足够了。请把完整执行结果保存到文件。
让第二次运行达到 changed=0
再次运行同一个 playbook,将输出保存到 /root/ans/idem/out/run2.txt。这次必须是 changed=0。
再次运行同一个 playbook。如果 changed 不是 0,请从日志中找出是哪一个 task 造成了变更。
定义 handler 并通过 notify 调用
定义名为 reload app 的 handler,并在处理配置的 task 中通过 notify 调用它。handler 运行时必须创建 /root/ans/idem/artifacts/reload.marker。
handler 名称必须与 notify 中的字符串完全一致。让 handler 运行后留下标记文件。
让第二次运行不触发 handler
第二次运行日志(run2.txt)中不得出现 RUNNING HANDLER,首次运行日志中则必须出现。
只有 task 状态为 changed 时才会触发 notify。第二次运行日志中不应出现 RUNNING HANDLER。
为 shell 命令添加 creates 保护条件
添加一个 command/shell task,并设置 creates 保护条件。该 task 要向 /root/ans/idem/artifacts/stamp.txt 写入一行;即使运行两次,文件中也只能有一行。
通过 creates 指明命令会创建的文件路径,后续运行就会跳过该命令。日志文件只能保留一行。
自行定义变更与失败判定
对只做查询的 task 至少使用一次 changed_when,对需要自行判定失败的 task 至少使用一次 failed_when。
只做查询的命令可设置 changed_when: false;需要自行判断失败的地方则使用 failed_when。
确认检查模式下也无变更
使用 --check 运行 playbook,并把输出保存到 /root/ans/idem/out/check.txt。结果必须为 changed=0、failed=0。
如果状态已经收敛,--check 运行也应得到 changed=0。不支持检查模式的模块可能会导致失败。
生成幂等性判定报告
创建 /root/ans/idem/out/idempotency.json,其中包含四个键:run1_changed(不小于 2)、run2_changed(0)、handler_fired(1 或 true)以及 verdict("idempotent")。
将两次运行的 changed 数量和 handler 触发次数整理为 JSON,并写入判定字符串。