编程语言研究书库

云原生部署策略 — 零停机发布的六种打法

云原生部署策略 — 零停机发布的六种打法

这一章讲什么: 版本要天天发,用户要无感知——这个矛盾怎么解。 答案是六种发布策略,每一种都是「新版本怎样见到用户」的一种不同安排: 有的慢慢替换,有的整批切换,有的先只给 5% 的用户试。 本章把每种策略的机制、取舍、K8s 实现和翻车的姿势全部讲透, 最后收在一个真实的金丝雀自动回滚故事里。

1. 主线例子:一个欺诈检查功能的上线

本章的主线例子,是原书结尾的完整实战:给支付服务加一个新的欺诈检查。

  第 1 步:不是直接推给所有人,而是金丝雀:10% 真实流量走新版,90% 留旧版
  第 2 步:前几分钟一切正常
  第 3 步:几分钟后,仪表盘讲故事了:
      金丝雀的错误率在缓慢爬升,支付成功率比平时略差
      ——不算灾难,但越过了事先约定的 SLO 阈值
  第 4 步:阈值是提前接进发布流水线的——
      系统自动停止推进,把流量全部拉回稳定版
      没有半夜被叫起来,没有手工回滚,用户几乎无感
  第 5 步:事后翻日志、修 bug、重发

  图说:渐进式交付的全部意义就在第 4 步——
  「回滚决策」在 calm time(平静时)做成规则,而不是在 incident 时临时靠人判断。
  比例与流程沿原书;具体错误率数值原书未给,属于「越过阈值」的量级。

要理解为什么这套能成立,先要有两个地基:应用长什么样(12-factor)才有资格玩这些策略, 以及策略共用的三个前提(兼容性、冗余、健康检查)。然后从最简单的策略一路讲到金丝雀, 最后是支撑它们的两个底座:服务网格与密钥管理。

2. 12-factor:入场资格证

12-factor app 是 Heroku 工程师提出的十二 SaaS 开发准则,十多年后依然是云原生的 「应用与基础设施之间的基线契约」1。十二条因子压缩成六条主线(全表照原书):

主线覆盖的因子核心要求
一份代码,多处部署Codebase所有环境出自同一个版本库
依赖显式声明Dependenciesrequirements.txt / package.json + 容器化
配置在环境里Config密钥和环境特定值不进代码,运行时注入
后端服务是可换的Backing services数据库、队列、缓存换个实现不该改代码
构建/发布/运行三段分开Build, release, runGitOps 是它的现代延伸
无状态、可抛弃、可并发Processes / Port binding / Concurrency / Disposability / Dev-prod parity / Logs / Admin processes状态外置、端口自包含、日志当事件流、管理任务一次性进程

原书给的 Node.js 例子(照抄结构):数据库连接的全部参数从 process.env.* 读, K8s 里用 secretKeyRef 从 Secret 注进环境变量——同一份镜像,三种环境三种配置, 代码一行不改2。

3. 六种部署策略

3.1 重建(Recreate):全杀再起

全部旧 Pod 杀掉,再起新的。用户吃到停机时间——唯一一种故意有停机的策略。 适用:内部系统、凌晨没人用的请假系统3。

3.2 滚动(Rolling):默认档,分批换

K8s 默认策略:一次换一小批(maxUnavailable: 1 表示最多一个不可用、 maxSurge: 1 表示最多多出一个),服务全程可用4。 但滚动有一个硬前提,原书强调为最关键的要求:

前后向兼容性。 滚动期间新旧版本同时在跑、互相通信——新版要读得了旧版写的数据 (后向),旧版也要读得了新版的(前向)。数据 schema 变更是最常见的翻车点, 解法是 expand-migrate-contract 三步5:

  Expand:    先加新列/新表,不破坏旧版(只增不改)
  Migrate:   新版双读双写——读新旧两处,写也写两处
  Contract:  全部实例都跑新版后,才删旧列旧表

  图说:三步把「一次危险的 schema 变更」拆成「三次安全的变更」。
  代价是中间态要维护两版并存——这是用工程量换零停机。

