KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
05 · 发布闭环:上线后的十分钟才是真正的考试 — keel 龙骨
这一章回答:点下发布按钮之后到底该盯什么、盯多久、什么情况立刻回滚。答案是"上线前 checks → 上线后冒烟 → 观测窗口 → 明确回滚"。
这一章回答:点下发布按钮之后到底该盯什么、盯多久、什么情况立刻回滚。答案是"上线前 checks → 上线后冒烟 → 观测窗口 → 明确回滚"。
绝大多数事故不是"没发布成功",而是"发布成功了但悄悄坏了半小时"。这一章给出可复制的发布前后检查清单。
一、上线前:把能提前发现的问题提前说完
□ 产物 版本号明确、digest 明确、来源 commit 明确
□ 门禁 所需检查全绿(不是"除了那个 flaky 的")
□ 变更范围 这次上线包含哪些 PR / 哪些改动(能说得出来)
□ 迁移 有没有 DB 迁移?是不是只做 expand 部分?有没有分批?
□ 配置 新增的环境变量已在目标环境就位(不是"上线时顺手加上")
□ 开关 新功能是否包在开关里?默认状态是什么?
□ 回滚 回滚命令/按钮在哪?旧版本号是什么?有迁移时怎么退?
□ 时机 是否避开业务高峰/大促/别人也在上线的窗口?
□ 人 谁负责盯、盯多久、谁有权决定回滚
最后两条经常被忽略,但它们是人为事故率最高的部分:发布窗口要避开别人(多个团队同时变更 = 故障归因变难),发布必须有人盯("发完就下班"是所有温水事故的起点)。
二、上线后冒烟:三分钟内能做完的检查
冒烟的逻辑是:先验证"系统活着",再验证"核心路径通",最后看指标。
第 0 分钟 健康检查通过?实例数回到预期?有没有 CrashLoop?
第 1 分钟 核心路径手动走一遍(登录 → 主流程 → 关键写操作)
第 3 分钟 看四条曲线:请求量、错误率、延迟 P99、资源使用(CPU/内存/连接)
| 检查项 | 具体动作 | 判据 |
|---|---|---|
| 实例健康 | 实例数 / restarts 计数 | 无重启、无 CrashLoop、数量回到目标值 |
| 版本正确 | 服务端暴露版本号接口 | 返回的正是刚发布的版本(这一步能防止"发了个寂寞") |
| 核心路径 | 手动或自动化跑一次主流程 | 成功且结果正确 |
| 关键指标 | 错误率、延迟 P99、依赖调用成功率 | 与发布前基线相比在波动范围内 |
"版本正确"这一项最容易被跳过,但它揪出的问题比例不低:缓存导致部署没生效、镜像 tag 指错了、产物内容不是你以为的那个 commit。
三、观测四件套:盯什么、为什么是这四个
| 类型 | 回答的问题 | 最小内容 |
|---|---|---|
| Metrics(指标) | 现在还好吗?趋势在往哪走? | 请求量、错误率、延迟分布(不是平均值)、饱和度(连接/队列/CPU) |
| Logs(日志) | 具体发生了什么? | 结构化日志 + trace_id + 版本字段 |
| Traces(链路) | 慢在哪一环? | 关键路径埋点,能按版本过滤 |
| Alerts(告警) | 什么时候叫醒人? | 基于症状(错误率/延迟)而非原因(CPU);每条告警都要有处置动作 |
两个通用纪律:
- 日志要带版本号。 没有版本号字段,发布期间看到的报错到底是新版本还是旧版本留下的,永远说不清——而这恰恰决定了你要不要回滚。
- 告警要有处置动作。 一条"收到告警但没人知道要做什么"的告警,只会训练团队忽略告警。
另外:延迟看 P99/P95,不看平均值。 平均值会被大量快请求拉平,用户的糟糕体验藏在长尾里——这也是为什么 SLO 通常写"P99 < 300ms"而不是"平均 < 100ms"。
四、发布窗口:盯多久算够
观察时长 ≥ max( 指标收敛时间, 一个完整业务周期, 慢任务最长耗时 )
- 指标收敛:错误率要有一定的样本量才可信,流量小的服务需要更久。
- 业务周期:如果你的业务有明显的峰谷,观察窗口要跨过一次典型路径。
- 慢任务:异步任务、定时任务、批处理有自己的周期,它们的失败往往要等下一次触发才暴露。发布后别忘了看异步侧——很多"线上没问题"的结论其实只验证了同步链路。
五、回滚触发条件:把"判断"提前翻译成"数字"
立即回滚(不需要讨论):
· 核心路径成功率跌破基线 2 个百分点以上,持续 ≥ 3 分钟
· P99 延迟涨到基线的 2 倍以上且未回落
· 出现发布前不存在的新错误类型,且量级在上升
· 数据正确性受损(哪怕量很小)
继续观察(但准备回滚):
· 指标在阈值边缘抖动
· 影响范围仅限非核心路径
· 已经定位到原因,且修复时间小于回滚时间
"这条该不该回滚"的讨论,一定要发生在事故之前。 线上争论三分钟,就是在让用户承担三分钟的风险。
六、发布后的收尾:最容易被漏掉,也最容易埋雷
| 动作 | 为什么 |
|---|---|
| 更新发布记录(版本/时间/人/变更范围) | 未来排查时第一个要看的东西 |
| 清理临时开关(feature flag 债) | 开关堆积是长期的复杂度债(有些团队会专门做"开关清理"的工作流) |
| 补一条发布后校验到流水线 | 让下一次发布自动做这次手动做的事 |
| 复盘数据留档(该次发布的四个指标曲线) | 后来的人能知道"当时发生了什么" |
| 若回滚过,补一个 RCA(根因分析) | 复盘的目的是让下一次错误更早被流水线挡住 |
动手:可观察结果
| 动作 | 产出物 | 判断标准 |
|---|---|---|
| 写一份本项目的检查清单 | 一页 checklist | 清单能贴在 PR 模板里,新同事照做不遗漏 |
| 给版本暴露一个端点 | /version 或等价指标 |
发布后能立刻确认线上的确实是新版本 |
| 给日志加版本与 trace 字段 | 日志样例 | 发布期间的报错能按版本筛出来 |
| 定义回滚阈值 | 一份数字化的触发条件 | 任意两个人看同一张图能得出相同结论 |
| 做完一次完整的发布 + 观察 + 收尾 | 一次记录 | 三步都留档,下次可复盘 |
故障注入
| 注入方式 | 观察什么 | 说明的现象 |
|---|---|---|
| 发布后只看健康检查 | 是否能发现业务级错误 | 健康检查通过但核心路径已坏——最常见的一类事故 |
| 故意发布带 bug 的版本(限演练环境) | 多久被检测到 | 这个时长就是你的实际 MTTR 下限 |
| 去掉日志里的版本字段 | 发布期故障能否定位版本 | 不能 → 只能"全量回滚"这种昂贵判断 |
| 把告警阈值改成 CPU > 30% | 团队反应 | 噪声告警会让所有人开始屏蔽它 |
| 异步任务发布后不看 | 是否漏掉失败 | 定时任务要等下次触发才炸,往往是隔夜事故 |
自测题
- 为什么"确认线上版本正确"这一项要单独作为发布后检查?
- 观测四件套各自的职责是什么?为什么告警要基于症状而不是基于原因?
- 发布后的观察时长由哪三个时间量的最大值决定?
- 回滚触发条件为什么要在事故之前就定好?
- 为什么说"延迟要看 P99 而不是平均值"?