无责复盘
一句话总结
复盘的目的是让同样的事故不再发生,而守住这一承诺的是行动项。所以好的复盘把原因写成系统而不是 人,用实际记录中的行支撑每一个论断,并为每一项措施附上“现在失败、修好后通过”的 验证命令。
为什么需要它
Google SRE 一书第 15 章(Postmortem Culture)强调无责复盘。原因很简单:把人写成原因,下次 事故时就没人说出事实,没有事实就找不到原因。“部署的人犯了错”这句话什么也修不好 ——让那个错误成为可能的系统(没有配置审查的部署、被关掉的健康检查、响得太晚的告警)依然存在。
还有更安静的失败:行动项以“已完成”关闭,同样的事故却再次发生。打开一看,验证只检查了“键是否存在”,
或者用 || true 盖住了失败,又或者检查的是服务并不读取的旧副本。验证抓不住缺陷,那项措施就
从未完成过。
它如何工作
区分原因。 触发(trigger)是引发事故的变更——通常是一次部署。根本原因(root cause)是那次变更造成的、 没有它就不会有事故的缺陷。例如,部署 v3.2.0 是触发,而那次部署把连接池从 64 减到 5 是根本 原因。回滚部署,事故会停止,但“连接池大小可以未经审查就被改动”这一根本原因依然存在。措施要针对根本原因和促成因素。促成因素(contributing factor)是原本就存在的缺陷,它们让事故被察觉得更晚(过高的 告警阈值)、无法阻止(被关掉的健康检查)或被放大(没有等待的重试)。把三者混在一起,措施就会用错地方。
证据以行的形式附上。 不写“首个错误大约在 06:24”,而是原样附上日志中的那一行。任何人都必须能重新找到同一行加以 确认,编造得像模像样的行会在验证中被抓出来。
措施有三要素: 负责方(团队而不是个人)、改什么,以及验证命令。验证必须具备三种性质。
| 性质 | 含义 | 常见的假验证 |
|---|---|---|
| 现在会失败 | 能抓住缺陷仍然存在 | 只看键是否存在,用 ` |
| 修好后会通过 | 也接受其他正确的修法 | 把一个值钉死的 grep -x |
| 对自身因素敏感 | 看的是自己的缺陷,而不是别人的 | 检查相似的其他键、旧副本 |
把验证写成以参数($ROOT)接收目标目录树,就能同样地在生产配置、事故当时的副本、修好后的副本上运行。
一个行动项是这样的(示例中的键和阈值只展示格式)。
action: 세션 저장소 연결 수를 팀 기준 이상으로
owner: platform-team
factor: example-too-small
verify: awk -F= '$1=="max_sessions" {v=$2} END {exit !(v != "" && v+0 >= 100)}' "$ROOT/etc/session.conf"
awk -F= 把 key=value 分成两部分,同一个键出现多次时,覆盖 v 使最后的值留下——这与服务读取配置
的方式相同。在 END 中与阈值比较,用退出码作答,所以只要不接在管道后面、不用 || echo 包起来,那个
结果就直接是验证结果。把这一行分别以 ROOT=<incident-copy>、ROOT=<fixed-copy> 运行一遍,几秒钟就能知道验证
是否诚实。
现场遇到的情形
在季度评审会议上,措施列表全是绿色,同样的事故却又发生了。打开一看,三项措施的验证各以不同的方式在撒谎。 修复的顺序总是一样的——先修验证,让它现在失败,揭示缺陷仍然存在这一事实, 然后真正修好系统,让验证诚实地通过。不先修验证,就连系统是否修好了都无法知道。
无责的写作也需要练习。把“A 把配置放错了”改写成“配置变更可以在没有审查和健康检查的情况下进入生产 环境”,句子所指向的修复对象就从人转移到了流水线。时间线中可以保留人的行动 ——不过那是为了理解“那个人凭当时掌握的信息为什么那样判断”的记录,而不是原因。
接下来的实验要做什么
用支付故障的日志、部署记录、告警,以实际的行找出触发部署和时间线的三根支柱,区分根本原因与促成因素,并为每项措施 编写验证命令。评分器会在事故当时的副本、两份修好的副本、只留下一个因素的副本上实际运行这些命令。写出不含人名的 复盘,真正修改生产配置,然后纠正上季度那些“验证通过”却仍发生同样故障的措施。