55.1 发布策略 (滚动 / 蓝绿 / 金丝雀)

一句话

发布策略决定 “新版本怎么替换旧版本”,核心权衡是风险 ↔ 成本 ↔ 速度。 没有最好的策略,只有最适合你回滚能力的策略。

四种策略一张图看全

策略 做法 停机 风险 回滚速度 资源成本 实现难度
停机发布 停服务 → 更新 → 启动 有 中 慢 1× 最低
滚动发布 逐台替换,边更新边服务 无 中 慢(逐台滚回) 1× 低
蓝绿发布 备一套全新环境,流量一次切换 无 低 秒级 2× 中
金丝雀发布 新版本先给 1–5% 流量,观察后放量 无 最低 快(切走流量) 略 >1× 高

逐个拆解

① 停机发布 (Big Bang / Recreate)

做法:停掉旧版本 → 部署新版本 → 启动。

  • 优点:最简单,没有 “新旧共存” 的问题,不用考虑兼容。
  • 缺点:有停机窗口 (哪怕 10 秒)。
  • 适合:内部系统、半夜低峰可停机的服务、早期产品。
  • 现实:K8s 里的 Recreate 策略就是它——先杀光旧 Pod,再起新 Pod。

💡 别瞧不起它。“能停机” 是一种巨大的奢侈——它意味着你可以不做任何兼容性设计。很多系统为了 “不停机”,付出了远超停机窗口价值的复杂度。


② 滚动发布 (Rolling Update)

做法:一台一台地替换。更新完一个实例、验证通过,再更新下一个,直到全部换成新版本。

  • 优点:不停机、不需要额外资源。
  • 缺点:发布期间新旧版本同时在线 (这是关键,见下面的铁律);回滚也是滚动的 (慢)。
  • 适合:无状态服务。这是 K8s 的默认策略。

K8s 里控制滚动速度的两个参数:

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # 滚动期间最多多起几个新 Pod(腾挪空间)
      maxUnavailable: 0  # 滚动期间最多允许几个 Pod 不可用(0 = 不中断服务)

🔑 滚动发布的命门是 “新旧共存窗口”:发布过程中,一部分请求打到新版本,一部分打到旧版本。 如果新旧版本不兼容 (接口变了、数据格式变了),这个窗口里就会随机报错——而且是 “时好时坏” 的随机报错,最难查。


③ 蓝绿发布 (Blue-Green)

做法:备一套完整的新环境 (绿),旧环境 (蓝) 继续服务;新环境验证通过后,把流量一次性从蓝切到绿。

        ┌─────┐  切换  ┌─────┐
用户 ──▶│ 蓝  │ ─────▶ │ 绿  │
        │旧版本│        │新版本│
        └─────┘        └─────┘
  • 优点:回滚是秒级的 (把流量切回蓝即可),风险低。
  • 缺点:资源成本翻倍 (两套环境同时存在)。
  • 关键问题——数据库怎么办:
方案 做法 代价
共享一个数据库 蓝绿只切应用,不切数据库 简单,但数据库 schema 变更必须向后兼容(否则绿的代码读不懂蓝的数据)
各自独立数据库 蓝绿各带一套库 切流量前要处理数据同步/迁移,复杂
  • 适合:核心链路、对 “回滚要快” 有强要求的场景。但先想清楚数据库,再谈蓝绿。

⚠️ 蓝绿最大的坑:只做了应用蓝绿,没做数据库兼容。结果一切到绿,绿代码读不懂旧表结构报错;想切回蓝,蓝代码也读不懂绿改过的数据——卡死,两头都不是。


④ 金丝雀发布 (Canary)

做法:新版本先只接 1–5% 流量,盯着指标看;没问题再逐步放量 (5% → 25% → 50% → 100%)。

用户流量 ──┬── 95% ──▶ 旧版本
           └── 5%  ──▶ 新版本(金丝雀)← 出了问题只炸这 5%
  • 优点:风险最小——真出了问题,只影响一小撮用户,且能立刻切走。
  • 缺点:需要流量治理能力 (按比例 / 按用户路由);实现和运维成本高。
  • 怎么分流:
方式 做法
权重路由 网关/Ingress 按 95:5 比例分发
用户分片 按 userID % N 或 hash 把固定用户导到新版本
Service Mesh Istio 的 traffic shifting,按权重切
  • 看什么指标再放量 (不是 “看看没报错”):