3.3 蓝绿(Blue-Green):两套全开,一键切换

两套一模一样的生产环境:蓝(当前)和绿(新版)。绿全套部署好、冒烟测试通过后, 把流量开关从蓝拨到绿——原书的实现是改 Service 的 selector(一行 kubectl patch)。 回滚即时:把流量切回蓝即可6。

代价与三个坑7:

  • 双倍基础设施成本(两套都在跑);
  • 数据兼容性:schema 变更仍要走 expand-migrate-contract,两套共用同一个数据库时尤其;
  • 会话陷阱(原书专门点名):会话存在应用内存里,切换瞬间全体用户被登出、 购物车清空——要么应用无状态,要么会话放共享存储(Redis),要么挑低峰切。

配套纪律:切换点要单一(Service selector / Ingress / LB),切换前对绿跑冒烟与回归 并接进 CI 门禁,SLO 一退立刻回拨。

3.4 金丝雀(Canary):小流量试错,逐步放量

先给 5–10% 流量,健康就 10% → 25% → 50% → 100%;任何一步 SLO 破了就退回。 两个实现(照原书)8:

  • Istio(服务网格,见 §4):VirtualService 按权重 90/10 拆分, DestinationRule 定义 v1/v2 两个版本子集;
  • NGINX Ingress(不用网格):注解 canary: "true" + canary-weight: "10"。

成功前提六条:渐进暴露(起步 1–10%)、细粒度流量控制、 SLO 门与自动回滚(每一步都挂在延迟/错误率/业务 KPI 上)、 版本偏斜兼容(canary 和 stable 的数据/API 要双向兼容,写操作要幂等)、 可观测性(canary 对照组比指标)、流水线自动化(权重调整走 PR + 策略检查)9。

3.5 影子(Shadow):复制流量,不应答

新版与生产并行,但真实请求被镜像一份给它——响应永远由稳定版发回, 影子版只默默处理、用于验证。零用户影响,适合压测、性能验证、ML 模型对照10。

两个硬要求(原书点出而容易忽略):影子实例要有独立数据库或写抑制—— 否则一笔订单被写两遍;对比观测:拿影子和稳定的日志/延迟/错误做平行对照。 Istio 实现:mirror: 字段 + mirrorPercentage: 10011。

3.6 A/B 测试:不是发布策略,是实验策略

两个版本同时服务真实用户,按 header/cookie/特性开关路由 (原书例子:X-Experiment: B 的走实验版),目的是测用户行为与业务指标 (转化率、点击),而不是系统稳定性。工程与产品共同负责; 需要埋点、分流一致性(用 LaunchDarkly/Unleash/Flipt 这类特性开关服务)、 统计显著性(实验要跑到置信度再合并优胜版)12。

3.7 六策略对照

策略用户感知回滚速度成本何时用
重建停机慢(重新部署)最低内部系统、可停机
滚动无中(重新滚)低默认;前后兼容时
蓝绿无(会话处理好)即时(流量切回)双倍基础设施要即时回滚的关键系统
金丝雀无快(自动)低(只多 5–10%)高风险变更的日常默认
影子完全无不适用(没接流量)额外算力 + 写隔离压测、模型验证
A/B无按分流回退中等业务实验,不为稳定性

4. 服务网格:不动代码的流量调度层

服务网格(service mesh):专门管服务与服务之间通信的基础设施层。 机制:每个 Pod 旁边注入一个轻量代理(通常是 Envoy),进出流量都过它—— 一堆代理组成数据面,中央的 Istio / Linkerd 管配置、策略、遥测(控制面)13。

它能给的(都不改应用代码)14:

  • 统一的流量控制:路由、重试、熔断、限流;
  • 安全:mTLS(双向 TLS:服务间通信自动加密加身份认证);
  • 深度可观测:每个请求路径的标准指标和追踪;
  • 韧性:熔断器、故障注入(故意注错做演练);
  • 渐进交付的原生支持:金丝雀分流、A/B 正是靠它(§3.4 的 Istio 例子就是 mesh 配置)。

