路线图

17-CD部署策略

星辉 2026-07-02 阅读 4 min 686 字 路线图
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 之间平滑切换。

yaml
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)。

蓝绿部署#

两套完整环境同时存在(蓝=旧、绿=新),流量一次性切换。切换前可对绿环境做充分冒烟测试,通过后把流量从蓝切到绿。

yaml
# 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 切权重:

yaml
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 参数在升级失败时自动回滚到上一版本,不让集群处于半升级状态:

bash
helm upgrade --install my-service ./my-service \
  -f values-prod.yaml \
  --set image.tag=v1.2.4 \
  -n prod \
  --atomic \
  --timeout 10m \
  --wait

Feature Flag(特性开关)#

特性开关把"部署"和"发布"彻底解耦——部署是把带新能力的代码发上去(开关默认关),放量是改开关(不重新部署),熔断是关开关(秒级)。

python
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 指标,达标晋级/劣化自动回滚。

yaml
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 服务还是服务,侵入性更低。

yaml
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 覆盖广。

成熟度演进#

text
L1 手动灰度:手动放量 + 人盯 Grafana + 手动回滚         ← 多数团队在这
L2 半自动:  指标看板自动化 + 金丝雀分析(人看结果手动晋级)
L3 全自动:  Argo Rollouts/Flagger + canary analysis 自动晋级 + 自动回滚
            + 特性开关解耦 + 高风险先影子验证

小结#

CD 部署策略的核心是把"发布风险"从全量收敛到可控的一小撮 + 可快速止损。滚动是 K8s 默认但风险集中;蓝绿/金丝雀通过切流量实现秒级回滚;特性开关把部署和发布解耦,是紧急熔断的利器。渐进式交付(Argo Rollouts/Flagger)把灰度从"人盯"升级到"指标驱动自治",是现代发布工程的标配。选型上,金丝雀 + 特性开关的组合覆盖了绝大多数场景。