指标 看什么
错误率 金丝雀版本错误率是否显著高于旧版
延迟 P99 是否恶化
业务指标 下单成功率、支付成功率
  • 适合:有流量治理能力、流量足够大的服务 (流量太小,5% 样本没意义)。

三条铁律

  1. **回滚能力比发布能力更重要。**判断发布策略好不好,先问 “回滚要几秒、演练过没”。
  2. **滚动 / 金丝雀期间,新旧版本必须兼容。**因为两者会同时在线——兼容不是可选项,是前提。
  3. 数据库变更必须走 Expand-Contract (先加列 → 迁移 → 再删列)。否则一旦改了表,新旧两个版本都没法回滚。(详细见 55.4 数据库变更管理 (Expand-Contract))

怎么选:一张决策表

你的情况 选
内部系统、能接受停机 停机发布(别为它过度设计)
无状态服务、要不停机、想简单 滚动发布(K8s 默认)
核心链路、回滚必须秒级、能接受双倍成本 蓝绿发布(先解决数据库)
流量大、有流量治理、要风险最小 金丝雀发布

🔑 现实里最常见的是组合:滚动 + 金丝雀——先放 5% 金丝雀,再滚动全量。既拿了金丝雀的低风险,又不用长期养两套环境。


反模式

反模式 后果
蓝绿只切应用、不处理数据库 切过去报错,切回来也报错,卡死
金丝雀放量不看指标,只"看看没报错" 5% 时的隐性 bug 在 100% 时爆炸
滚动期间新旧接口不兼容 随机报错,时好时坏,最难查
没有演练过回滚 真出事的回滚,大概率失败

落到数字:批次、观察窗口与阈值

策略 批次 观察窗口 自动回滚阈值
滚动 25% × 4 每批 2–5min 错误率 > 1% 或 P99 > 基线 1.5×
蓝绿 0% → 100% 切换后 5–15min 同上,回滚 = 切回旧池
金丝雀 1% → 5% → 25% → 100% 每档 5–30min 错误率 / 延迟 / 业务指标三选二
停机 — — 有维护窗口才用
# Argo Rollouts 风格(示意)
strategy:
  canary:
    steps:
      - setWeight: 1
      - pause: {duration: 10m}
      - setWeight: 5
      - pause: {duration: 10m}
      - analysis: {templates: [{templateName: error-rate}]}
      - setWeight: 25

关键不在 “怎么切”,而在 “切多少、看多久、什么指标算坏”——这三件事没有数字,金丝雀就只是 “慢一点的滚动”。

自检

Q1 你选蓝绿发布,但应用绿环境读旧数据库时一直报错。根因是什么?怎么提前避免?

▸ 答案:只做了应用蓝绿,没做数据库兼容。根因是数据库 schema 变更没走 Expand-Contract——绿代码读不懂旧表结构。避免方法:先做向后兼容的 schema 变更 (先加列),再切换;回滚时蓝代码也能读懂绿改过的数据。

Q2 滚动发布期间,监控显示错误率 “时好时坏”、随机波动。最可能的原因是什么?

▸ 答案:新旧版本不兼容。发布窗口里一部分请求落到新版本、一部分落到旧版本,接口或数据格式不一致导致随机报错。滚动发布的前提就是 “新旧兼容”——这个前提没满足,滚动就是事故。

Q3 金丝雀的 “坏” 用什么指标判定?

▸ 答案:至少两类:技术指标(错误率、P99、CPU)与业务指标(下单成功率、支付转化)。只看技术指标会漏掉 “接口 200 但业务失败”;只看业务指标则反应太慢。常见做法是 “错误率 1% 或业务成功率下降 2% 即回滚”。

Q4 蓝绿发布最大的隐性成本是什么?

▸ 答案:双倍资源与数据兼容:绿环境要完整跑起来,资源翻倍;更贵的是数据库要同时兼容新旧代码(Expand-Contract),否则切换瞬间就报错。蓝绿不是 “点一下切流量”,而是 “两套兼容性都要维护”。

Q5 数据库变更怎么和发布策略配合?

▸ 答案:走 Expand → Migrate → Contract:先加列/加表(新旧都能跑),再双写/回填,最后删旧列。任何 “先删后发” 的做法,都会让滚动/蓝绿在切换窗口里直接失败——回滚也没用,因为旧代码已经读不到数据了。