别再写“演练方案”了:真正靠谱的 Incident Playbook,应该能直接跑起来
凌晨 2 点,告警突然炸了。
CPU 100%,接口大量 5xx,订单开始超时。
值班同学打开群聊:
“先看看是不是数据库问题。”
“我这边看应用日志没发现异常。”
“要不要先重启一下?”
“谁有之前那个故障处理文档?”
“等一下,我找找……”
五分钟过去了。
十分钟过去了。
最后大家发现:真正的问题不是没人知道怎么处理,而是知道的东西根本没有变成可以执行的东西。
这也是我越来越强烈的一个感受:
Incident Playbook 如果只能拿来“阅读”,那它其实还没有真正完成。
真正成熟的 Playbook,不应该是一篇写得很漂亮的 Word 文档,而应该更像一套“故障处理程序”。
甚至可以进一步:
把故障演练写成代码,让 Playbook 本身具备可执行、可验证、可回归测试的能力。
这才是 Incident Playbook 可测试化真正有意思的地方。
一、为什么很多故障演练,最后都变成了“开会演戏”?
传统的故障演练,大概是这么玩的。
提前准备一份方案:
场景:数据库连接池耗尽
现象:接口大量超时
处理步骤:
- 查看数据库连接数
- 检查应用连接池
- 查看慢 SQL
- 必要时重启应用
- 恢复业务
看起来没毛病。
然后演练开始。
负责人说:
“现在模拟数据库连接池耗尽。”
A 同学:
“我会先查看监控。”
B 同学:
“然后检查应用日志。”
C 同学:
“如果确认连接池满了,我会进行扩容。”
大家点点头。
演练结束。
总结:
“本次演练圆满完成。”
但是问题来了:
你真的验证了吗?
你有没有验证:
- 告警到底会不会触发?
- 告警触发后多久能发现?
- 值班人员能不能拿到正确信息?
- Playbook 里面的命令还能不能执行?
- 权限还存在吗?
- 依赖的接口还存在吗?
- 自动恢复真的有效吗?
- 回滚真的能够成功吗?
- 业务指标恢复了吗?
如果这些都没有验证,那么这更像是:
大家一起把剧本念了一遍。
而不是一次真正的故障演练。
二、Playbook 最大的问题:写的是“人话”,执行需要的是“机器话”
我们来看一个非常典型的 Playbook。
发现 API 5xx 增加
↓
检查应用状态
↓
检查数据库
↓
确认是否为数据库连接问题
↓
如果连接池耗尽,则重启应用
↓
观察错误率
↓
恢复正常后结束
人当然能看懂。
但机器看不懂。
因为里面大量都是模糊描述:
“检查应用状态。”
到底检查什么?
“观察错误率。”
多少算恢复?
“如果连接池耗尽。”
多少算耗尽?
所以我认为:
Playbook 可测试化的第一步,不是写代码,而是把模糊的人话变成明确的判断条件。
例如:
incident:
name: api_database_connection_exhaustion
trigger:
metric: http_5xx_rate
condition: "> 5%"
duration: "3m"
diagnosis:
db_connection_usage:
condition: "> 90%"
remediation:
action: restart_application
success:
http_5xx_rate: "< 1%"
duration: "5m"
这时候事情就完全不一样了。
因为它已经不再是一篇“建议怎么处理”的文档。
它开始变成:
一份机器可以理解的故障契约。
三、真正的 Playbook,应该像测试用例一样写
如果让我重新设计 Incident Playbook,我会把它拆成几个非常明确的部分:
Trigger
↓
Detect
↓
Diagnose
↓
Remediate
↓
Validate
↓
Rollback
其实和我们写测试代码特别像。
比如一个“Redis 故障”的演练:
def test_redis_failure_recovery():
inject_redis_failure()
assert wait_for_alert("RedisUnavailable")
assert api_error_rate() > 0.05
run_playbook("redis-failure")
assert wait_until(
lambda: api_error_rate() < 0.01,
timeout=300
)
assert redis_connection_count() < 1000
注意这里最关键的不是 Python。
而是最后一句:
assert
没有 assert 的演练,很容易变成表演。
因为你必须明确告诉系统:
什么叫成功?
四、故障演练最应该测试的,其实不是“你会不会处理”
很多团队做演练的时候,非常关注一个问题:
“这个工程师会不会处理故障?”
但成熟的工程体系,更应该关注:
“我们的系统到底有没有能力让一个普通值班人员正确处理故障?”
这是完全不同的思路。
假设 Playbook 写着:
kubectl rollout restart deployment/order-service
看起来非常简单。
但是到了真正的生产环境:
error: deployments.apps "order-service" not found
为什么?
因为三个月前服务已经改成:
order-api
Playbook 没更新。
这种事情特别常见。
所以 Playbook 本身也应该进行自动化测试。
例如:
def test_playbook_command():
result = run_command(
"kubectl get deployment order-service"
)
assert result.exit_code == 0
如果失败:
❌ Playbook validation failed
Service: order-service
Command: kubectl get deployment order-service
Reason: resource not found
这时候你甚至不用等故障发生。
Playbook 已经提前告诉你:这份剧本过期了。
五、把 Playbook 当成代码管理,而不是 Word 文档
这是我特别推荐的一种方式。
不要:
故障处理手册.docx
而是:
incident-playbooks/
├── api/
│ ├── high-5xx.yaml
│ ├── high-latency.yaml
│ └── service-down.yaml
│
├── database/
│ ├── connection-exhaustion.yaml
│ ├── slow-query.yaml
│ └── primary-failure.yaml
│
├── cache/
│ ├── redis-down.yaml
│ └── redis-memory-high.yaml
│
└── tests/
├── test_api.py
├── test_database.py
└── test_cache.py
然后直接进入 Git。
这样你就拥有了很多传统文档没有的能力:
Git
↓
Code Review
↓
CI
↓
Playbook Test
↓
发布
以后修改生产架构的时候:
修改服务
↓
修改 Playbook
↓
修改测试
↓
CI 自动验证
↓
全部通过
这时候 Playbook 才真正成为了生产系统的一部分。
六、甚至可以给 Playbook 写 CI
这个思路其实非常有意思。
假设我们规定:
所有 P1/P2 级故障 Playbook,必须通过自动化测试才能合并。
那么 CI 可以这样跑:
name: Incident Playbook Test
on:
pull_request:
jobs:
playbook-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Validate YAML
run: python scripts/validate_playbook.py
- name: Run Playbook Tests
run: pytest tests/
- name: Check Commands
run: python scripts/check_commands.py
- name: Check Rollback
run: python scripts/test_rollback.py
于是以后有人修改:
remediation:
action: restart_application
CI 就会告诉你:
❌ Rollback test failed
❌ Validation condition missing
❌ Command not executable
这就很舒服了。
因为以前是:
出事故 → 发现 Playbook 不能用。
现在变成:
提交代码 → 就发现 Playbook 不能用。
故障还没发生,问题已经被发现了。
七、真正高级的玩法:故障注入 + Playbook 自动执行
如果只是验证 Playbook 文件格式,其实还不够。
更进一步,可以把它和故障注入结合起来。
比如我们有一个订单服务:
order-api
↓
Redis
↓
MySQL
↓
MQ
现在模拟 Redis 故障:
def chaos_test():
# 1. 注入故障
chaos.kill("redis")
# 2. 验证告警
assert alert("RedisUnavailable")
# 3. 自动执行 Playbook
execute_playbook(
"redis-failure"
)
# 4. 验证业务
assert wait_until(
lambda: order_success_rate() > 0.99,
timeout=300
)
# 5. 验证故障清理
assert chaos.active_faults() == []
这时候整个过程已经形成闭环:
制造故障
↓
触发监控
↓
产生告警
↓
执行 Playbook
↓
恢复服务
↓
验证业务
↓
清理故障
这已经不是传统意义上的“演练”了。
这其实就是:
Incident Playbook 的自动化回归测试。
八、别只验证“服务活了”,一定要验证“业务活了”
这里还有一个非常容易踩坑的地方。
比如:
assert http_status() == 200
测试通过。
但是用户还是无法下单。
为什么?
因为:
HTTP 200
≠
业务正常
所以真正的故障恢复测试,最好分成三层。
第一层:基础设施
assert pod_status() == "Running"
assert cpu_usage() < 80
assert memory_usage() < 80
第二层:应用
assert api_error_rate() < 0.01
assert api_latency_p99() < 500
第三层:业务
assert order_success_rate() > 0.99
assert payment_success_rate() > 0.99
最终判断应该是:
机器活了
+
服务活了
+
业务活了
=
真正恢复
而不是:
Pod Running
=
事故结束
九、Playbook 还应该测试“权限”
这个问题特别容易被忽略。
很多 Playbook 在写的时候:
kubectl delete pod xxx
作者自己执行当然没问题。
但是半年以后,真正值班的人执行:
Error: Forbidden
因为 RBAC 权限已经调整。
所以可以增加:
def test_operator_permission():
result = run_as(
role="oncall",
command="kubectl get pods"
)
assert result.exit_code == 0
甚至可以进一步检查:
assert can_execute(
role="oncall",
command="restart deployment"
)
assert not can_execute(
role="oncall",
command="delete production database"
)
这就把一个非常现实的问题解决了:
Playbook 写得对,不代表值班人员执行得了。
十、还要测试“时间”,因为事故不是做题
生产事故和考试最大的区别是什么?
考试没有用户流失,事故有。
所以 Incident Playbook 不应该只关心:
最终能不能恢复?
还应该关心:
多久能发现?
多久能定位?
多久能执行?
多久能恢复?
例如:
assert detection_time < 60
assert diagnosis_time < 300
assert remediation_time < 600
assert recovery_time < 900
这时候你就可以得到:
MTTD = 42s
MTTI = 183s
MTTR = 527s
然后每次演练都做对比:
第一次:MTTR 18min
第二次:MTTR 11min
第三次:MTTR 7min
这才叫真正的工程改进。
否则每次复盘都是:
“以后加强监控。”
这种话说十遍,系统也不会自动变好。
十一、我特别喜欢一个思路:让 Playbook 自己证明自己没过期
现实里最大的 Playbook 问题之一,就是:
它会腐烂。
服务变了。
架构变了。
命令变了。
权限变了。
监控指标变了。
但是文档还停留在两年前。
所以可以定期执行:
def validate_all_playbooks():
for playbook in load_playbooks():
assert syntax_valid(playbook)
assert dependencies_exist(playbook)
assert commands_available(playbook)
assert permissions_valid(playbook)
assert rollback_defined(playbook)
assert success_condition_defined(playbook)
最终生成:
Incident Playbook Health Report
Total: 86
Passed: 79
Warning: 5
Failed: 2
❌ mysql-primary-failure
rollback command unavailable
❌ order-api-high-5xx
alert rule not found
这时候运维团队甚至可以给 Playbook 建一个“健康度”。
比如:
Playbook Health Score
Syntax 100%
Command 96%
Permission 98%
Alert 91%
Rollback 87%
Recovery Test 93%
Overall 94%
故障处理手册,终于也开始有“可观测性”了。
十二、最终你会发现:Playbook 和代码,其实越来越像
传统运维:
文档
↓
人执行
↓
出了问题再修改文档
成熟运维:
Playbook
↓
自动测试
↓
CI
↓
定期演练
↓
生产事故
↓
反馈
↓
更新 Playbook
↓
再次测试
这其实就是软件工程里的:
代码 → 测试 → CI → 发布 → 反馈 → 重构
只不过以前大家把这个方法用在业务代码上,却没有用在事故响应上。
我认为这恰恰是未来 Incident Management 很值得继续发展的一个方向:
Incident Playbook 不应该只是“告诉人怎么做”,而应该能够证明“自己还能不能这么做”。
最后:别把演练当成一次活动,把它当成一次测试
我现在越来越不喜欢“年度故障演练”这种说法。
因为很容易让人产生一种错觉:
一年做一次,拍个照,写个总结,这事就结束了。
但真正靠谱的演练,应该是持续发生的。
今天测试:
Redis 故障
明天测试:
MySQL 主库故障
后天测试:
MQ 堆积
再测试:
API 5xx
甚至可以直接在 CI/CD、预生产环境、混沌工程平台里持续跑。
最终形成这样一套体系:
Incident Playbook
│
┌──────────┴──────────┐
↓ ↓
人工执行 自动执行
│ │
↓ ↓
Drill Test
│ │
└──────────┬──────────┘
↓
Metrics
↓
MTTR / MTTD
↓
Review
↓
Playbook Update
↓
CI
↓
Regression Test
这才是我理解的 Incident Playbook 可测试化。
说到底,运维真正害怕的,从来不是“没有文档”。
而是:
事故来了,大家手里都有文档,却没人敢确定这份文档现在到底还能不能用。
所以,与其每年花一天时间“演一场事故”,不如把事故演练变成可以随时运行的测试。
因为真正优秀的运维体系,不是保证:
“我们永远不会出事故。”
而是保证:
“事故真的来了,我们知道怎么发现、怎么处理,而且这套方法已经被反复证明过。”
这两句话,差别很大。
也是运维从“靠经验救火”,走向“靠工程体系抗事故”的一个重要分水岭。