原书的 80/20 例子(照抄):VirtualService 把 payments 的流量 80% 给 v1、20% 给 v215。

判断(我们的,不是书里的): 本章六策略的选择,真正的变量只有两个: 变更有多危险(决定要不要金丝雀/影子)和回滚要多快(决定蓝绿还是滚动)。 A/B 混进这张表其实类别不同——它是实验方法不是发布方法,只是共享同一套分流设施; 把「验证系统」和「验证用户」两类目标分开,选型就不会纠结。 如果错,会错在: Serverless/函数平台上,这些策略大半由平台内建 (流量拆分是托管能力),「选策略」退化成「配平台」,本章的 K8s 中心化视角会失真。

5. 密钥与配置:分开放,都要管

这一节把第 8 章的 ConfigMap/Secret 展开成生产实践16。

配置管理(非敏感值):ConfigMap 存运行参数(端口、日志级别、各功能的启停), 五条例程——配置进 ConfigMap 不进镜像、按环境分 namespace、 清单进版本库但不含敏感值、用 Reloader 之类工具改完自动热加载、 部署前做 schema 校验(admission webhook)。

密钥管理(敏感值)四条理由必须正经做:凭证嵌在代码或镜像里极易被拖走; GDPR/PCI DSS/SOC 2 合规强制要求;多环境必须隔离; 流水线要动态取密钥且不能泄进日志17。七条控制18:

  1. 集中存储:AWS Secrets Manager / Google Secret Manager / HashiCorp Vault / Azure Key Vault;
  2. 接进 K8s:External Secrets Operator 或 Sealed Secrets 把外部密钥同步成 K8s Secret;
  3. 静态加密 + 传输加密(etcd 加密、全程 TLS);
  4. RBAC 细粒度权限;
  5. 自动轮换:定期换密钥;
  6. Git 里只放引用或加密占位符,永不放明文;
  7. ConfigMap 与 Secret 分开引用,边界清晰。

对照表(原书):配置 = 端口/功能启停/URL,常变进版本库;密钥 = token/证书, 加密、禁入版本库、定期轮换19。

6. 生产流水线:把上面所有东西串起来

成熟环境的发布是一条七段流水线,每段自动、可追、可逆20:

  提交 → 构建+测试(含漏洞扫描)→ 推镜像仓库(ECR/Harbor)
       → CD 工具(Argo CD/Flux)应用 Helm/Kustomize 到预发
       → 冒烟 + 金丝雀放 10%
       → SLO 达标后晋升生产
       → 部署后持续验证(监控 + 回滚钩子)

  图说:每个「晋升」都是一道闸;闸的标准是事先写下的指标,不是值班人的临场判断。

密钥与配置在部署时动态注入(原书给了 Jenkinsfile 用 withCredentials 包住 三条 kubectl apply 的例子——凭证不进脚本)21。渐进交付由 Flagger、 Argo Rollouts 这类控制器自动化:流量自动搬、指标自动看、SLO 一退自动回22。 Argo CD 的 Application 声明式例子收尾:selfHeal: true 让集群永远追 Git23。

7. 边界与局限

  1. 重建策略几乎不该在云上出现——本章收录它只为对照;真实世界里连内部系统 也多数用滚动。
  2. 影子部署的写隔离只给了原则:双写怎么抑制、影子读什么数据,生产实现远比 两句话说到的复杂。
  3. 服务网格的成本面没讲:sidecar 的 CPU/内存开销、运维复杂度, 在中小规模常常 outweigh 收益,本章立场是「上了规模就该上 mesh」。
  4. A/B 的统计部分极浅(一句「跑到显著」),实验设计真正的方法论(样本量、 多重检验)需要别的材料。
  5. 会话粘滞(sticky session)与蓝绿的冲突提了一句,没有给「必须粘滞的老系统」 的迁移路径。

