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 → 关闭监听 → 处理完剩余请求 → 退出)
- 优雅退出:收到 SIGTERM 后应当先停止接收新请求(从负载均衡摘除),处理完在手的请求再退出。没有优雅退出,每次滚动更新都是一次小范围的用户报错。 常见坑:容器的默认停止宽限期短于请求最长耗时。
- 长连接处理:WebSocket / SSE 这类流式连接(你的 E 平台 SSE 场景正是如此)不会自动中断,要么让旧实例在不再有新流量后自行结束(graceful drain),要么客户端支持断线重连。
- 就绪判断必须是真实的:就绪探针如果只返回 200,等于没有探针——探针要检查它真正依赖的东西(能否连上数据库、依赖的下游是否可达),但不要把下游抖动写进存活探针(见下)。
三、金丝雀:核心不是"切流量",而是"看得懂"
金丝雀分析的最小闭环:
切 5% 流量 → 观察窗口(≥ 收敛时间)→ 对比关键指标 → 达标则放大/否则回滚
↑ ↑
观察窗口必须长于指标收敛时间 指标必须是"这个服务"的,且新旧版本可分
成败完全取决于两件事:
- 指标可用性:至少需要一个成功率指标和一个延迟指标,并且能区分金丝雀版本与稳定版本(两套 Deployment/Service,或依赖服务网格的路由标签)。没有可分辨的指标,自动分析就是玄学。
- 自动化的阈值要有依据:阈值应来自历史波动范围,而不是拍脑袋的"错误率 < 1%"。
由此得到一条严格的先后次序:先把服务观测做扎实(成功率、延迟、饱和度、业务指标),再谈自动金丝雀。 顺序反了会得到一套"有自动化但没人信"的机制。
四、健康检查三件套:很多部署事故其实死在这里
| 探针 | 作用 | 失败后果 | 设计要点 |
|---|---|---|---|
| startupProbe | 启动慢的应用在被 liveness 误判前的保护期 | — | 覆盖最坏启动时间;它成功前另外两个探针不生效 |
| readinessProbe | 是否可以接收流量 | 从负载均衡摘除 | 检查自身关键依赖;不要因为下游抖动就让自身下线(会雪崩) |
| livenessProbe | 是否应当被重启 | 重启容器 | 只检查进程是否死锁;绝不要依赖外部服务,否则下游一抖,你所有实例集体重启 |
最常见的反模式:把 liveness 探针写成"检查数据库能不能连"。 下游挂掉的瞬间,你的服务会被自己重启一遍——然后恰好赶上下游恢复,看起来像"自愈了",实际是把一次外部故障放大成了全实例重启+缓存冷启动。
五、怎么选:一张决策表
服务能不能中断 5 分钟?
能(内部工具/批处理) → Recreate
不能 ↓
能否接受双倍资源?
能,且需要原子切换 → Blue/Green
不能 ↓
有没有可用的新/旧版本对比指标?
有 → Canary(可叠加 Feature Flag)
没有 → 先把观测补齐,期间用 Rolling + Feature Flag
是否要求"上线"和"对用户可见"分离? → Feature Flag
补充两条现实约束:
- 资源敏感的小团队:Rolling + Feature Flag 覆盖了 80% 的需求,别一上来就做蓝绿。
- 有状态/独占资源的服务(定时任务消费者、需要持有一个硬件资源的进程):不适合并行版本,Recreate 或主备切换更合理。
六、回滚:唯一必须提前演练的东西
回滚预案要写清楚四件事:
① 触发条件:哪个指标的什么阈值、持续多久 → 谁有权决定回滚
② 回滚动作:具体命令/按钮在哪,执行人是谁(不是"联系运维")
③ 数据形状:这次变更有没有迁移?回滚后旧代码能否承受新 schema
④ 回滚后:要不要清数据/解开关/通知用户
没有演练过的回滚预案等于不存在。 每个季度至少真跑一次(哪怕在 staging),并把用时记下来——这个数字就是你恢复能力(MTTR)的真实值。
动手:可观察结果
| 动作 | 产出物 | 判断标准 |
|---|---|---|
| 把某个服务的就绪探针写真实 | 探针配置 | 故意让依赖不可用 → 实例被摘流量但不重启 |
| 故意把 liveness 写成依赖数据库 | 一次实验记录 | 观察下游抖动时的连锁重启(做过一次就再也不会这么写) |
| 配置优雅退出 | 停止脚本/信号处理 | 部署期间没有 502;记录最长请求处理时长与终止宽限期的关系 |
| 写一份回滚预案 | 一页文档 | 四要素齐全;随机找个人能照着执行 |
| 一次回滚演练 | 计时 + 记录 | 在预定目标时间内完成;脚本/按钮路径已被验证 |
故障注入
| 注入方式 | 观察什么 | 说明的现象 |
|---|---|---|
| 取消优雅退出,直接发 SIGTERM | 部署期间的用户报错率 | 立刻看到 502/连接重置——这就是多数线上抖动的原因 |
| 金丝雀指标不可用时自动分析 | 它做了什么决定 | 无指标会"如实通过",这就是"自动化但不可信" |
| 把 liveness 探针依赖下游 | 下游抖动时本机行为 | 全实例重启;你会盯着一个"看起来在自愈"的灾难 |
| 蓝绿切换时保留长连接 | 旧环境是否还有流量 | 连接排干前切流量会丢正在进行的会话 |
| 回滚前先看有没有迁移 | 回滚能否成功 | 有 contract 类迁移时,回滚=旧代码遇到新 schema,必失败 |
自测题
- "部署"和"发布"这两个词在本章的定义差别是什么?哪个技术手段能彻底分开它们?
- liveness 与 readiness 探针分别检查什么?为什么 liveness 不能依赖下游服务?
- 金丝雀自动分析的前提是什么?缺了这个前提会出现什么后果?
- 优雅退出缺位会造成什么现象?它和终止宽限期有什么关系?
- 一份合格的回滚预案必须写清哪四件事?