文章

灰度发布与渐进式交付

灰度发布与渐进式交付

概述

kubectl apply -f deployment.yaml 是赌博——你相信新版本没问题,但生产环境总有惊喜。渐进式交付(Progressive Delivery)的核心思想是:新版本不会一次性推到所有用户,而是逐步扩大流量比例,每一步都由指标验证决定是继续还是回滚。K8s 原生 Deployment 的 RollingUpdate 只关心 Pod 是否 Ready(进程存活),不关心业务指标是否正常(错误率、延迟)。

一句话:RollingUpdate 保证部署过程不中断服务,Argo Rollouts + Flagger 保证部署后不引入故障。前者是部署工具,后者是发布决策引擎。

Argo Rollouts —— 替代 Deployment 的渐进式交付控制器

核心概念

Argo Rollouts 是 Argo 家族的发布控制器,用 Rollout CRD 替代 Deployment 管理 Pod 生命周期——但额外支持 Blue-Green 和 Canary 两种渐进式策略。

Deployment:
  RollingUpdate → 逐步替换 Pod,一次一批,等 Ready → 完成

Rollout (Canary):
  Step 1: 创建 1 个新版本 Pod(canary)
  Step 2: 等 5 分钟,观察 Prometheus 指标
  Step 3: 如果指标正常 → 扩大到 25% 流量
  Step 4: 再等 10 分钟
  Step 5: 如果仍正常 → 100% 流量 → 删除旧 Pod
  任意一步指标异常 → 自动回滚

Rollout CRD v1.7 的完整结构:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: health-ack
  namespace: health
spec:
  replicas: 5
  selector:
    matchLabels:
      app: health-ack

  # 保持和 Deployment 一样的 Pod 模板(原地迁移只需要改 apiVersion + kind)
  template:
    metadata:
      labels:
        app: health-ack
    spec:
      containers:
        - name: app
          image: registry.example.com/health-ack:v2.3.1
          ports:
            - containerPort: 8080

  # Blue-Green 策略
  strategy:
    blueGreen:
      activeService: health-ack-active     # 生产流量指向的 Service
      previewService: health-ack-preview   # 新版本预览 Service
      autoPromotionEnabled: false          # true=自动切换、false=手动确认
      prePromotionAnalysis:                # 切换前做分析
        templates:
          - templateName: smoke-test       # 冒烟测试 AnalysisTemplate
      postPromotionAnalysis:               # 切换后验证
        templates:
          - templateName: canary-metrics

  # Canary 策略(分步灰度)
  # strategy:
  #   canary:
  #     canaryService: health-ack-canary     # canary 流量的 Service
  #     stableService: health-ack-stable     # stable 流量的 Service
  #     steps:
  #       - setWeight: 10                    # Step 1: 10% 流量到 canary
  #       - pause: { duration: 5m }          # 等 5 分钟
  #       - analysis:                        # 分析指标
  #           templates:
  #             - templateName: canary-metrics
  #       - setWeight: 25                    # Step 2: 25%
  #       - pause: { duration: 10m }
  #       - setWeight: 50                    # Step 3: 50%
  #       - pause: { duration: 15m }
  #       - setWeight: 100                   # Step 4: 100%(promote)

Blue-Green vs Canary 决策

维度Blue-GreenCanary
切换方式一次性切换 100% 流量逐步增加流量比例
回滚速度秒级(切换 Service selector)逐步减量
双倍资源需求✅ 需要 2× Pod✅ 只需要少量 canary Pod
数据库 Schema 变更❌ 不能同时有新旧两版写同一 DB✅ 可以(小流量先写,观察)
适用关键服务、需要秒级回滚通用,推荐
你的场景api-health 的健康检查 endpoint(无状态、无 DB 写)health-ack 的 checkout(有 DB 写)

AnalysisTemplate —— 让发布决策由数据驱动

AnalysisTemplate 定义了在灰度过程中需要验证的指标,以及如何判断成功/失败:

apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: canary-metrics
  namespace: health
