跳到内容

第 6 卷 / 第 16 章 / 第 1 课

16.1 部署与发布:滚动、蓝绿、金丝雀和功能开关 ​

假设某团队每次发布都要停服 30 秒,一次参数错配还曾造成数小时故障。部署策略因此必须同时控制暴露范围和回滚路径,不能只负责复制新制品。

部署把新制品放进生产环境,发布让用户开始使用新行为。把两者分开,团队才能先验证运行健康,再逐步暴露业务变化。

无论采用哪种策略,前提都是同一份不可变制品经过环境晋升。若每个环境重新构建,验证过的二进制和生产运行的二进制可能不是同一个东西。

先定义可验证信号 ​

Pod 处于 Running 阶段并不保证所有容器就绪,更不证明用户操作成功。发布判断还需要:

  • 技术信号:错误率、延迟、饱和度、重启和依赖失败;
  • 业务信号:下单成功率、结算差异、注册转化;
  • 数据不变量:重复处理、余额或积分对账;
  • 用户反馈:特定客户端或租户的异常。

发布策略只是流量机制,只有连接到这些信号和停止条件时才构成安全控制。

滚动更新 ​

滚动更新逐批替换实例,资源成本低,适合向前/向后兼容的常规发布。

在新旧版本并存期间必须保证:

  • API 和消息 schema 能跨版本通信;
  • 数据库变更先扩展、后收缩;
  • 会话不依赖单个实例本地状态,或有明确排空策略;
  • readiness 只在实例真正能接流量时通过;
  • 终止前停止接新请求并给在途请求留出时间。

例如期望副本数为 10,maxSurge: 20% 允许最多额外创建 2 个副本,maxUnavailable: 0 要求控制器等新副本可用后再减少旧副本。它需要可调度的额外容量,也不保证业务零错误;终止中的 Pod 还可能让实际资源占用暂时超过这 12 个副本的预算。progressDeadlineSeconds 超时会报告进度失败,不会自动替团队回滚。Deployment 文档列出了这些控制边界。

滚动更新不适合新旧版本无法并存的破坏性变更。

蓝绿部署 ​

蓝绿保留两套完整环境,在绿环境验证后一次切换入口:

text
流量 → Blue v1
        Green v2(预热与验证)

切换后:流量 → Green v2
                  Blue v1(回退窗口)

这里的“一次切换”指路由配置变更,连接排空与规则传播仍需时间,不能假设全球请求瞬间一起换版本。两套环境里的队列消费者和定时任务也需要单独启停或划分所有权,HTTP 切流不会替它们选主。

优势是切换和应用回退快;代价是双份容量,以及数据库、缓存、队列等共享状态不能随入口瞬间回退。

如果 v2 已写入 v1 无法理解的数据,切回 v1 可能比继续运行 v2 更危险。因此回退能力要通过兼容性测试证明,不能只看负载均衡器是否能切回。

金丝雀发布 ​

金丝雀先把少量代表性流量送到新版本,再按阶段扩大:

text
内部账号 → 1% → 5% → 25% → 50% → 100%

每一阶段应有:

  • 最短观察窗口和足够样本量;
  • 与基线版本对比的错误率和延迟;
  • 业务指标与关键不变量;
  • 自动暂停/回退阈值;
  • 按风险确定是否需要人工批准,以及谁负责。

这组百分比只是示例。假设独立请求的真实故障率为 1%,仅观察 100 次请求时,完全没看到错误的概率仍有 0.99^100 ≈ 36.6%。因此“这一分钟全绿”可能只是样本太少。

低流量阶段可能看不到稀有错误,百分比也未必代表高价值租户。切流维度可以是租户、区域、账号或请求特征,但要避免同一工作流在两个版本之间随机跳动。

Feature Flag 把功能暴露独立出来 ​

功能开关允许代码已部署但能力尚未向全部用户开放。下面是项目内查询方法的分支片段,依赖提供稳定分桶和故障默认值的 flags,以及返回相同业务契约的两个查询实现:

java
if (flags.enabled("new-ranking", user.id())) {
    return newRanking.query(user);
}
return legacyRanking.query(user);

开关需要:

  • 所有者、用途和到期时间;
  • 稳定的用户分桶,避免体验来回变化;
  • 默认值和配置服务不可用时的行为;
  • 变更审计与紧急关闭路径;
  • 上线完成后的删除任务。

关闭开关只影响后续路径选择,不会撤销已经写入的新数据或在途工作。新旧查询实现必须在回退窗口内继续理解同一份数据。

权限检查、财务规则和数据完整性约束不能简单由客户端开关保护。客户端开关只能控制呈现,服务端仍需执行安全与业务约束。

数据库采用 Expand–Migrate–Contract ​

安全的破坏性变更通常跨多个发布:

  1. Expand:新增可空列、表或兼容 API;
  2. Migrate:回填历史数据,让新旧代码兼容读写;
  3. Switch:切换读取与写入所有权并观测;
  4. Contract:确认旧版本、批处理和消费者都已退出,且回退窗口结束后删除旧结构。

例如把 player_name 重命名为 display_name:只回填历史行还不够,回填期间的新写入也要同步维护,或通过变更捕获持续追赶。切读前核对新旧表示的一致性,再停止旧写入。若一个本地事务可以维护两份表示,就不必为此引入跨库双写。

大表回填要限速、分批、可恢复,并监控锁、复制延迟和存储增长。应用回滚不等于数据自动回滚。

选择策略 ​

条件倾向
常规兼容变更、容量有限滚动更新
需要快速整体切换且能承担双环境蓝绿
风险高、可按流量逐步验证金丝雀
部署与用户暴露需要分离Feature Flag,与其他策略组合

下一课讨论同一部署单元内的辅助容器模式,以及如何用故障实验验证恢复假设。

参考资料 ​

Built with VitePress | Software Systems Atlas