8. 可带走的

  1. 零停机的前提是 12-factor 式应用:无状态、配置在环境、日志是事件流;
  2. 滚动更新的命门是前后向兼容;数据库变更走 expand-migrate-contract 三步;
  3. 蓝绿 = 两套全开 + 一次流量切换;回滚即时,但双倍成本、会话陷阱要吃透;
  4. 金丝雀 = 小流量 + SLO 门 + 自动回滚;起步 5–10%,Istio 或 NGINX 注解都能做;
  5. 影子 = 镜像流量不应答;独立数据库/写抑制是红线;
  6. A/B 测用户行为不是系统稳定性,别拿它当发布策略;
  7. 服务网格 = sidecar 代理 + 控制面;不改代码获得 mTLS、流量控制、可观测;
  8. 密钥:集中存、动态取、自动轮换、Git 无明文;配置与密钥分开管;
  9. 回滚决策要在平静时写成规则(阈值接进流水线),不要留到事故时靠人。

9. 原文地图

主题原书章原文位置
12-factor 十二条Introduction(第 9 章)text/27-fm-introduction.txt:90(搜「Codebase」) · text/27-fm-introduction.txt:293(搜「baseline contract」)
环境变量配置例子Introduction(第 9 章)text/27-fm-introduction.txt:215(搜「process.env.DB_USER」)
滚动与兼容性三前提Introduction(第 9 章)text/27-fm-introduction.txt:344(搜「maxUnavailable」) · text/27-fm-introduction.txt:411(搜「Backward compatibility」)
expand-migrate-contractIntroduction(第 9 章)text/27-fm-introduction.txt:424(搜「Introduce」) · text/27-fm-introduction.txt:439(搜「expand, migrate, contract」)
蓝绿与切换命令Introduction(第 9 章)text/27-fm-introduction.txt:462(搜「two identical environments」) · text/27-fm-introduction.txt:613(搜「kubectl patch」)
会话陷阱Introduction(第 9 章)text/27-fm-introduction.txt:659(搜「forced logouts」)
金丝雀与 Istio/NGINX 例子Introduction(第 9 章)text/27-fm-introduction.txt:668(搜「5 to 10%」) · text/27-fm-introduction.txt:726(搜「VirtualService」) · text/27-fm-introduction.txt:786(搜「canary-weight」)
影子部署与镜像Introduction(第 9 章)text/27-fm-introduction.txt:872(搜「dark launch」) · text/27-fm-introduction.txt:941(搜「mirrorPercentage」)
A/B 与特性开关Introduction(第 9 章)text/27-fm-introduction.txt:973(搜「split testing」) · text/27-fm-introduction.txt:1087(搜「LaunchDarkly」)
服务网格架构Introduction(第 9 章)text/27-fm-introduction.txt:1115(搜「sidecar proxy」) · text/27-fm-introduction.txt:1167(搜「80% of requests」)
密钥管理七控制Introduction(第 9 章)text/27-fm-introduction.txt:1321(搜「Centralize secret storage」) · text/27-fm-introduction.txt:1333(搜「Automate rotation」)
七段流水线与金丝雀实战Introduction(第 9 章)text/27-fm-introduction.txt:1483(搜「Code commit」) · text/27-fm-introduction.txt:1575(搜「new fraud check」)
Argo CD Application 例子Introduction(第 9 章)text/27-fm-introduction.txt:1605(搜「payments-service」) · text/27-fm-introduction.txt:1647(搜「selfHeal」)

