作者提出将runbook写成版本化YAML文件,配合freeze/unfreeze规则,解决AI凭借长上下文自信给出过时/错误操作建议的问题,让AI的置信度与事实同步。
大多数 runbook 之所以腐烂,是因为没有人真正负责维护,而一个拥有超长对话记忆的 AI 助手会让这种腐烂在凌晨三点 pager 响起之前完全不可见。先说结论:把 runbook 做成一个版本化的 YAML 文件,让模型只读取那个文件,并在同一个 artifact 中编码一条 freeze/unfreeze 规则。下面的代码片段构成的是一个参考设计而非生产系统,文末的测试计划会告诉你它对你的场景是否安全。
上周的 DEV feed 上有两组讨论很火:一组是关于 LLM 记忆信任一切的问题,另一组是关于没人真正测试过的 reviewer,两个话题戳中了同一根神经。模型很自信,是因为它有上下文,而不是因为上下文是真的。在值班场景下,这种自信伤害最大:bot 兴高采烈地建议对一个在两次事故前就已经废弃的服务做回滚,而你累得连争辩的力气都没有。
wiki 里的 runbook 已经是负担了,因为它积累观点的速度比修正快。模型的对话历史更糟,因为它积累了一样的过时事实,再加上之前的值班人员走过的每一个弯路。你会信任一个拒绝查看当前部署清单、反而背诵上季度记忆的人类运维吗?我不这么认为,但这就是我们大多数 AI 工具的实际接法。
我现在遵循的规则很简单:checked-in 的 YAML 是唯一的真实来源,模型在回答事故问题时永远看不到对话历史。每次查询都从磁盘开始,而记忆只是装饰,不是证据。
这个setup的核心是一个 YAML 文件,描述每一个重要的告警,加上最初要执行的命令和升级路径。下面是一个精简示例,你可以今晚就粘贴到自己仓库里:
version: 3
owner_team: platform
slack_channel: '#oncall-platform'
alerts:
- name: HighErrorRate
severity: P1
first_commands:
- 'kubectl get pods -n api -o wide'
- 'kubectl logs -n api -l app=api --tail=500'
escalation:
- after_minutes: 15
contact: 'primary'
- after_minutes: 30
contact: 'platform-eng'
freeze_required: true
- name: DbConnectionPoolExhausted
severity: P2
first_commands:
- 'kubectl exec -it -n data primary-db-0 -- patronictl list'
- 'kubectl get po -n data | grep -i pool'
escalation:
- after_minutes: 20
contact: 'db-owner'
freeze_required: false
freeze_policy:
window_minutes: 30
unfreeze_requires_ack: true
在这个参考设计中,服务在每次请求时从磁盘加载该文件,而不是从内存加载,查询在 Python 中是一行代码:
import yaml, sys
def load_runbook(path='runbook.yml'):
with open(path) as f:
return yaml.safe_load(f)
def lookup(name, runbook):
for alert in runbook['alerts']:
if alert['name'].lower() == name.lower():
return alert
return None
if __name__ == '__main__':
entry = lookup(sys.argv[1], load_runbook())
print(yaml.dump(entry) if entry else 'unknown alert: check runbook.yml')
你可以立即用 python runbook.py lookup HighErrorRate 测试,文件一改变响应就跟着变。这才是关键:artifact 是枯燥的、确定性的、在 code review 中可以 diff 的。
每个团队在重大事故期间都有一个隐式的 freeze,但隐式规则在凌晨四点就会被违反。这里的模式是一个小型状态机,它只写一个 JSON 文件,命令足够短,可以在半睡半醒时敲出来:
python freeze.py freeze 'INC-42: error budget exhausted'
python freeze.py status
python freeze.py unfreeze --ack emery
决策表放在代码旁边,读起来是这样的:
状态文件故意做得很枯燥——一行 JSON,包含 frozen 标志、事故 ID、时间戳和一个 acked 布尔值。枯燥才是重点,因为这是系统中模型不被允许解释或覆盖的部分。
为了让这个 setup 在紧急时刻可用,设计中增加了一个 AI 总结步骤,读取匹配的 YAML 条目和 metric payload,然后生成三个要点的上下文。声明:本文是 MonkeyCode 产品推广的一部分准备的。MonkeyCode 是一个开源项目,在撰写本文时宣传免费模型访问,配有 10M token 限额和小服务的免费服务器套餐;配额和条款会变化,所以在依赖之前请查看仓库的 README。
工作流很短:模型把 YAML 和当前告警 payload 压缩成两行摘要,而状态机仍然决定 freeze。模型不决定任何事情,它只做摘要,查询端点可以托管在 MonkeyCode 的免费服务器套餐上,这样团队获得一个稳定的 URL 而不需要为云函数付费。
在愤怒中信任这个 setup 之前先试一下,把每一步当作通过/失败的关卡:
runbook.yml 中删除一个 first_commands 条目,commit,再运行 lookup——响应必须立即改变。freeze.py freeze 'TEST' 并确认一个预演的部署命令被拒绝,显示事故 ID。freeze.py unfreeze --ack <yourname> 并确认 quiet window 重置为 15 分钟。任何一步失败,接线就有问题,你真的会在真实事故中信任它吗?我会在下一个 pager 响起之前修复这个接线。
这个方法是一个 runbook 助手,而不是告警平台,它不会替换 PagerDuty、Grafana 或人脑。免费套餐很适合周末原型,但不要围绕可能变化的免费配额来规划生产 SLA。本地 JSON 状态文件也假设了一个团队和一个区域,如果两个事故指挥官同时 freeze 和 unfreeze,它就会崩溃。如果你的团队已经有强制变更窗口的可靠 runbook 自动化,这个 setup 增加的价值很小,而如果你还没有事故纪律,没有任何 YAML 文件能为你创建它。
下次 pager 把你从床上拖起来的时候,最糟糕的问题是「上个月我们到底决定了什么?」——把答案放进一个文件,在救火时 freeze 它,让模型总结那个文件,而不是你的记忆。如果这个工作流听起来有用,MonkeyCode 的免费配额是一个今晚就能尝试的廉价方式。