KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

04 · 部署策略:从"删了重启"到"精确控制爆炸半径" — keel 龙骨

这一章回答:五种主流部署方式各自的回滚成本、资源代价和适用场景——以及为什么"能快速回滚"比"上线不出错"更值得投入。

这一章回答:五种主流部署方式各自的回滚成本、资源代价和适用场景——以及为什么"能快速回滚"比"上线不出错"更值得投入。

上线策略的本质是一个权衡:变更发生时,你愿意让多少用户承担多少时间的风险,以及发现不对时断开的速度有多快。

一、五种策略横向对比

策略 流量切换方式 回滚速度 额外资源 适用场景 主要缺点
重建(Recreate) 先停旧版再起新版 慢(要重新起旧版) 无 内部工具、独占资源的批处理 有停机时间
滚动(Rolling) 逐批替换实例 中(再滚回来) 略(多 1~2 个实例) 绝大多数无状态服务 期间新旧并存,需兼容(见03章迁移)
蓝绿(Blue/Green) 两套完整环境,整体切换 秒级(切回去) 双倍 需要原子切换、停机不可接受 资源成本最高,切换要处理长连接与会话
金丝雀(Canary) 小比例引流,按步骤放大 快(把流量调回 0) 低(少量金丝雀实例) 用户量大的在线服务 强依赖指标质量,否则就是盲开
特性开关(Feature Flag) 代码先上线,功能后打开 秒级(关开关) 无 需要"部署"与"发布"解耦 代码里多分支,开关本身要治理

一个关键区分:部署(deploy)= 把新产物放上去;发布(release)= 让新功能对用户可见。 特性开关是唯一能把这两件事彻底分开的手段,所以它经常与金丝雀组合使用:先用开关控制"哪些人看到",再用金丝雀控制"多少流量走新版本"。

二、滚动更新:默认选项,但有三个必须先搞对的点

initialDelaySeconds/startPeriod  ← 应用启动慢时,避免未就绪就被判定失败
maxSurge / maxUnavailable        ← 控制并行多起几个、允许多少个不可用
优雅退出(SIGTERM → 关闭监听 → 处理完剩余请求 → 退出)
  1. 优雅退出:收到 SIGTERM 后应当先停止接收新请求(从负载均衡摘除),处理完在手的请求再退出。没有优雅退出,每次滚动更新都是一次小范围的用户报错。 常见坑:容器的默认停止宽限期短于请求最长耗时。
  2. 长连接处理:WebSocket / SSE 这类流式连接(你的 E 平台 SSE 场景正是如此)不会自动中断,要么让旧实例在不再有新流量后自行结束(graceful drain),要么客户端支持断线重连。
  3. 就绪判断必须是真实的:就绪探针如果只返回 200,等于没有探针——探针要检查它真正依赖的东西(能否连上数据库、依赖的下游是否可达),但不要把下游抖动写进存活探针(见下)。

三、金丝雀:核心不是"切流量",而是"看得懂"

金丝雀分析的最小闭环:

切 5% 流量 → 观察窗口(≥ 收敛时间)→ 对比关键指标 → 达标则放大/否则回滚
              ↑                              ↑
        观察窗口必须长于指标收敛时间        指标必须是"这个服务"的,且新旧版本可分

成败完全取决于两件事:

由此得到一条严格的先后次序:先把服务观测做扎实(成功率、延迟、饱和度、业务指标),再谈自动金丝雀。 顺序反了会得到一套"有自动化但没人信"的机制。

四、健康检查三件套:很多部署事故其实死在这里

探针 作用 失败后果 设计要点
startupProbe 启动慢的应用在被 liveness 误判前的保护期 — 覆盖最坏启动时间;它成功前另外两个探针不生效
readinessProbe 是否可以接收流量 从负载均衡摘除 检查自身关键依赖;不要因为下游抖动就让自身下线(会雪崩)
livenessProbe 是否应当被重启 重启容器 只检查进程是否死锁;绝不要依赖外部服务,否则下游一抖,你所有实例集体重启

最常见的反模式:把 liveness 探针写成"检查数据库能不能连"。 下游挂掉的瞬间,你的服务会被自己重启一遍——然后恰好赶上下游恢复,看起来像"自愈了",实际是把一次外部故障放大成了全实例重启+缓存冷启动。

五、怎么选:一张决策表

服务能不能中断 5 分钟?
  能(内部工具/批处理)            → Recreate
  不能 ↓
能否接受双倍资源?
  能,且需要原子切换               → Blue/Green
  不能 ↓
有没有可用的新/旧版本对比指标?
  有                              → Canary(可叠加 Feature Flag)
  没有                            → 先把观测补齐,期间用 Rolling + Feature Flag
是否要求"上线"和"对用户可见"分离? → Feature Flag

补充两条现实约束:

六、回滚:唯一必须提前演练的东西

回滚预案要写清楚四件事:
  ① 触发条件:哪个指标的什么阈值、持续多久 → 谁有权决定回滚
  ② 回滚动作:具体命令/按钮在哪,执行人是谁(不是"联系运维")
  ③ 数据形状:这次变更有没有迁移?回滚后旧代码能否承受新 schema
  ④ 回滚后:要不要清数据/解开关/通知用户

没有演练过的回滚预案等于不存在。 每个季度至少真跑一次(哪怕在 staging),并把用时记下来——这个数字就是你恢复能力(MTTR)的真实值。

动手:可观察结果

动作 产出物 判断标准
把某个服务的就绪探针写真实 探针配置 故意让依赖不可用 → 实例被摘流量但不重启
故意把 liveness 写成依赖数据库 一次实验记录 观察下游抖动时的连锁重启(做过一次就再也不会这么写)
配置优雅退出 停止脚本/信号处理 部署期间没有 502;记录最长请求处理时长与终止宽限期的关系
写一份回滚预案 一页文档 四要素齐全;随机找个人能照着执行
一次回滚演练 计时 + 记录 在预定目标时间内完成;脚本/按钮路径已被验证

故障注入

注入方式 观察什么 说明的现象
取消优雅退出,直接发 SIGTERM 部署期间的用户报错率 立刻看到 502/连接重置——这就是多数线上抖动的原因
金丝雀指标不可用时自动分析 它做了什么决定 无指标会"如实通过",这就是"自动化但不可信"
把 liveness 探针依赖下游 下游抖动时本机行为 全实例重启;你会盯着一个"看起来在自愈"的灾难
蓝绿切换时保留长连接 旧环境是否还有流量 连接排干前切流量会丢正在进行的会话
回滚前先看有没有迁移 回滚能否成功 有 contract 类迁移时,回滚=旧代码遇到新 schema,必失败

自测题

  1. "部署"和"发布"这两个词在本章的定义差别是什么?哪个技术手段能彻底分开它们?
  2. liveness 与 readiness 探针分别检查什么?为什么 liveness 不能依赖下游服务?
  3. 金丝雀自动分析的前提是什么?缺了这个前提会出现什么后果?
  4. 优雅退出缺位会造成什么现象?它和终止宽限期有什么关系?
  5. 一份合格的回滚预案必须写清哪四件事?

进入 keel 阅读