spec:
  metrics:
    # 指标 1:HTTP 错误率(5xx/总请求 < 1%)
    - name: error-rate
      interval: 30s                     # 每 30 秒取样一次
      count: 10                         # 取 10 个样本
      failureLimit: 3                   # 允许最多 3 次失败
      provider:
        prometheus:
          address: http://prometheus.monitoring:9090
          query: |
            sum(rate(http_requests_total{
              namespace="health",
              app="health-ack",
              status=~"5.."
            }[1m]))
            /
            sum(rate(http_requests_total{
              namespace="health",
              app="health-ack"
            }[1m])) > 0.01

    # 指标 2:P99 延迟(< 500ms)
    - name: latency-p99
      interval: 30s
      count: 10
      failureLimit: 2
      provider:
        prometheus:
          address: http://prometheus.monitoring:9090
          query: |
            histogram_quantile(0.99,
              sum(rate(http_request_duration_seconds_bucket{
                namespace="health",
                app="health-ack"
              }[1m])) by (le)
            ) > 0.5

    # 指标 3:新版本 Pod 重启次数(> 0 即失败)
    - name: pod-restarts
      interval: 60s
      count: 5
      failureLimit: 1
      successCondition: result == 0    # 自定义成功条件
      provider:
        prometheus:
          address: http://prometheus.monitoring:9090
          query: |
            sum(increase(kube_pod_container_status_restarts_total{
              namespace="health",
              pod=~"health-ack-.*",
              container="app"
            }[1m]))

    # 指标 4:Webhook 回调(外部分析系统)
    - name: external-validation
      provider:
        web:
          url: "https://qa-tool.internal/validate?app=health-ack&version=v2.3.1"
          timeoutSeconds: 30
          jsonPath: "{$.passed}"

B/G + Analysis 完整工作流

1. 运维触发 Rollout 更新
   → kubectl argo rollouts set image health-ack *=registry.example.com/health-ack:v2.3.1
   → 或 ArgoCD 自动检测 Git 变更

2. Rollout Controller:
   → 创建新 ReplicaSet(可以指定新 replicas=1 作预览)
   → 新 Pod 加入 previewService(不影响生产流量)
   → 触发 prePromotionAnalysis → 跑 AnalysisTemplate

3. AnalysisTemplate (冒烟测试 + 指标验证):
   → 如果所有指标通过 → autoPromotion(如果开启)或等待手动 promote
   → 如果任一指标失败 → AnalysisRun 标记为 Failed → 不 promote

4. promote:
   → activeService selector 从旧 ReplicaSet 切到新 ReplicaSet
   → 100% 流量瞬间切换到新版本
   → 触发 postPromotionAnalysis → 继续监控 5 分钟
   → 如果通过 → 删除旧 ReplicaSet
   → 如果失败 → 秒级切回(Service selector 切回旧 RS)

Flagger —— Service Mesh 原生的渐进式交付

Flagger 是 Weaveworks 开发的项目,专为 Istio / Linkerd / Contour / NGINX 设计的金丝雀发布工具。与 Argo Rollouts 的互补关系:Rollouts 管 Deployment 的生命周期和副本管理,Flagger 管 Service Mesh 的流量分割和指标验证。

Flagger + Istio 完整示例

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: health-ack
  namespace: health
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment                # 注意:Flagger 管的是 Deployment,不是 Rollout
    name: health-ack
  service:
    port: 8080
    gateways:
      - istio-system/ingress-gateway
    hosts:
      - "api.health.example.com"
  analysis:
    interval: 30s                   # 每 30s 检查一次指标
    threshold: 5                    # 连续 5 次阈值检查通过 → 成功
    maxWeight: 50                   # 金丝雀最大流量比例(50%)
    stepWeight: 10                  # 每次增加 10%
    stepWeights: [5, 10, 20, 50]   # 自定义每步目标比例

    # 第一步就验证的指标
    metrics:
      - name: request-success-rate
        thresholdRange:
          min: 99                   # 成功率 ≥ 99%
        interval: 1m
      - name: request-duration
        thresholdRange:
          max: 500                  # P99 < 500ms
        interval: 1m

    # Webhook:金丝雀开始/结束/回滚时回调
    webhooks:
      - name: load-test
        type: pre-rollout
        url: http://flagger-loadtester.health/
        timeout: 5m
        metadata:
          type: cmd
          cmd: "hey -z 1m -c 10 http://health-ack-canary.health:8080/api/health"
      - name: notification
        type: post-rollout
        url: http://notification-service.health/notify
      - name: rollback-notification
        type: rollback
        url: http://notification-service.health/notify

