本文讨论发布周期(release cycles)里 deployment strategy 的问题,抛开大规模部署的 big bang deployment

滚动重启、金丝雀发布、AB testing

在 martin fowler 的博客里,金丝雀发布和滚动重启和 AB testing 并没有本质区别,都是 phased approach或者 incremental approach,是 ParallelChange 思想的实践。

当我们拥有一个新版本时:

滚动重启(rolling restart)

rolling restart 会让新旧版本在环境里长时间共存,逐一使节点部署新版本,这样易于发现问题和回滚。

金丝雀发布(canary release)

而金丝雀发布同样允许新旧版本长时间共存,在逐一部署新节点的前提下,逐步利用 LB 之类的基础设施来切分用户,其策略还可以细分为:

先不给新版本,在无流量的情况下在生产环境验证 - 很多大厂的实现都忽略了这点。

尽量让内部用户先使用 - FB 之类的大厂的员工都非常多,使用一个特性开关(名字很多,比如 feature bits, flags, flippers, switches, martin fowler prefers FeatureToggle),单独让内部员工使用,来检查其中的问题。 amazon 使用暗部署(dark launch),而蚂蚁金服使用灰度环境(grey environment) 来将生产的真流量释放到新版本上。

然后逐步开放给新用户使用。这个过程中涉及到的策略和方法是:使用 LB 的路由策略,将流量逐一发布到特定新版本节点上;基于用户选择,只有特定用户的流量可以进入到新版本的机器里。大部分大厂都采用基于节点的流量分配法则,实际上还可以根据源 ip、地理位置和用户人群划分来解决这个问题。

AB testing

金丝雀发布因为可以使不同的人群体验不同版本,所以可以被看作 AB testing 的等同 implementation。

但是,金丝雀发布的用意是“发现新版本问题,提供回滚的灵活性”,而 AB testing 的用意是为了验证和比对不同的具体策略的效果。金丝雀发布必然导致软件的新版本代替旧版本(注意软件的版本和代码的版本是不一样的),间接地提供了 AB testing 的能力;而真正厉害的 ab testing 却可以不依赖软件版本更新,只把用户加以区分,并配以不同的策略即可。

蓝绿部署

蓝绿部署的特性要求:

1 旧版本存在于蓝集群。

2 新版本部署于绿集群。

绿集群在上线的时候完全没有流量,在充分验证完成以后一下子通过 LB 把流量完全切入绿集群。

金丝雀发布在事实上是在同一套物理环境里实现渐进式替换,优点是要求的节点数量更小,发现 last minute 问题的时候影响面更可控,缺点是出了问题也要逐步回滚,系统是在进行有损服务的。

蓝绿部署要求生产环境有两套环境,优点是可以直接通过 LB 一键回滚,缺点是占用节点数量过多,出现 last minute 问题的时候影响面更大。蓝绿部署的缺点是大部分公司不直接采用它的原因。

特性开关

特性开关的细节可以参考Feature Toggles (aka Feature Flags),不同的开关实际上体现的是不同的精细化程度。

特性开关的要点是解耦 decision point 和 decision 决策逻辑。

维护这些开关的长期性和动态性实际上需要很重的架构权衡。

此处输入图片的描述

影子部署(Shadow Deployment / Traffic Mirroring)

影子部署是一种高级的发布策略,它将生产环境的流量镜像复制到新版本服务上,但新版本的响应不会被返回给用户。这种策略主要用于:

  • 在生产环境中验证新版本的正确性,而不影响真实用户
  • 测试新版本的性能和稳定性
  • 验证数据库迁移的正确性
  • 在实际流量下进行压力测试

优点:

  • 完全不影响用户体验
  • 可以在真实生产流量下验证新版本
  • 可以提前发现潜在问题

缺点:

  • 需要双倍的计算资源
  • 增加了监控和日志的复杂度
  • 不适合有写操作的场景(可能造成数据不一致)

典型实现: Istio 的 Traffic Mirroring 功能

红黑部署(Red-Black Deployment)

红黑部署实际上是蓝绿部署的一种变体,两者的核心思想完全一致:

  • 维护两套独立的生产环境
  • 新版本在完全隔离的环境中进行验证
  • 通过切换流量实现版本切换

与蓝绿部署的区别:

  • 命名习惯不同:蓝绿 vs 红黑
  • 红黑部署通常更强调版本间的完全隔离和并行运行
  • 某些实现中,红黑部署可能同时运行多个版本(红、黑、黄等)

