17-CD部署策略
持续部署(Continuous Deployment, CD)是把制品从仓库推到运行环境的核心环节。与 CI 解决"怎么构建"不同,CD 解决的是"怎么发布"——如何在降低风险的前提下,把新版本安全地送到用户面前。本章覆盖 K8s 环境下的主流部署策略、回滚机制、特性开关,以及渐进式交付的工程化方案。
部署 vs 发布#
一个常见混淆:部署(Deploy)不等于发布(Release)。
- 部署:把新版本代码/镜像放到目标环境,Pod 起来、健康检查通过。
- 发布:把流量切到新版本,让真实用户开始使用。
解耦这两个动作是现代 CD 的核心思路——先部署不接流量,验证无误后再逐步放量,出问题就切回。蓝绿、金丝雀、特性开关都是这种思路的不同实现。
五大部署策略#
| 策略 | 隔离性 | 回滚速度 | 资源成本 | 流量控制粒度 | 适用场景 |
|---|---|---|---|---|---|
| 滚动 Rolling | 弱 | 慢(重新滚回) | 无额外 | 粗(按 Pod 比例) | K8s 默认、低风险变更 |
| 蓝绿 Blue-Green | 强(两套完整环境) | 秒级(切回) | 高(双倍) | 全有或全无 | 要求干净切换、可瞬间回滚 |
| 金丝雀 Canary | 中(共享基础设施) | 秒级(切流量) | 低(只多新版副本) | 细(按比例/身份) | 渐进式交付主力 |
| 影子 Shadow | 强(不返回用户) | N/A | 中 | 镜像全量但只读 | 高风险变更先验证、性能压测 |
| 特性开关 Feature Flag | 代码层 | 秒级(关开关) | 无额外 | 最细(按用户/属性) | 与部署解耦、A/B 测试、紧急熔断 |
选型心法:日常迭代用金丝雀(细粒度 + 低成本 + 可自动化);数据库 schema 等不可回滚变更前用影子验证;要"发了不一定开、能瞬间熔断"用特性开关;要求绝对干净切换/合规用蓝绿。金丝雀 + 特性开关组合是现代主流。
滚动更新(K8s 默认)#
K8s Deployment 的 RollingUpdate 通过 maxSurge 和 maxUnavailable 控制滚动速度,在新旧 Pod 之间平滑切换。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 6
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2 # 滚动时最多多出 2 个 Pod
maxUnavailable: 1 # 滚动时最多不可用 1 个 Pod
template:
spec:
containers:
- name: app
image: my-app:v2
readinessProbe: # readiness 通过才开始接流量
httpGet:
path: /healthz
port: 8080局限:风险集中在发布那一刻——滚动 1 分钟完成,如果有 bug,1 分钟内所有用户被打中;没有指标门禁(readiness 只看 Pod 能否接流量,不知道业务逻辑是否正确);回滚靠人(kubectl rollout undo)。
蓝绿部署#
两套完整环境同时存在(蓝=旧、绿=新),流量一次性切换。切换前可对绿环境做充分冒烟测试,通过后把流量从蓝切到绿。
# Service 通过 selector 切换指向 blue 还是 green
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
selector:
app: my-app
slot: green # 切流:改 blue <-> green
ports:
- port: 80
targetPort: 8080适用:schema 变更、协议变更、大版本升级等"风险无法通过小流量观察"的场景。代价是双倍资源。
金丝雀部署#
两个版本同时在线,按权重切流:5% → 25% → 50% → 100%。核心假设:新版本如果有问题,小流量下就能通过错误率、延迟等指标观察到。
Istio 实现方式——DestinationRule 分 subset + VirtualService 切权重:
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: my-svc
spec:
host: my-svc.ns.svc.cluster.local
subsets:
- name: baseline
labels: { version: baseline }
- name: canary
labels: { version: canary }
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: my-svc
spec:
hosts: [my-svc.ns.svc.cluster.local]
http:
- route:
- { destination: { host: my-svc, subset: baseline }, weight: 90 }
- { destination: { host: my-svc, subset: canary }, weight: 10 }关键:先给 baseline Pod 打 version:baseline label,再上 DR,否则路由到不存在 subset → 503。
回滚机制#
不同策略的回滚方式:
| 策略 | 回滚方式 | 速度 |
|---|---|---|
| 滚动 | kubectl rollout undo deployment/<name> | 慢(重新部署旧版) |
| 蓝绿 | Service selector 切回 | 秒级(切流量) |
| 金丝雀 | canary 权重设为 0 | 秒级(切流量) |
| 特性开关 | 关开关 | 秒级(代码层) |
关键区别:回滚是"切流量"还是"重新部署"。切流量秒级完成、零风险;重新部署要等 Pod 重建,分钟级。这就是蓝绿/金丝雀优于滚动的核心原因。
Helm 的 --atomic 参数在升级失败时自动回滚到上一版本,不让集群处于半升级状态:
helm upgrade --install my-service ./my-service \
-f values-prod.yaml \
--set image.tag=v1.2.4 \
-n prod \
--atomic \
--timeout 10m \
--waitFeature Flag(特性开关)#
特性开关把"部署"和"发布"彻底解耦——部署是把带新能力的代码发上去(开关默认关),放量是改开关(不重新部署),熔断是关开关(秒级)。
if feature_flags.is_enabled("new_checkout_v2", user_id=uid, default=False):
return new_checkout() # 灰度命中的用户走新逻辑
else:
return old_checkout() # 其余走旧逻辑开关后端:Unleash(开源)、LaunchDarkly(商业)。支持按 user / 百分比 / 属性定向 + 实时下发。
特性开关的杀手锏是"与部署解耦"——发了不一定开,出问题秒级关,不需要重新部署。这让它成为紧急熔断和 A/B 测试的首选。
渐进式交付(Progressive Delivery)#
渐进式交付是灰度发布的现代形态:不只是"切一部分流量",而是"切一部分流量 + 用指标自动判断新版好不好 + 好就逐步放量、坏就自动回滚",把人从"盯着发布"里解放出来。
金丝雀分析(Canary Analysis)#
金丝雀分析对比的是 Google SRE 的 4 个黄金信号:
| 信号 | canary 判定 |
|---|---|
| Latency 延迟 | canary P99 ≤ baseline P99 × (1+容忍) |
| Traffic 流量 | 确认 canary 真收到了有意义的流量 |
| Errors 错误率 | canary 错误率 ≤ baseline + 阈值 |
| Saturation 饱和度 | canary 不比 baseline 更饱和 |
防误判:低流量时指标抖动大易误判,需要门控——连续 N 次分析通过才晋级;冷启动期/低流量豁免;canary vs baseline 同时段对比(不是 vs 历史)。
Argo Rollouts#
Argo Rollouts 引入 Rollout CRD 替代 Deployment,内嵌发布策略。它按 Rollout.steps 调整流量权重,到 analysis 步骤起 AnalysisRun,查询 Prometheus 对比 canary vs baseline 指标,达标晋级/劣化自动回滚。
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-svc
spec:
replicas: 5
strategy:
canary:
canaryService: my-svc-canary
stableService: my-svc-stable
trafficRouting:
istio:
virtualService: { name: my-svc }
steps:
- setWeight: 5
- pause: { duration: 5m }
- analysis:
templates: [{ templateName: success-rate-latency }]
- setWeight: 25
- pause: { duration: 5m }
- analysis:
templates: [{ templateName: success-rate-latency }]
- setWeight: 50
- analysis:
templates: [{ templateName: success-rate-latency }]
- setWeight: 100
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate-latency
spec:
metrics:
- name: success-rate
interval: 1m
successCondition: result[0] >= 0.99
failureLimit: 3
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(istio_requests_total{destination_workload="my-svc-canary",response_code!~"5.."}[2m]))
/ sum(rate(istio_requests_total{destination_workload="my-svc-canary"}[2m]))
- name: p99-latency
successCondition: result[0] <= 500
provider:
prometheus:
query: |
histogram_quantile(0.99, sum(rate(
istio_request_duration_milliseconds_bucket{destination_workload="my-svc-canary"}[2m]
)) by (le))任一 analysis Failed → 自动 setWeight 0 回滚(秒级切流量,不是重新部署)。
Flagger#
Flagger 不改 Deployment,外挂一个 Canary CR 指向 Deployment——关掉 Flagger 服务还是服务,侵入性更低。
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: my-svc
spec:
targetRef: { kind: Deployment, name: my-svc }
service: { port: 80 }
analysis:
interval: 1m
threshold: 5 # 连续 5 次失败 → 回滚
maxWeight: 50
stepWeight: 10
metrics:
- name: request-success-rate
thresholdRange: { min: 99 }
- name: request-duration
thresholdRange: { max: 500 }
webhooks:
- name: load-test
url: http://flagger-loadtester.flagger-system/Argo Rollouts vs Flagger 选型:
- 已用 Argo CD / 要复杂发布编排(手动 gate + 多阶段)→ Rollouts,steps DSL 表达力更强。
- 不想改 Deployment / 团队规模小、追求标准化 / mesh 种类多 → Flagger,配置量少、provider 覆盖广。
成熟度演进#
L1 手动灰度:手动放量 + 人盯 Grafana + 手动回滚 ← 多数团队在这
L2 半自动: 指标看板自动化 + 金丝雀分析(人看结果手动晋级)
L3 全自动: Argo Rollouts/Flagger + canary analysis 自动晋级 + 自动回滚
+ 特性开关解耦 + 高风险先影子验证小结#
CD 部署策略的核心是把"发布风险"从全量收敛到可控的一小撮 + 可快速止损。滚动是 K8s 默认但风险集中;蓝绿/金丝雀通过切流量实现秒级回滚;特性开关把部署和发布解耦,是紧急熔断的利器。渐进式交付(Argo Rollouts/Flagger)把灰度从"人盯"升级到"指标驱动自治",是现代发布工程的标配。选型上,金丝雀 + 特性开关的组合覆盖了绝大多数场景。