事故复盘不是写检讨,而是让团队变强的一次机会
凌晨 2 点,监控突然开始报警。
接口 P99 从 200ms 飙到 5s,错误率一路上涨。值班同学冲进群里,开始查日志、重启服务、扩容、回滚。
两个小时后,服务终于恢复。
第二天上午,大家坐在会议室里开事故复盘。
然后有人问:
“这次事故到底是谁改的?”
如果复盘最后变成了这句话,那我觉得这次事故基本算白出了。
真正成熟的运维团队,应该慢慢完成一个转变:
从“事故发生了以后怎么解释”,走向“事故发生以后组织怎么变强”。
这也是我越来越认同的一件事情:
Postmortem 的终点,不应该是一份复盘文档,而应该是一次组织学习的开始。
一、很多公司的事故复盘,其实只是“事故作文”
我见过一些非常典型的事故复盘。
标题:
《XX系统 9月15日生产事故复盘报告》
内容大概是:
事故时间: 02:13
事故恢复: 03:47
事故影响: XX用户
事故原因: 某同学发布配置错误
处理方式: 回滚
责任人: XXX
整改措施: 加强发布审核
看起来特别完整。
但是过了三个月,同样的事故又发生了。
只是这次换了个人。
为什么?
因为这种复盘回答的是:
“上一次是谁做错了?”
而不是:
“为什么一个正常工作的工程师,能够这么容易把系统搞挂?”
这两个问题看起来差不多,实际上完全不同。
二、真正应该复盘的,不是“谁犯错”,而是“系统为什么允许错误发生”
假设生产环境有这样一条命令:
kubectl delete deployment payment-service -n prod
一个新人拿到了生产权限。
凌晨三点,他本来想删除测试环境 Deployment,结果 namespace 写错了。
系统挂了。
如果你的复盘结论是:
“新人操作失误,生产操作不规范。”
那你下一步大概率会做:
加强培训
加强教育
加强审批
加强责任意识
听起来都对。
但其实没有解决核心问题。
因为真正值得问的是:
为什么一个人的一个命令,就能让生产系统直接进入不可用状态?
成熟一点的改进可能是:
生产环境禁止直接 kubectl delete
↓
必须通过发布平台操作
↓
平台识别生产环境
↓
高危操作二次确认
↓
权限最小化
↓
操作审计
↓
异常行为自动告警
这时候,事故才真正产生了价值。
因为你不是要求:
“以后大家千万别犯错。”
而是让系统变成:
“即使有人犯错,也不至于造成灾难。”
这才是工程思维。
三、Postmortem 最重要的一个词:Blameless
很多人第一次听到 Blameless Postmortem(无责复盘),会产生一个误解:
“无责复盘是不是出了事故也不用追责?”
不是。
Blameless ≠ 不承担责任。
它真正想表达的是:
复盘的时候,不要把人的错误当成分析终点。
比如:
def deploy(config):
if config.env == "prod":
deploy_to_production(config)
这里最大的风险是什么?
不是某个人粗心。
而是:
代码允许任何人直接部署生产
那我们真正应该修改的,是系统。
例如:
def deploy(config, operator):
if config.env == "prod":
if not operator.has_permission("prod_deploy"):
raise PermissionError("没有生产发布权限")
if not config.approved:
raise PermissionError("生产发布未审批")
return deploy_to_environment(config)
再进一步:
def deploy(config, operator):
validate_config(config)
if config.env == "prod":
check_permission(operator)
check_approval(config)
check_change_window(config)
create_audit_record(operator, config)
return deploy_to_environment(config)
你会发现:
真正优秀的复盘,最后一定会落到系统、流程、工具和组织机制上。
四、事故不是“坏事”,重复事故才是真正的坏事
我特别喜欢一个判断事故价值的方法:
第一次事故叫问题,第二次同类事故叫管理问题。
比如第一次 Redis 把内存打爆。
大家发现:
没有内存告警
没有容量预测
没有淘汰策略
没有压测
然后补上。
半年后,又因为 Redis 内存爆了。
这时候就不能再简单说:
“又一次 Redis 内存事故。”
真正应该问的是:
为什么第一次事故留下的知识,没有进入组织?
这就涉及一个很容易被忽略的问题:
事故复盘 ≠ 组织学习
复盘只是:
发生了什么?
为什么发生?
怎么恢复?
以后怎么办?
而组织学习则是:
事故
↓
知识
↓
行动
↓
机制
↓
标准
↓
工具
↓
自动化
↓
组织能力
这是完全不同的层次。
五、不要只写“整改措施”,要给整改措施加上生命周期
很多 Postmortem 最大的问题,是整改项写得特别漂亮。
例如:
1. 增加监控
2. 优化发布流程
3. 加强代码审核
4. 完善应急预案
然后一个月以后:
TODO
TODO
TODO
TODO
最后谁都不知道做到什么程度了。
我更推荐把事故 Action Item 做成类似下面这种结构:
action_items = [
{
"problem": "数据库连接池耗尽",
"action": "增加连接池使用率监控",
"owner": "张三",
"priority": "P0",
"deadline": "2026-09-20",
"status": "DOING",
"verify": "模拟连接池耗尽,确认告警5分钟内触发"
}
]
注意最后那个:
verify
非常重要。
因为:
没有验证方式的整改措施,很容易只是“写过了”。
比如:
增加数据库监控
不够。
应该变成:
数据库连接池 > 80%
↓
触发 Warning
数据库连接池 > 95%
↓
触发 Critical
模拟连接池耗尽
↓
确认告警
↓
确认通知
↓
确认值班人员收到
这时候,它才从一句话变成真正的工程能力。
六、事故复盘真正应该关注的是“控制点”
举个大家都熟悉的例子。
一次线上发布导致 CPU 100%。
最开始调查发现:
某次代码提交引入了死循环
如果到这里就结束了:
“开发写代码不严谨。”
那意义其实不大。
继续往下问:
第一层
为什么有死循环?
代码 Bug
第二层
为什么测试没发现?
测试场景覆盖不足
第三层
为什么测试场景覆盖不足?
没有针对大数据量做测试
第四层
为什么没有大数据量测试?
性能测试没有纳入发布门禁
第五层
为什么发布可以绕过性能测试?
CI/CD 没有强制检查
你看。
一开始是:
一个 Bug
最后变成:
CI/CD质量门禁缺失
这就是典型的 从个人错误走向系统性原因。
七、真正高级的运维,不是“救火快”,而是“让火越来越少”
传统运维经常有一种英雄主义:
“这个问题只有我能解决。”
凌晨服务器挂了。
某个大佬上线。
ssh production
tail -f xxx.log
grep ERROR xxx.log
kill -9 xxx
systemctl restart xxx
然后:
“好了。”
群里:
“牛逼。”
确实牛逼。
但从组织能力角度来看,这其实不一定是好事。
因为如果:
只有 A 知道怎么处理
那么组织能力其实是:
A = 100
其他人 = 0
真正成熟之后应该变成:
A知道
↓
A写下来
↓
形成Runbook
↓
自动化
↓
新人也能执行
最终:
if service_down:
detect()
diagnose()
execute_runbook()
verify()
record()
这时候,原来只有一个人会的“绝活”,变成了整个团队的基础能力。
八、我特别建议把事故沉淀成“可执行知识”
很多公司的 Wiki 有几十万字。
但真正出事故的时候,没人看。
为什么?
因为知识写成了:
第一章 系统介绍
第二章 架构说明
第三章 网络拓扑
第四章 数据库设计
……
半夜三点的时候,你根本没心情看。
真正有价值的运维知识应该长这样:
【现象】
接口 5xx > 10%
【第一步】
检查:
kubectl get pods
【第二步】
查看:
kubectl logs xxx
【第三步】
如果出现:
OOMKilled
【处理】
kubectl rollout restart deployment xxx
【验证】
错误率恢复 < 1%
【升级】
超过10分钟无法恢复,通知DBA
这才叫:
Runbook。
它的价值不是“记录历史”。
而是:
让下一次事故处理得更快。
九、事故还可以反过来喂给监控、测试和AI
这也是我认为未来 AIOps 特别有价值的一点。
一次事故发生:
事故
↓
日志
↓
指标
↓
Trace
↓
人工判断
↓
根因
如果只是写一份 Postmortem:
事故结束
就结束了。
但如果继续做:
事故
↓
提取特征
↓
形成规则
↓
加入监控
↓
加入测试
↓
加入知识库
↓
AI学习
那事故就开始产生复利。
例如以前:
数据库连接数 > 90%
没人特别关注。
发生一次事故以后发现:
连接数 > 90%
+
慢SQL增加
+
CPU > 80%
+
请求量持续上涨
是一个非常危险的组合。
那么就可以形成:
if (
db_connections > 0.9
and slow_sql_rate > 0.2
and cpu_usage > 0.8
):
alert(
level="critical",
message="数据库资源耗尽风险"
)
下一次事故甚至可能还没发生,系统已经告诉你:
“这个事故正在形成。”
这就是从:
事故响应
走向:
事故预防。
十、最终要建立的是一条“事故 → 能力”的流水线
如果让我给一个真正落地的模型,我会这样设计:
┌──────────────┐
│ 生产事故 │
└──────┬───────┘
↓
┌──────────────┐
│ 快速恢复 │
└──────┬───────┘
↓
┌──────────────┐
│ Postmortem │
└──────┬───────┘
↓
┌────────┴────────┐
↓ ↓
技术原因 系统原因
↓ ↓
修复Bug 修机制
↓ ↓
加测试 加监控
↓ ↓
加告警 自动化
└────────┬────────┘
↓
┌──────────────┐
│ Runbook │
└──────┬───────┘
↓
┌──────────────┐
│ 知识库 │
└──────┬───────┘
↓
┌──────────────┐
│ 团队培训 │
└──────┬───────┘
↓
┌──────────────┐
│ 组织能力提升 │
└──────────────┘
这才是真正意义上的:
从 Postmortem 到组织学习。
十一、最后,我越来越觉得:事故本身不是最可怕的
运维工作有一个很残酷的现实:
复杂系统一定会出事故。
你再努力,也不可能做到:
事故率 = 0
真正应该追求的可能是:
事故发生
↓
影响越来越小
↓
发现越来越快
↓
恢复越来越快
↓
重复事故越来越少
↓
团队越来越不依赖个人
所以我不太喜欢把 Postmortem 翻译成简单的“事故总结”。
它更像是一种组织的记忆机制。
人会忘记。
新人会加入。
老员工会离职。
系统会重构。
代码会重写。
但是,如果一次事故最终变成了:
一条监控
一个测试
一个Runbook
一个自动化脚本
一个发布门禁
一条工程规范
一套培训材料
那么这场事故就没有白发生。
最好的事故复盘,不是让大家记住“谁搞砸了”。
而是让组织记住:
“我们曾经在这里摔倒过,所以以后不会再用同样的姿势摔第二次。”
这才是 Postmortem 最有价值的地方。
也是我理解的运维真正的成长:
从“我能把系统救回来”,到“我能让这个团队以后更不容易把系统搞挂”。
前者是运维能力。
后者,才是组织能力。