在实际应用中,红黑部署和蓝绿部署可以视为同一类策略的不同命名方式。

渐进式交付(Progressive Delivery)

渐进式交付是一种发布理念的统称,它强调通过渐进式的方式来发布新功能,包括但不限于:

  • 金丝雀发布
  • 蓝绿部署
  • A/B 测试
  • 特性开关
  • 影子部署

渐进式交付的核心价值在于:

  1. 降低风险:渐进式地暴露新功能,发现问题可以快速回滚
  2. 快速反馈:通过真实用户的反馈来优化产品
  3. 精细控制:可以基于用户属性、地理位置、设备类型等进行精准的流量控制
  4. 自动化验证:结合自动化测试和监控,实现智能化的发布决策

各部署策略对比

策略 资源占用 回滚速度 风险控制 适用场景
滚动重启 慢(逐步) 中等 一般业务,资源受限
金丝雀发布 中等 需要精细流量控制
蓝绿/红黑部署 关键业务,要求快速回滚
影子部署 不适用 极高 仅用于验证,不切换流量
A/B 测试 不适用 产品功能验证

Kubernetes 中的实现方式

滚动重启(Rolling Update)

1
2
3
4
5
6
7
8
9
10
11
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 最多可以多出 1 个 Pod
maxUnavailable: 1 # 最多可以有多少个 Pod 不可用

蓝绿部署

通过两个 Deployment 实现,配合 Service selector 切换流量。

金丝雀发布

使用 Istio 进行流量分割:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
http:
- match:
- headers:
end-user:
exact: jason
route:
- destination:
host: reviews
subset: v2
- route:
- destination:
host: reviews
subset: v1

特性开关分类

根据 Martin Fowler 的分类,特性开关可以分为以下几类:

Release Toggles(发布开关)

用于控制新功能的发布时机。一旦功能完全发布,这类开关应该从代码中移除。

  • 用途:解耦部署和发布
  • 生命周期:短期(通常几天到几周)
  • 示例:新功能开关,功能稳定后即删除

Experiment Toggles(实验开关)

用于 A/B 测试,验证不同策略的效果。

  • 用途:产品决策支持
  • 生命周期:短期(实验周期)
  • 示例:不同 UI 布局的测试开关

Ops Toggles(运维开关)

用于控制系统的运行时行为,帮助运维人员处理紧急情况。

  • 用途:运维应急响应
  • 生命周期:长期(可能存在数月甚至数年)
  • 示例:限流开关、降级开关、熔断开关

Permission Toggles(权限开关)

基于用户属性控制功能的可见性。

  • 用途:权限管理和功能授权
  • 生命周期:长期
  • 示例:VIP 功能开关、内测用户专属功能

特性开关的维护成本很高,需要:

  • 统一的管理平台
  • 监控和告警机制
  • 定期清理机制
  • 文档和规范

回滚策略对比

策略 回滚方式 回滚时间 数据一致性 复杂度
滚动重启 逐步回滚到旧版本 较长 可能不一致 中等
金丝雀发布 调整流量比例 一致 较高
蓝绿/红黑部署 切换流量 极快 一致
影子部署 无需回滚 不适用 不适用

数据库迁移处理

不同部署策略下的数据库迁移需要特别注意:

滚动重启

  • 采用渐进式数据库迁移
    1. 先添加新字段/表(不删除旧字段)
    2. 部署兼容新旧版本的应用
    3. 进行滚动重启
    4. 数据迁移完成后,部署只使用新版本的应用
    5. 最后删除旧字段/表

蓝绿部署

  • 可以采用停机迁移双写迁移
    1. 停机迁移:停服 → 迁移数据库 → 启动新版本(简单但影响用户体验)
    2. 双写迁移:新旧版本同时读写 → 数据同步 → 切换到新版本

金丝雀发布

  • 必须使用兼容性迁移
    • 数据库 Schema 必须同时兼容新旧版本
    • 采用添加字段而非修改字段的方式
    • 使用数据库触发器或应用层逻辑保证数据一致性

最佳实践

  1. 永远不要修改数据库字段类型,而是添加新字段
  2. 使用版本化 Schema,记录每次变更
  3. 自动化数据库迁移脚本,并与应用代码版本绑定
  4. 生产环境前充分测试迁移脚本
  5. 保留回滚方案,确保迁移可以撤销