Flagger 自动创建和管理的 Istio 资源(你无需手动配):

# Flagger 自动创建:
kubectl get virtualservices -n health
# health-ack         ← 自动创建(canary ↔ stable 权重分割)
kubectl get destinationrules -n health
# health-ack         ← 自动创建(canary/sttps subsets 定义)

Argo Rollouts vs Flagger

维度Argo RolloutsFlagger
管理什么Pod 副本(替代 Deployment)Service Mesh 流量分割
流量分割方式多 Service 切换Istio/Linkerd VirtualService 权重
分析AnalysisTemplate(PromQL/Webhook/Datadog)metrics(PromQL)+ webhooks
与 ArgoCD 集成✅ 原生(同家族)✅ 通过注解触发
复杂度中等(需替换 Deployment 为 Rollout)较高(需 Service Mesh)
适用场景通用、无需 Service Mesh已有 Istio/Linkerd 的集群
你的场景没有 Service Mesh 时的首选迁移到 Istio 后的升级选择

生产实践:xirang-ocr 灰度发布实例

从你之前的 GPU 灰度发布问题中提练出一个标准模式:

发布前检查(Checklist)

# 1. 对比旧版本和新版本的 resource spec(防止原地 CPU/Memory resize 异常)
diff <(kubectl get deployment xirang-ocr -o json | jq '.spec.template.spec.containers[0].resources') \
     <(cat new-deployment.yaml | yq '.spec.template.spec.containers[0].resources')

# 2. 确认 GPU 节点可用性
kubectl get nodes -l accelerator=nvidia-tesla-t4 -o json | jq '.items[] | {name: .metadata.name, allocatable: .status.allocatable."nvidia.com/gpu"}'

# 3. 确认 Istio sidecar 注入(确保 canary Pod 在 Service Mesh 内)
kubectl get namespace health -o json | jq '.metadata.labels["istio-injection"]'

Rollout 配置(GPU 场景)

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: xirang-ocr
spec:
  replicas: 2
  selector:
    matchLabels:
      app: xirang-ocr
  template:
    metadata:
      labels:
        app: xirang-ocr
    spec:
      nodeSelector:
        accelerator: nvidia-tesla-t4
      tolerations:
        - key: "nvidia.com/gpu"
          operator: "Exists"
          effect: "NoSchedule"
      containers:
        - name: ocr
          image: registry.example.com/xirang-ocr:v2.1.0
          resources:
            limits:
              nvidia.com/gpu: 1
            requests:
              nvidia.com/gpu: 1
  strategy:
    canary:
      steps:
        - setWeight: 10
        - pause: { duration: 5m }
        - analysis:
            templates:
              - templateName: gpu-metrics       # GPU 专项验证
        - setWeight: 50
        - pause: { duration: 10m }
        - setWeight: 100
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: gpu-metrics
spec:
  metrics:
    - name: gpu-utilization
      interval: 30s
      count: 5
      failureLimit: 2
      provider:
        prometheus:
          address: http://prometheus.monitoring:9090
          query: |
            avg(DCGM_FI_DEV_GPU_UTIL{
              pod=~"xirang-ocr-.*"
            }) < 10                          # GPU 利用率 < 10% = 模型没加载成功
    - name: gpu-memory
      failureLimit: 2
      provider:
        prometheus:
          query: |
            avg(DCGM_FI_DEV_FB_USED{
              pod=~"xirang-ocr-.*"
            }) < 1000000                    # 显存使用 < 1MB = 模型未加载

回滚流程

# 1. 立即中止正在进行的 Rollout
kubectl argo rollouts abort xirang-ocr -n ocr

# 2. 回滚到上一个稳定版本
kubectl argo rollouts undo xirang-ocr -n ocr

# 3. 验证回滚后状态
kubectl argo rollouts status xirang-ocr -n ocr --watch
# 等 "Rollout 'xirang-ocr' is Healthy."

# 4. 排查失败原因
kubectl describe analysisrun -n ocr -l rollout=pright-ocr
# 查看哪个指标触发了失败

关联知识

参考资源

学习时间

阶段时间备注
渐进式交付2026-07-06Blue-Green/Canary、AnalysisTemplate、Flagger、GPU 发布案例、回滚流程

状态: 🌱 学习中 下次复习日期: 2026-07-13