灰度发布、滚动发布、蓝绿发布应该如何选择?
发布策略解决的不是“怎样把镜像换掉”这么简单,而是如何控制新版本暴露范围、发现风险并恢复。滚动发布、蓝绿发布和灰度发布并非互斥:滚动描述实例替换方式,灰度描述流量逐步放量方式,蓝绿描述两套完整环境的切换方式。实际平台经常组合使用。
1. 先看四个决策维度
选择策略前回答:
- 新旧版本能否同时运行并共享数据库、缓存和消息?
- 失败后仅切回流量是否足够,还是数据已发生不可逆变化?
- 系统是否有足够资源同时保留两套容量?
- 能否按版本观测错误率、延迟和业务指标?
没有版本维度的监控,所谓灰度只是“少量用户替你试错”,无法准确判断新版本是否更差。
2. 滚动发布
滚动发布逐批停止旧实例、启动新实例:
起始: old old old old
批次1: new old old old
批次2: new new old old
完成: new new new new
Kubernetes Deployment 示例:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 25%
maxUnavailable: 0 尽量保证可用容量,maxSurge 允许临时增加实例。百分比会按 Kubernetes 规则取整,副本数很小时结果可能与直觉不同;还应结合 minReadySeconds、progressDeadlineSeconds、资源配额和 PodDisruptionBudget 验证真实可用容量。PDB 只约束部分自愿中断,不会阻止 Deployment 自身滚动替换,也不是高可用保证。真实可用性还取决于 readinessProbe、启动时间、连接预热和外部负载均衡摘流速度。
优点:资源增量较小、平台原生支持、适合频繁小版本。缺点:发布窗口内新旧实例同时处理流量;发现故障时部分请求已经由新版本执行;回滚仍需再次滚动。
适合:无状态服务、向后兼容变更、测试和观测成熟的日常发布。
3. 蓝绿发布
蓝环境运行当前版本,绿环境部署完整新版本并验证,随后一次切换入口:
+-> Blue v1 (100%)
用户 -> 路由
+-> Green v2 (0%)
切换后 Green v2 (100%),Blue v1 暂时保留
优点:切流和回切快;新环境可在承接生产流量前完整预热;环境边界清晰。缺点:需要接近双倍计算资源;数据库、消息和第三方副作用通常仍然共享,不能真正复制整个世界;配置漂移可能让绿环境测试失真。
适合:切换窗口要求短、回切速度重要、资源预算充足的核心系统。若新版本执行了不兼容数据库迁移或发出不可逆支付指令,切回蓝环境并不能撤销数据影响。
4. 金丝雀与灰度发布
金丝雀先让少量流量进入新版本,根据健康结果逐步放大:
v2: 1% -> 5% -> 20% -> 50% -> 100%
流量可按随机比例、用户 ID、租户、地区、设备版本或内部员工分组。稳定哈希比每次随机更适合有状态体验:同一用户持续落到同一版本,便于复现和避免流程前后版本变化。
优点:限制故障爆炸半径,能在真实流量下验证。缺点:发布持续时间更长;要求网关、服务网格和指标支持版本维度;低频错误在 1% 流量下可能很久才出现;分组不当会产生样本偏差。
“内部员工正常”不能证明普通用户正常,因为账户权限、数据规模和使用路径完全不同。
5. 策略对比
| 维度 | 滚动发布 | 蓝绿发布 | 金丝雀/灰度 |
|---|---|---|---|
| 额外资源 | 低到中 | 高,接近双环境 | 低到中 |
| 新旧并存 | 是 | 切换前两环境并存 | 是,且持续更久 |
| 回切速度 | 中,需要再次滚动 | 快,切换路由 | 快,停止灰度流量 |
| 风险暴露 | 按批次 | 切换后可能立即全量 | 按流量比例控制 |
| 平台复杂度 | 低 | 中 | 高,需要精细路由与指标 |
| 适合场景 | 高频兼容发布 | 核心系统快速切换 | 高风险变化、真实流量验证 |
实际常用组合是:先把绿色/新 ReplicaSet 准备好,再按 1%、10%、50% 灰度流量,最后完成滚动或蓝绿切换。
6. Readiness 不是进程启动成功
新实例启动后需要完成配置加载、连接池建立、类预热、缓存初始化,才能接流量。readinessProbe 应验证“当前实例可以安全服务”,但不能每次深度调用所有下游,否则依赖波动会造成实例集体摘除。
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
periodSeconds: 5
failureThreshold: 3
livenessProbe 只判断进程是否需要重启。把数据库暂时超时纳入 liveness,可能导致所有 Pod 同时重启,扩大事故。启动慢的应用应配置 startupProbe,在启动探针成功前抑制其他探针,避免 JVM 预热阶段被反复重启。探针端点应轻量、设置超时,并防止未经授权暴露详细依赖和配置状态。
下线时要先停止接收新请求,再等待在途请求和消息处理完成:
readiness=false -> 负载均衡摘流 -> 等待传播与 drain -> 进程退出
仅配置 terminationGracePeriodSeconds 而没有应用优雅停机,仍可能中断事务。
7. 数据库决定能否安全回滚
应用可以保留两个镜像,数据库 Schema 却通常只有一份。采用 Expand and Contract:
- 先新增可空列、表或索引,旧代码仍可运行。
- 发布同时兼容新旧结构的应用。
- 回填数据并验证。
- 新代码切换读取新结构。
- 观察完成后再删除旧列。
禁止在同一发布中先重命名列,再部署只认识新列的应用。滚动期间旧实例会立即失败,回滚也会因旧 Schema 消失而失败。
Feature Flag 可分离“代码部署”和“能力启用”,但数据库写入格式仍需兼容。开关关闭只能停止新路径,不能自动恢复已改变的数据。
8. 消息、缓存与会话的版本兼容
消息消费者升级通常比 HTTP 更难同步:旧消息可能在队列中停留数天。新消费者要能读取旧 Schema,旧消费者在滚动窗口内也可能收到新消息。优先新增可选字段,并为事件维护版本与兼容测试。
缓存值若直接序列化 Java 对象,新旧版本可能互相无法反序列化。可使用稳定 DTO Schema、版本化 key 或“双读旧 key、写新 key”的迁移策略。
本地 Session 或内存状态会使灰度粘性复杂化。生产服务优先无状态化;确需粘性会话时,应确认回切后会话格式仍兼容。
9. 灰度指标与自动判定
至少按 service + version + route 比较:
- 请求量、5xx、业务错误码。
- P50/P95/P99 延迟和超时。
- CPU、内存、GC、线程池、连接池。
- 下游调用错误与重试。
- 订单成功率、支付转化率等业务护栏。
只看整体指标会稀释 1% 灰度的故障。假设 v2 的错误率为 20%,在总流量中只占 1%,整体错误率仅上升约 0.2 个百分点,可能低于告警阈值。
自动回滚规则需要最小样本量、持续窗口和多指标判断:
灰度请求 >= 5000
且连续 5 分钟
v2 5xx > v1 5xx + 0.5%
或 v2 P99 > v1 P99 × 1.3
=> 停止放量并回切
低流量服务可能无法快速达到统计显著性,应结合合成检查和人工审批。
10. Feature Flag 的作用与风险
特性开关允许代码先部署、功能后启用,并按租户或用户灰度:
if (featureFlags.enabled("new-pricing", tenantId)) {
return newPricing.calculate(command);
}
return oldPricing.calculate(command);
开关不是永久分支管理。每个开关应有负责人、创建日期、默认值、过期日期和删除任务。开关平台不可用时必须有安全默认行为;关键决策最好在请求内保持一致,避免一次业务流程中途变化。
不要让两个版本分别依据不同开关配置处理同一事件,否则排障会非常困难。配置快照和评估结果应进入 trace/log 上下文。
11. 回滚与前滚
回滚不是总是最佳恢复方式:
- 新版本已经写入旧版本无法理解的数据。
- 数据库迁移不可逆。
- 第三方 API 或消息 Schema 已切换。
- 漏洞修复不能重新暴露旧版本。
此时更适合快速前滚修复。发布计划应在上线前明确:哪些故障可流量回切,哪些需要数据修复,哪些只能前滚。回滚镜像、配置和数据库兼容窗口必须提前验证,不能在事故中第一次执行。
12. 发布流程示例
构建不可变镜像并生成 SBOM
-> 自动化测试与安全扫描
-> 兼容性/迁移检查
-> 部署新实例但不接流量
-> 冒烟与预热
-> 1% 内部 + 5% 真实流量
-> 自动观察与人工确认
-> 20% -> 50% -> 100%
-> 保留回切窗口
-> 清理旧实例与过期开关
每一阶段都应有明确进入条件、观察时间、停止条件和负责人。不要使用“观察一下没问题就继续”这种无法审计的门槛。
13. 上线检查清单
- 新旧版本能否同时读取数据库、缓存和消息?
- readiness、摘流和优雅停机是否经过故障演练?
- 灰度路由是否稳定,能否定位具体用户和版本?
- 指标是否按版本拆分,并包含业务护栏?
- 自动回滚是否设置最小样本与持续窗口?
- 回切能否处理已写入的新格式数据?
- Feature Flag 是否有安全默认值和删除日期?
- 发布期间容量是否足够,连接池是否随实例增加压垮数据库?
- 是否有不可逆外部副作用,需要前滚或补偿?
- 发布完成后是否清理旧 ReplicaSet、旧环境和临时配置?
发布策略的成熟度不体现在名称,而体现在风险能否被量化和限制。可兼容的数据设计让新旧版本能够共存,分版本观测让团队知道何时停止,经过演练的回切与前滚方案才让“可回滚”不只是一句口号。