无责复盘——现在失败、修好后通过的措施
目标
用支付故障的记录,连同实际的行区分触发部署、根本原因、促成因素,并为每项措施编写“现在失败、修好后通过”的 验证命令。写出不含人名的复盘,真正修改生产配置,并纠正上季度验证在撒谎的措施。
为什么重要
复盘承诺的是“不会再发生”,而守住这一承诺的不是文字,而是措施。即使措施以“已完成”关闭, 只要验证抓不住缺陷,它就从未完成过。这个实验的评分器会在事故当时的副本、两份修好的副本、 只留下一个因素的副本上实际运行你的验证命令——现场中验证撒谎的所有方式(只看键、掩盖退出码、看旧副本或其他键、 钉死一个值)都会在这里被抓出来。这个实验按 60 分钟安排,与默认会话时长相同,所以开始时请先延长时间。
步骤
- 用
python3 /opt/fixtures/ic/ickit.py open postmortem展开素材,阅读/root/ic/postmortem/README.txt。把app.log中首个 ERROR 之前紧挨着进入的部署的提交 sha(前 7 个字符)以及该部署修改的配置文件,以deploy=<sha>、changed=<etc/...file>两行写入/root/ic/postmortem/answers/trigger.txt。 - 在
/root/ic/postmortem/answers/evidence.txt中写下三行:trigger: <trigger-line>(deploys.log 中指向触发部署的 commit 行或标题行)、first_error: <first-error-line>(app.log 中的首个 ERROR 行)、recovery: <resolved-line>(alerts.csv 中这起事故告警解除的那一行)。每一行都必须与素材中的原样一致。 - 把 README 中的团队标准、
/srv/shop/etc/中的配置以及部署记录对照起来,把一个根本原因(触发部署改到标准之外的东西)和两个促成因素(原本就在标准之外的东西)写入/root/ic/postmortem/answers/factors.txt。在原因行(root_cause: <code>或contributing: <code>)的正下方附上evidence: <line>。代码:alert-threshold-high(告警阈值过高)、cache-ttl-zero(缓存 TTL 为 0)、deploy-no-healthgate(部署健康检查关闭)、pool-too-small(连接池太小)、retry-no-backoff(重试没有等待)、timeout-too-long(网关超时太长)。 - 为根本原因和每个促成因素各写一项措施,写入
/root/ic/postmortem/answers/actions.txt。每项措施四行:action: <what>、owner: <team>、factor: <factor-code>、verify: <shell-command>。verify 要以环境变量$ROOT接收配置目录树(例:"$ROOT/etc/pool.conf"),该因素在团队标准之外时以非 0 代码结束,在标准之内时以 0 结束。评分器会在事故当时的副本(应当失败)、两份修好的副本(值不同也应当通过)、只留下该因素的副本(应当失败)上实际运行它。 - 在
/root/ic/postmortem/answers/postmortem.md中写复盘。必须有## Summary(摘要)、## Impact(影响)、## Root cause(原因)、## Actions(措施)四节(节名也接受韩文的## 요약、## 영향、## 원인、## 조치);影响一节写duration: <N>m(从首个 ERROR 到告警解除的分钟数,向下取整)和failed_requests: <M>(app.log 中 ERROR 的行数),原因一节写根本原因和促成因素的代码,措施一节为每项措施写一行verify:。任何一行都不写人名(频道账号)——只有引用素材、以evidence:开头的行例外。 - 修改生产配置
/srv/shop/etc/,把三个缺陷纳入团队标准之内(不要弄坏原本在标准之内的配置)。然后确认第 04 步的 verify 以ROOT=/srv/shop全部通过。 - 运行
incident ic-unverifiable会触发故障事件。/srv/billing/postmortems/2026-q3.md中的措施 A1、A2、A3 都是 done,/srv/billing/bin/verify-actions对三者都给出 PASS,可/srv/billing/etc/中的配置三者都在团队标准(/srv/billing/README.txt)之外。(1) 为每项措施修改 verify,使缺陷仍存在时失败、修好后通过(factor 和措施顺序不变);(2) 真正修改配置,让 verify-actions 诚实地给出 PASS。评分器会在事故当时的副本(包括旧副本etc/old/)、两份修好的副本、只留下该因素的副本上运行修改后的 verify。在/root/incident/ic-unverifiable/report.txt中写下A1: <code>、A2: <code>、A3: <code>三行以及evidence: <original-verify-line>。每项措施的代码是以下之一:always-passes(不看值,只看键是否存在)、exit-masked(在后面掩盖了失败的退出码)、flaky-timing(结果随时刻变化)、wrong-file(看的是服务不读取的文件)、wrong-key(看的是名称相似的另一个键)。
参考
- 素材工具:
python3 /opt/fixtures/ic/ickit.py open <name>会把素材展开到/root/ic/<name>/。素材用每个人不同的随机种子生成,评分器用同样的种子重新计算正确答案——别人的数字对不上。重新展开也是同样的素材,加上--fresh则从新的素材重新开始。 - 答案写在
/root/ic/<name>/answers/下。会话结束后就会消失,需要的话请另外保存。 - 事件工具:用
incident list查看可触发的事件及一行症状,用incident show <name>重新查看工单。再次触发同一事件,会用新的随机素材回到最初的故障状态(之前写的答案将不再正确)。 - 报告是
/root/incident/<name>/report.txt。每个对象写一行<target>: <cause-code>,最后原样附上evidence: <line>。证据必须是素材或观察记录中原样存在的一行(20 个字符以上)。评分器先看修好后的实际状态,报告用于确认你的判断。 - verify 以
bash -c '<command>'的形式运行,环境变量ROOT中放入配置目录树路径,并以该目录树为当前目录。想亲自测试,就像ROOT=/srv/shop bash -c '<command>'; echo $?这样运行,查看退出码。 - 配置文件每行一条
key=value,同一个键出现多次时,下面的行胜出。awk -F= '$1=="key" {v=$2} END {exit !(<condition>)}' <file>这种形式符合这一规则。 - 两个常见错误——在验证末尾加上
|| echo …抹掉失败,以及把生产路径(/srv/shop)写死在验证中而无视$ROOT。
展开素材——找出触发部署
用 python3 /opt/fixtures/ic/ickit.py open postmortem 展开素材,阅读 /root/ic/postmortem/README.txt。把 app.log 中首个 ERROR 之前紧挨着进入的部署的提交 sha(前 7 个字符)以及该部署修改的配置文件,以 deploy=<sha>、changed=<etc/...file> 两行写入 /root/ic/postmortem/answers/trigger.txt。
app.log 是 UTC,git 的 Date 是提交者的时区。要找的不是“最近一次部署”,而是“首个错误之前紧挨着的部署”。部署提交正文中的 change: 行会告诉你改了哪个文件。
用实际的行写出时间线的三根支柱
在 /root/ic/postmortem/answers/evidence.txt 中写下三行:trigger: <trigger-line>(deploys.log 中指向触发部署的 commit 行或标题行)、first_error: <first-error-line>(app.log 中的首个 ERROR 行)、recovery: <resolved-line>(alerts.csv 中这起事故告警解除的那一行)。每一行都必须与素材中的原样一致。
把用 grep 找到的行原样粘贴(带上 grep -n 的行号前缀也可以)。相似的其他行或手工改过的行会不通过。
根本原因与促成因素
把 README 中的团队标准、/srv/shop/etc/ 中的配置以及部署记录对照起来,把一个根本原因(触发部署改到标准之外的东西)和两个促成因素(原本就在标准之外的东西)写入 /root/ic/postmortem/answers/factors.txt。在原因行(root_cause: <code> 或 contributing: <code>)的正下方附上 evidence: <line>。代码:alert-threshold-high(告警阈值过高)、cache-ttl-zero(缓存 TTL 为 0)、deploy-no-healthgate(部署健康检查关闭)、pool-too-small(连接池太小)、retry-no-backoff(重试没有等待)、timeout-too-long(网关超时太长)。
在标准之内的配置不是缺陷(诱饵)。证据要选显示该因素的日志、部署记录、告警中的行——配置文件中的一行太短,不能作为证据。
可验证的措施
为根本原因和每个促成因素各写一项措施,写入 /root/ic/postmortem/answers/actions.txt。每项措施四行:action: <what>、owner: <team>、factor: <factor-code>、verify: <shell-command>。verify 要以环境变量 $ROOT 接收配置目录树(例:"$ROOT/etc/pool.conf"),该因素在团队标准之外时以非 0 代码结束,在标准之内时以 0 结束。评分器会在事故当时的副本(应当失败)、两份修好的副本(值不同也应当通过)、只留下该因素的副本(应当失败)上实际运行它。
配置是 key=value,同一个键再次出现时下面的行胜出。只看键是否存在、把一个值按字面钉死、用 || 掩盖退出码,都会不通过。用 awk -F= 记住最后的值,在 END 中与标准比较后 exit,这种形式很方便。亲自用 ROOT=/srv/shop bash -c '<command>'; echo $? 运行试试。
撰写无责复盘
在 /root/ic/postmortem/answers/postmortem.md 中写复盘。必须有 ## Summary(摘要)、## Impact(影响)、## Root cause(原因)、## Actions(措施)四节(节名也接受韩文的 ## 요약、## 영향、## 원인、## 조치);影响一节写 duration: <N>m(从首个 ERROR 到告警解除的分钟数,向下取整)和 failed_requests: <M>(app.log 中 ERROR 的行数),原因一节写根本原因和促成因素的代码,措施一节为每项措施写一行 verify:。任何一行都不写人名(频道账号)——只有引用素材、以 evidence: 开头的行例外。
原因不写“是谁”,而写“是什么”(配置、流程、缺少检查)。数字要从素材中重新统计。负责方写团队名称。
真正执行措施
修改生产配置 /srv/shop/etc/,把三个缺陷纳入团队标准之内(不要弄坏原本在标准之内的配置)。然后确认第 04 步的 verify 以 ROOT=/srv/shop 全部通过。
同一个键出现多次时,服务使用下面的行——无论是改那一行还是在末尾追加,只要最后的值在标准之内即可。修改后亲自运行 verify 看退出码。
故障事件——“验证通过”,同样的故障却又发生了
运行 incident ic-unverifiable 会触发故障事件。/srv/billing/postmortems/2026-q3.md 中的措施 A1、A2、A3 都是 done,/srv/billing/bin/verify-actions 对三者都给出 PASS,可 /srv/billing/etc/ 中的配置三者都在团队标准(/srv/billing/README.txt)之外。(1) 为每项措施修改 verify,使缺陷仍存在时失败、修好后通过(factor 和措施顺序不变);(2) 真正修改配置,让 verify-actions 诚实地给出 PASS。评分器会在事故当时的副本(包括旧副本 etc/old/)、两份修好的副本、只留下该因素的副本上运行修改后的 verify。在 /root/incident/ic-unverifiable/report.txt 中写下 A1: <code>、A2: <code>、A3: <code> 三行以及 evidence: <original-verify-line>。每项措施的代码是以下之一:always-passes(不看值,只看键是否存在)、exit-masked(在后面掩盖了失败的退出码)、flaky-timing(结果随时刻变化)、wrong-file(看的是服务不读取的文件)、wrong-key(看的是名称相似的另一个键)。
方向:验证撒谎的途径有好几种——读的是什么(文件、键)、和什么比较(是否真的看了值)、退出码是否一直保留到最后。为每项措施看看三者中哪里出了偏差。 工具:用 ROOT=/srv/billing bash -c '<command>'; echo $? 亲自运行原来的 verify、README 中的标准、grep -n。 具体:先修改 verify,亲眼看到它在当前配置下失败,然后修改配置让它通过。顺序反过来,就无法知道验证能否抓住缺陷。