Footnotes

  1. 出处:「Introduction」(第 9 章)第 84 段(text/27-fm-introduction.txt:84,搜「Heroku」)与第 293 段(text/27-fm-introduction.txt:293,搜「baseline contract」)。 ↩

  2. 出处:「Introduction」(第 9 章)第 203–287 段(text/27-fm-introduction.txt:215,搜「process.env.DB_USER」;text/27-fm-introduction.txt:251,搜「secretKeyRef」)。 ↩

  3. 出处:「Introduction」(第 9 章)第 311–335 段(text/27-fm-introduction.txt:335,搜「HR leave application」)。 ↩

  4. 出处:「Introduction」(第 9 章)第 338–376 段(text/27-fm-introduction.txt:344,搜「maxUnavailable」)。 ↩

  5. 出处:「Introduction」(第 9 章)第 411–439 段(text/27-fm-introduction.txt:411,搜「Backward compatibility」;text/27-fm-introduction.txt:439,搜「expand, migrate, contract」)。 ↩

  6. 出处:「Introduction」(第 9 章)第 459–617 段(text/27-fm-introduction.txt:462,搜「two identical environments」;text/27-fm-introduction.txt:613,搜「kubectl patch」)。 ↩

  7. 出处:「Introduction」(第 9 章)第 629–652 段(text/27-fm-introduction.txt:629,搜「Environment parity」;text/27-fm-introduction.txt:649,搜「sticky sessions」)。 ↩

  8. 出处:「Introduction」(第 9 章)第 665–838 段(text/27-fm-introduction.txt:668,搜「5 to 10%」;text/27-fm-introduction.txt:726,搜「VirtualService」;text/27-fm-introduction.txt:786,搜「canary-weight」)。 ↩

  9. 出处:「Introduction」(第 9 章)第 840–866 段(text/27-fm-introduction.txt:850,搜「Progressive exposure」;text/27-fm-introduction.txt:856,搜「SLO-based gates」)。 ↩

  10. 出处:「Introduction」(第 9 章)第 869–885 段(text/27-fm-introduction.txt:872,搜「dark launch」;text/27-fm-introduction.txt:884,搜「double-writes」)。 ↩

  11. 出处:「Introduction」(第 9 章)第 947–966 段(text/27-fm-introduction.txt:957,搜「separate databases」;text/27-fm-introduction.txt:941,搜「mirrorPercentage」)。 ↩

  12. 出处:「Introduction」(第 9 章)第 970–1099 段(text/27-fm-introduction.txt:973,搜「split testing」;text/27-fm-introduction.txt:1093,搜「Statistical significance」)。 ↩

  13. 出处:「Introduction」(第 9 章)第 1112–1135 段(text/27-fm-introduction.txt:1115,搜「sidecar proxy」;text/27-fm-introduction.txt:1131,搜「mTLS」)。 ↩

  14. 出处:「Introduction」(第 9 章)第 1138–1158 段(text/27-fm-introduction.txt:1148,搜「mutual TLS」;text/27-fm-introduction.txt:1154,搜「fault injection」)。 ↩

  15. 出处:「Introduction」(第 9 章)第 1167–1218 段(text/27-fm-introduction.txt:1206,搜「weight: 80」)。 ↩

  16. 出处:「Introduction」(第 9 章)第 1281–1304 段(text/27-fm-introduction.txt:1301,搜「Reloader」)。 ↩

  17. 出处:「Introduction」(第 9 章)第 1253–1276 段(text/27-fm-introduction.txt:1269,搜「GDPR」;text/27-fm-introduction.txt:1275,搜「dynamically」)。 ↩

  18. 出处:「Introduction」(第 9 章)第 1321–1339 段(text/27-fm-introduction.txt:1321,搜「Centralize secret storage」;text/27-fm-introduction.txt:1336,搜「Avoid plaintext in Git」)。 ↩

  19. 出处:「Introduction」(第 9 章)第 1442–1462 段(text/27-fm-introduction.txt:1458,搜「stored in version control」)。 ↩

  20. 出处:「Introduction」(第 9 章)第 1480–1501 段(text/27-fm-introduction.txt:1483,搜「Code commit」;text/27-fm-introduction.txt:1495,搜「Canary release」)。 ↩

  21. 出处:「Introduction」(第 9 章)第 1513–1546 段(text/27-fm-introduction.txt:1519,搜「withCredentials」)。 ↩

  22. 出处:「Introduction」(第 9 章)第 1549–1584 段(text/27-fm-introduction.txt:1552,搜「Flagger」;text/27-fm-introduction.txt:1581,搜「shifts traffic back」)。 ↩

  23. 出处:「Introduction」(第 9 章)第 1587–1650 段(text/27-fm-introduction.txt:1647,搜「selfHeal」)。 ↩

On this page