生产事故响应手册:从慌乱到从容的系统性方法
凌晨 3 点被报警叫醒?面对生产事故手足无措?分享我 5 年处理 100+ 起事故的实战经验,建立从事前预防到事后复盘的全流程体系。
那个改变一切的凌晨
2021 年 12 月 18 日,凌晨 3:47。
我被刺耳的报警声惊醒。手机屏幕上闪烁着红色:「CRITICAL: Payment service down」。
我手忙脚乱地打开电脑,VPN 连接失败。尝试三次后终于连上,却发现日志系统也挂了。Dashboard 一片空白,只有 Slack 里用户愤怒的投诉刷屏。
接下来的 4 小时,是一场噩梦:
- 我不知道问题出在哪里
- 我不知道该联系谁
- 我不确定回滚是否有用
- 我不敢做重大改动,怕让事情更糟
最终,我们在早上 8 点恢复了服务——但已经损失了 300 万订单,客户满意度暴跌。
那次事故后,我发誓要建立一套系统性的响应方法。
现在,5 年过去了,我处理了 100+ 起事故,从手忙脚乱到从容应对。这篇文章分享我的完整方法论。
为什么需要事故响应体系
事故是软件的一部分
任何复杂系统都会故障。
- Google:每年数千起事故
- Amazon:每天有服务故障
- Netflix: intentionally 破坏自己的系统(Chaos Monkey)
关键不是避免事故,而是快速恢复并从中学习。
没有体系的代价
| 维度 | 有体系 | 无体系 |
|---|---|---|
| MTTR (平均恢复时间) | 15 分钟 | 2-4 小时 |
| 团队压力 | 可控 | 极高 |
| 客户影响 | 最小化 | 不可控 |
| 事后学习 | 系统化 | 无章法 |
| 重复事故 | 极少 | 频繁 |
事故响应的三个阶段
事前准备(80% 的工作)
│
├── 监控与报警
├── Runbook 文档
├── 演练与培训
└── 架构设计
│
事故发生(关键时刻)
│
├── 发现
├── 响应
├── 定位
├── 止损
└── 恢复
│
事后复盘(成长机会)
│
├── 时间线重建
├── 根因分析
├── 改进项跟踪
└── 知识沉淀
关键认知:80% 的工作在事前,但很多人只关注事故发生时。
第一阶段:事前准备
1. 监控体系
没有监控 = 盲人骑瞎马
监控的层次
┌─────────────────────────────────────────────────────────┐
│ 业务层(Business Metrics) │
│ - 订单量、支付成功率、用户活跃度 │
│ - 最重要,直接影响收入 │
├─────────────────────────────────────────────────────────┤
│ 应用层(Application Metrics) │
│ - 响应时间、错误率、吞吐量 │
│ - 服务的健康状态 │
├─────────────────────────────────────────────────────────┤
│ 系统层(Infrastructure Metrics) │
│ - CPU、内存、磁盘、网络 │
│ - 资源使用情况 │
├─────────────────────────────────────────────────────────┤
│ 日志(Logs) │
│ - 请求日志、错误日志、审计日志 │
│ - 详细的问题诊断信息 │
└─────────────────────────────────────────────────────────┘
关键指标设定
RED 方法(面向用户的服务):
- Rate:请求量(每秒请求数)
- Errors:错误率(百分比)
- Duration:响应时间(分位值 p50, p95, p99)
USE 方法(面向资源):
- Utilization:利用率
- Saturation:饱和度
- Errors:错误数
报警规则
好的报警:
- ✅ 可操作的(Actionable):收到报警知道要做什么
- ✅ 具体的(Specific):指出具体哪个服务、哪个指标
- ✅ 及时的(Timely):问题正在发生
- ✅ 重要的(Important):需要立即处理
不好的报警:
- ❌ CPU > 80%(不一定是问题)
- ❌ 任何错误(正常系统也有错误)
- ❌ 预测性报警(提前 1 小时预警,结果 false positive)
我的报警分级:
| 级别 | 触发条件 | 响应时间 | 通知方式 |
|---|---|---|---|
| P0 | 服务完全不可用 | 5 分钟 | 电话 + Slack |
| P1 | 核心功能受损 | 15 分钟 | Slack + SMS |
| P2 | 性能严重下降 | 1 小时 | Slack |
| P3 | 警告阈值 | 4 小时 | 邮件 |
2. Runbook(操作手册)
Runbook 是事故响应的救命稻草。
什么是 Runbook
针对每个可能报警的详细操作步骤。
Runbook 模板
# Service X - High Error Rate
## 症状
- 错误率 > 5%
- 影响用户登录
## 检查清单(按顺序执行)
### 1. 确认问题
- [ ] 查看 Dashboard: https://grafana.xxx.com/service-x
- [ ] 确认不是下游依赖问题
- [ ] 确认不是网络抖动
### 2. 快速止损选项
**选项 A:回滚到上一版本**
```bash
kubectl rollout undo deployment/service-x
- 风险:可能丢失数据(如果有 schema 变更)
- 时间:2 分钟
选项 B:扩容
kubectl scale deployment/service-x --replicas=10
- 适用:流量激增导致
- 时间:1 分钟
选项 C:启用降级模式
curl -X POST https://api.xxx.com/admin/degrade \
-H "Authorization: Bearer $TOKEN" \
-d '{"feature": "recommendation", "level": "basic"}'
- 适用:特定功能问题
- 时间:30 秒
3. 根因诊断命令
查看错误日志:
kubectl logs deployment/service-x --tail=500 | grep ERROR
查看依赖服务状态:
./scripts/check-dependencies.sh service-x
4. 升级路径
如果 10 分钟内无法解决:
- 通知 Tech Lead(电话:xxx)
- 在 #incident-response 频道发布事故公告
- 启动事故响应流程
5. 恢复确认
- Dashboard 恢复正常 5 分钟
- 无新错误日志
- 业务指标恢复
相关文档
- 架构图:xxx
- 代码仓库:xxx
- 依赖服务列表:xxx
历史事故
- 2025-08-15: 数据库连接池耗尽,解决:扩容连接池
- 2025-05-22: 缓存雪崩,解决:预热缓存 + 熔断
#### Runbook 维护
- [ ] 每次事故后更新 Runbook
- [ ] 每季度 review 一次
- [ ] 新服务上线必须有 Runbook
- [ ] 演练时验证 Runbook 有效性
### 3. 事故响应流程
#### 角色定义
事故指挥官(Incident Commander, IC) ├── 职责:协调响应,做关键决策 ├── 技能:了解全局,善于沟通 └── 切换:必要时可切换给其他人
技术负责人(Technical Lead, TL) ├── 职责:主导技术诊断和修复 ├── 技能:深入了解系统 └── 专注:不参与对外沟通
沟通负责人(Communications Lead, CL) ├── 职责:对内对外沟通 ├── 对外:客户通知、Status Page 更新 └── 对内:团队同步、升级通知
记录员(Scribe) ├── 职责:记录时间线和关键决策 └── 工具:共享文档(Google Doc / Notion)
**注意**:一个人可以兼任多个角色,但要明确分工。
#### 沟通模板
**事故公告模板(Slack)**:
```markdown
🚨 INCIDENT ALERT 🚨
服务:Payment API
状态:Investigating
影响:用户无法完成支付
开始时间:14:23 UTC
当前状态:团队正在调查,预计 30 分钟内更新
更新频道:#incident-2026-03-15-payment
状态更新模板(每 30 分钟):
INCIDENT UPDATE
时间:15:00 UTC(事故发生后 37 分钟)
状态:Mitigating
进展:已定位到数据库连接池耗尽,正在扩容
ETA:预计 15:30 恢复
影响:约 5000 笔支付失败
下一步:扩容完成后验证,更新 Status Page
事故关闭模板:
✅ INCIDENT RESOLVED
服务:Payment API
恢复时间:15:45 UTC
总时长:1 小时 22 分钟
影响:8000 笔支付失败,已自动重试成功
根因:数据库连接池配置过小
复盘会议:明天 10:00 AM
文档链接:[Incident Report](xxx)
4. 演练(Game Day)
没有演练的体系是不完整的。
演练类型
桌面演练(Tabletop):
- 围坐在一起,讨论假设场景
- 不实际操作,只走流程
- 频率:每月一次
- 时长:1 小时
功能演练(Functional):
- 实际模拟事故
- 使用测试环境
- 频率:每季度一次
- 时长:半天
混沌工程(Chaos Engineering):
- 生产环境随机注入故障
- 验证系统自愈能力
- 频率:每月一次(小规模)
- 工具:Chaos Monkey, Litmus
演练流程
- 策划:定义目标、范围、场景
- 准备:设置监控、准备 rollback 方案
- 执行:注入故障,观察响应
- 观察:记录响应时间、决策过程
- 复盘:讨论改进点
- 改进:更新 Runbook、修复问题
演练场景示例
- 数据库主库宕机
- 缓存服务不可用
- 第三方 API 超时
- 网络分区(脑裂)
- 证书过期
- 配置错误导致服务不可用
5. On-Call 体系
On-Call 轮换
原则:
- 每人每次 on-call 不超过 1 周
- 轮换后有 1 周 “cool down” 时间
- 避免连续节假日 on-call
我的团队安排:
- 5 人轮值
- 每人每周一次
- 主备双岗(Primary + Secondary)
On-Call 补偿
- 工作日 on-call:正常工资 + 调休
- 周末 on-call:2 倍调休
- 节假日 on-call:3 倍调休
- 实际处理事故:额外调休
关键:On-Call 是工作,需要补偿。
On-Call 工具包
每个 on-call 工程师需要:
- VPN 随时可连
- 生产环境访问权限
- 报警 App(PagerDuty/Opsgenie)
- 团队联系方式(电话)
- 笔记本电源充足
- 手机流量充足(备用网络)
第二阶段:事故发生
事故响应流程图
发现报警
│
▼
确认事故(5分钟内)
│
├── 是事故 → 通知团队,启动响应
│
└── 误报 → 调整阈值,记录
│
▼
组建响应团队(IC + TL + CL)
│
▼
评估影响
│
├── 高影响 → 立即止损(回滚/降级)
│
└── 低影响 → 继续诊断
│
▼
定位根因
│
▼
修复并验证
│
▼
恢复服务
│
▼
监控观察(30分钟)
│
▼
事故关闭
1. 发现阶段
目标:尽快知道问题发生
关键:
- 监控覆盖全面
- 报警渠道可靠
- 避免报警疲劳(否则容易忽视)
2. 确认阶段
不要立即响应,先确认。
检查清单:
- 是否真实事故?(排除误报)
- 影响范围?(多少用户、哪些功能)
- 是否是已知问题?(查 Runbook)
时间盒:5 分钟内必须确认
3. 响应阶段
立即行动(First 5 Minutes)
-
通知团队
在 #incident-response 频道发布事故公告 @channel 通知当前 on-call 人员 -
组建响应团队
- 自己担任 IC
- 找 TL(如果不确定,请求支援)
-
创建事故文档
- 使用模板创建共享文档
- 实时记录时间线
止损优先
黄金法则:先止血,再治疗。
常见止损手段:
| 手段 | 适用场景 | 恢复时间 | 副作用 |
|---|---|---|---|
| 回滚 | 新版本引入问题 | 2-5 分钟 | 丢失新功能 |
| 降级 | 部分功能问题 | 30 秒-1 分钟 | 体验下降 |
| 熔断 | 下游依赖故障 | 自动 | 功能受限 |
| 限流 | 流量激增 | 1 分钟 | 部分请求拒绝 |
| 扩容 | 资源不足 | 2-5 分钟 | 成本增加 |
关键决策:什么时候止损 vs 继续诊断?
我的原则:
- 如果影响 > 1000 用户或 > 1 万元损失/小时 → 立即止损
- 如果不确定根因 → 优先止损
- 如果有明确根因且 5 分钟可修复 → 直接修复
4. 诊断阶段
系统诊断方法
分层诊断法:
用户端 → CDN → 网关 → 应用 → 缓存 → 数据库 → 外部依赖
│ │ │ │ │ │ │
└───────┴──────┴───────┴───────┴───────┴─────────┘
从外到内排查
二分法:
- 确定问题在哪个层
- 每层用二分法缩小范围
- 配合日志和监控定位
关键命令和工具
快速健康检查:
# 服务状态
kubectl get pods
# 资源使用
top
kubectl top nodes
kubectl top pods
# 网络连通
ping
curl -v
# 数据库连接
mysql -e "SHOW PROCESSLIST;"
redis-cli ping
日志查询:
# 实时日志
kubectl logs -f deployment/service-x
# 错误日志
kubectl logs deployment/service-x | grep ERROR
# 特定用户请求
kubectl logs deployment/service-x | grep "user-id: xxx"
链路追踪:
- Jaeger/Zipkin:分布式追踪
- 找出请求在哪一步变慢/失败
5. 修复阶段
修复原则
- 小步快跑:小改动,快速验证
- 一次一个:不要同时改多个东西
- 随时回滚:每个改动都要能回滚
- 记录变更:记录所有尝试过的操作
常见修复操作
配置变更:
# 修改配置并热更新
kubectl apply -f config.yaml
# 回滚配置
kubectl rollout undo configmap/app-config
代码回滚:
# 查看历史版本
kubectl rollout history deployment/service-x
# 回滚到上一版本
kubectl rollout undo deployment/service-x
# 回滚到特定版本
kubectl rollout undo deployment/service-x --to-revision=3
数据库操作(谨慎!):
# 查看慢查询
mysql -e "SELECT * FROM information_schema.processlist WHERE time > 10;"
# 杀掉慢查询(如果确定是问题)
mysql -e "KILL <query_id>;"
# 扩容连接池(应用配置)
6. 恢复阶段
验证清单:
- 错误率恢复正常
- 响应时间恢复正常
- 业务指标恢复(订单量等)
- 无新的错误日志
- 持续观察 30 分钟
保持监控:
- 事故后 24 小时内密切监控
- 准备好二次故障的应对
第三阶段:事后复盘
复盘的目的
- 不是为了追责(Blameless)
- 是为了学习和改进
- 防止同类事故再次发生
复盘的时间线
| 时间点 | 活动 |
|---|---|
| 事故后 24 小时内 | 撰写事故时间线 |
| 事故后 48 小时内 | 召开复盘会议 |
| 事故后 1 周内 | 完成改进项 |
| 事故后 1 个月 | 验证改进效果 |
事故时间线
详细记录每个关键事件:
14:23:00 - 部署 v2.3.1 到生产环境
14:25:30 - 错误率开始上升(从 0.1% 到 1%)
14:28:00 - PagerDuty 报警触发
14:28:15 - On-call 工程师(张三)收到报警
14:30:00 - 张三确认事故,在 #incident-response 发布公告
14:32:00 - 李四加入(TL),开始诊断
14:35:00 - 定位到数据库连接池耗尽
14:38:00 - 执行扩容,连接池从 50 增加到 100
14:40:00 - 错误率开始下降
14:45:00 - 服务完全恢复,错误率回到正常水平
14:50:00 - 持续监控 5 分钟,确认稳定
15:00:00 - 事故关闭
关键:精确到分钟,包含所有尝试过的操作。
复盘会议
参会人员
- IC(事故指挥官)
- TL(技术负责人)
- 相关工程师
- 可选:产品经理、客户支持代表
会议流程(1 小时)
-
时间线回顾(15 分钟)
- 由 IC 带领,按时间线回顾
- 补充遗漏的细节
-
五个为什么(5 Whys)(20 分钟)
- 找出根本原因
- 不只停留在表面
-
改进项讨论(20 分钟)
- 列出所有可改进的点
- 分配责任人和 deadline
-
总结(5 分钟)
- 确认 action items
- 感谢团队的努力
五个为什么示例
问题:数据库连接池耗尽
为什么连接池耗尽?
→ 连接没有正确释放
为什么没有释放?
→ 代码里有连接泄漏
为什么代码有泄漏?
→ 新功能没有正确关闭连接
为什么没有发现?
→ 测试环境数据量小,没有触发
为什么没有监控?
→ 缺少连接池使用率监控
根因:
1. 代码缺陷:连接未正确释放
2. 测试不足:没有大流量测试
3. 监控缺失:缺少连接池监控
事故报告模板
# 事故报告:Payment API Outage
## 基本信息
- 事故 ID:INC-2026-0315-001
- 发生时间:2026-03-15 14:23 UTC
- 恢复时间:2026-03-15 14:45 UTC
- 总时长:22 分钟
- 严重级别:P1
## 影响评估
- 受影响用户:约 8000 用户
- 失败支付:约 5000 笔
- 收入影响:约 ¥200,000
- 数据丢失:无
## 时间线
[详细时间线...]
## 根因分析
[五个为什么...]
## 事故经过
[详细描述发生了什么...]
## 响应评价
做得好的:
- 快速定位问题(10 分钟)
- 有效止损(扩容)
做得不好的:
- 监控报警太晚(错误率 1% 才报警,应该 0.5%)
- 缺少连接池监控
## 改进项
| 改进项 | 负责人 | 优先级 | Deadline | 状态 |
|--------|--------|--------|----------|------|
| 修复连接泄漏 | 张三 | P0 | 3-17 | 待完成 |
| 添加连接池监控 | 李四 | P0 | 3-17 | 待完成 |
| 优化报警阈值 | 王五 | P1 | 3-20 | 待完成 |
| 增加大流量测试 | 赵六 | P1 | 3-31 | 待完成 |
## 经验教训
[总结学到的经验...]
## 附件
- 相关日志:[link]
- 监控截图:[link]
- 复盘会议录屏:[link]
改进项跟踪
关键:复盘的价值在于改进,而不是报告。
跟踪机制:
- 所有 action items 进入 backlog
- 每周 review 进度
- 事故后 1 个月验证改进效果
文化建设
Blameless Culture(无责文化)
核心:
- 人都会犯错
- 系统设计应该防错
- 追责只会让人隐瞒问题
实践:
- 复盘时不问「谁做错了」
- 而是问「系统设计出了什么问题」
- 公开表扬主动上报问题的人
心理安全
On-call 不应该是恐惧的来源。
建立心理安全:
- 新成员 on-call 前 pairing 资深工程师
- 明确「不懂就问」是 expected
- 事故后感谢而不是指责
- 定期轮岗,避免 burnout
持续学习
建立事故知识库:
- 所有事故报告归档
- 可搜索、可学习
- 新成员 onboarding 必读
定期分享:
- 每月「事故学习会」
- 分享有趣的事故案例
- 讨论预防方案
工具推荐
| 类别 | 工具 | 用途 |
|---|---|---|
| 监控 | Datadog, Grafana | 指标可视化 |
| 报警 | PagerDuty, Opsgenie | On-Call 管理 |
| 日志 | ELK, Splunk | 日志聚合分析 |
| 链路追踪 | Jaeger, Zipkin | 分布式追踪 |
| 事故管理 | Incident.io, PagerDuty | 事故响应流程 |
| 沟通 | Slack | 实时沟通 |
| 文档 | Notion, Confluence | Runbook, 事故报告 |
总结
事故响应体系 = 80% 准备 + 15% 执行 + 5% 复盘
立即行动清单
本周:
- 检查你的监控是否覆盖业务层
- 为 Top 3 服务写 Runbook
- 确认 on-call 轮换和补偿机制
本月:
- 做一次桌面演练
- 建立事故响应流程文档
- 创建事故报告模板
本季度:
- 做一次功能演练
- 复盘最近 3 起事故
- 更新所有 Runbook
关键原则
- 事前准备 > 事中救火:花 80% 时间准备
- 先止损,再诊断:保护用户和业务
- Blameless:从系统角度改进,不追责个人
- 持续演练:没有演练的体系不可靠
- 学习成长:每次事故都是改进机会
参考资源
书籍:
- 《Site Reliability Engineering》Google
- 《The DevOps Handbook》
- 《Chaos Engineering》
文章:
- Google SRE Book: https://sre.google/sre-book/table-of-contents/
- PagerDuty Incident Response: https://response.pagerduty.com/
- Atlassian Incident Management: https://www.atlassian.com/incident-management
工具文档:
- PagerDuty On-Call Guide
- Datadog SLO Monitoring
- Kubernetes Troubleshooting
你印象最深的一次生产事故是什么?从中学到了什么?
本文总结自我 5 年处理 100+ 起事故的实战经验。希望这些经验能帮你从容应对下一个凌晨 3 点的报警。