跳过正文
Flagger 渐进式交付实战:金丝雀、蓝绿、A/B 与 Istio/NGINX/Gateway API 集成

Flagger 渐进式交付实战:金丝雀、蓝绿、A/B 与 Istio/NGINX/Gateway API 集成

·4105 字·20 分钟·
目录

1. 发布风险与渐进式交付
#

1.1 滚动更新到底解决了什么,又没解决什么
#

Kubernetes 原生的 Deployment 只有一种发布策略是真正意义上的"安全"的:RollingUpdate。它通过 maxSurgemaxUnavailable 两个旋钮控制滚动速度,在新旧 Pod 之间平滑切换,保证服务不中断。这套机制从 2015 年 Kubernetes 1.0 之后几乎没动过,原因是它已经足够解决"部署过程中不掉线"这一个问题。

但滚动更新没解决的问题更多:

  • 风险集中在发布那一刻:滚动 1 分钟完成,如果新版本有 bug,1 分钟内所有用户都被打中。
  • 没有指标门禁:kubectl 不知道什么叫"错误率升高",它只看 readinessProbe 是否为 ok。而 readiness 只能告诉你 Pod 能否接流量,不能告诉你业务逻辑是否正确。
  • 回滚靠人:发现问题之后,运维敲 kubectl rollout undo,从发现到执行中间是分钟级的人工窗口。
  • 无法做 A/B 测试:没办法让 1% 流量去尝试一个实验性版本,其余 99% 走稳定版本。
  • 无法渐进切流:滚动 3/10 个 Pod,流量比例并不是精确的 30%,因为 kube-proxy 的负载均衡粒度是 endpoint 而不是权重。

这些问题,渐进式交付(Progressive Delivery)都可以解决。渐进式交付不是一个具体的工具,而是一类方法论:把"部署"和"放量"解耦,让新版本先上线但不接流量,然后按指标分阶段放量,每一阶段都要过指标门禁,过不了就自动回滚。

1.2 金丝雀、蓝绿、A/B 三种策略的本质区别
#

三种策略经常被混着提,但它们解决的问题不一样:

金丝雀(Canary):两个版本同时在线,按权重切流。从 10% → 20% → 50% → 100%。核心假设:新版本如果有问题,小流量下就能通过错误率、延迟等指标观察到。适用于大多数增量变更。

蓝绿(Blue/Green):两套完整环境同时存在,流量一次性切换。切换前可以对绿环境做充分的冒烟测试,通过之后把流量从蓝切到绿。核心假设:变更的风险无法通过小流量观察,必须在"影子环境"里做完整回归。适用于 schema 变更、协议变更、大版本升级。

A/B 测试(A/B Testing):基于请求特征(header、cookie、地理位置、用户 ID)切流,而不是基于权重。核心假设:需要让特定用户群体走特定版本,观察业务指标而不是系统指标。适用于产品实验、功能灰度、按租户开关。

维度金丝雀蓝绿A/B
切流依据权重全量切换请求特征
观察指标系统指标(错误率、延迟)手动冒烟 + 系统指标业务指标(转化率、留存)
回滚成本降权即可切回蓝环境降权/下线 canary 规则
资源成本略高(多一份 Pod)翻倍略高
典型场景增量变更高风险变更产品实验

Flagger 把这三种策略统一到一个 CRD(Canary)里面,只是 analysis 字段的配置不同。这是 Flagger 区别于 Argo Rollouts 的核心设计之一。

1.3 为什么需要"控制器化"地做这件事
#

原理搞清楚之后,很多人第一反应是"我写个脚本也能做"。比如自己写一个 shell 脚本,部署 canary Deployment、更新 VirtualService 的权重、调用 PromQL 查错误率、判断之后再推进。这种脚本化方案的问题在于:

  1. 状态不持久:脚本跑一半挂了怎么办,重启之后无法感知当前阶段。
  2. 没有一致性保证:多个服务同时发版,可能互相影响,脚本难以编排。
  3. 不是声明式:和 Kubernetes 的声明式风格格格不入,GitOps 工具(Argo CD、Flux)无法直接管理。
  4. 扩展困难:想加一个新的指标来源、新的 mesh 支持,都要改脚本。

控制器化(operator pattern)是 Kubernetes 生态解决这类问题的标准答案。Flagger 把整套逻辑装进一个 controller,通过 CRD 声明意图,controller 轮询当前状态并推进。这样:

  • 状态写在 etcd 里,controller 重启无损。
  • 用户声明"我要金丝雀,每 1 分钟增 10%",controller 负责推进。
  • 可以被 Argo CD / Flux 作为标准 Kubernetes 资源管理。
  • 新增 provider 只要实现一个 interface,不动主干。

这也是 Flagger 能长期活在 CNCF 毕业项目之下的原因。

2. Flagger 是什么
#

2.1 项目背景
#

Flagger 最初由 Weaveworks 团队开发,和 Flux 是同一家。2020 年随 Flux 一起捐给 CNCF,目前是 CNCF 毕业项目。它在设计上刻意做到 mesh/ingress 无关,这意味着不论你用 Istio、Linkerd、App Mesh、NGINX Ingress、Contour、Gloo、Skipper、Traefik,还是新兴的 Gateway API,都能用同一个 CRD 描述渐进式发布流程。

2.2 和 Flux 的关系
#

Flagger 是 Flux 生态的一部分,但不强依赖 Flux。你可以只装 Flagger 不装 Flux,在 Argo CD 的体系下使用也完全没问题。Flagger 负责"发布过程",Flux/Argo CD 负责"期望状态同步",二者正交。

2.3 和 Service Mesh 的关系
#

Flagger 不是 service mesh,它是 service mesh 的"指挥家"。它利用 mesh 提供的流量路由能力(VirtualService / HTTPRoute / Ingress annotation)执行切流,利用 mesh 提供的遥测(Prometheus 指标)做决策。没有 mesh 也能跑,Flagger 会退化到用 NGINX Ingress 或 Gateway API 的 backendRefs 切流。

2.4 核心能力清单
#

  • Canary:权重切流,可配置步长、阈值、最大权重。
  • Blue/Greeniterations 模式,一次性切流前多次指标检查。
  • A/B Testing:基于 header/cookie 的流量匹配。
  • Traffic Mirroring:影子流量,复制一份生产流量到 canary,不影响用户。
  • Metric Analysis:支持 Prometheus、Datadog、New Relic、CloudWatch、Dynatrace、Graphite。
  • Webhook:pre/during/post rollout 的钩子,用于集成负载测试、冒烟测试、外部审批。
  • 通知:Slack、Discord、Microsoft Teams、Rocket、Google Chat、通用 Webhook。
  • Alerting:MetricTemplate 一键生成 PrometheusRule。

3. 架构剖析
#

3.1 核心对象关系
#

用户创建一个 Canary 资源,指向一个现有的 Deployment(称为 target)。Flagger controller 监听这个 CR,然后自顶向下创建一堆派生资源:

Canary (CR, user creates)
  └── targetRef → Deployment (user creates, Flagger mutates)
  │
  ├── creates: <name>-primary Deployment
  ├── creates: <name>-primary Service (ClusterIP)
  ├── creates: <name>-canary Service  (ClusterIP)
  ├── creates: <name>  Service (虚拟入口,指向 primary)
  ├── creates: VirtualService (Istio) / HTTPRoute (Gateway API) / Ingress rules (NGINX)
  └── creates: MetricTemplate / PrometheusRule

注意几个关键点:

  1. targetRef 指向的 Deployment 最终不会接生产流量。Flagger 会把它的副本数降为 0,只把它当作"canary 的源",真正跑生产流量的是 <name>-primary
  2. 用户不要手动改 <name>-primary,它由 Flagger 管理。
  3. 服务访问入口是 <name> 这个 Service,不是原来的 <name>,Flagger 会把原 Service 的 selector 也调整到 primary。
  4. 每次发布,Flagger 检测到 targetRef 变化,把变化同步到 canary Deployment(即用户创建的那个),然后启动 analysis。analysis 推进过程中,流量逐步从 primary 迁到 canary,最后 analysis 通过,primary 被更新成 canary 的内容,canary 副本数再次降为 0,完成一次发布。

3.2 发布生命周期状态机
#

一个 Canary 对象的 status.phase 会在下列状态之间流转:

  • Initializing:Flagger 正在创建 primary/canary 资源。
  • Initialized:primary 已就绪,等待 targetRef 的变化。
  • Progressing:检测到变化,正在做 analysis(切流 + 指标检查)。
  • Promoting:analysis 通过,正在把 canary 的配置同步到 primary。
  • Finalising:primary 更新完成,等待老版本 Pod 销毁。
  • Succeeded:整个发布成功,canary 副本数归零,等待下一次变化。
  • Failed:analysis 未通过,流量切回 primary,canary 副本数归零。

这个状态机很重要,排障时第一步就是 kubectl get canary 看当前卡在哪。

3.3 与 Prometheus 的关系
#

Flagger 自带两条默认指标:request-success-raterequest-duration。它们用的 PromQL 会根据 mesh provider 不同而不同。比如 Istio 的 request-success-rate 是:

sum(
  rate(
    istio_requests_total{
      reporter="destination",
      destination_workload_namespace="{{ namespace }}",
      destination_workload=~"{{ target }}",
      response_code!~"5.*"
    }[{{ interval }}]
  )
) / sum(
  rate(
    istio_requests_total{
      reporter="destination",
      destination_workload_namespace="{{ namespace }}",
      destination_workload=~"{{ target }}"
    }[{{ interval }}]
  )
) * 100

NGINX 的版本是基于 nginx_ingress_controller_requests,Gateway API 的版本依赖 Prometheus 抓取 Gateway 实现的指标。三者结构一致,只是指标名换了。

Flagger 把这些 PromQL 抽象成 MetricTemplate CRD,用户可以通过它定义任意自定义指标。这是后面自定义指标章节要展开的。

4. 安装部署
#

4.1 前置条件
#

  • Kubernetes 1.23+
  • 一个 mesh 或 ingress provider(Istio / Linkerd / NGINX / Gateway API 实现 / …)
  • Prometheus 可访问的 endpoint(不必装在同一个集群,但网络要通)

4.2 Helm 安装 Flagger(Istio 模式)
#

helm repo add flagger https://flagger.app
helm repo update

kubectl create ns flagger-system || true

helm upgrade -i flagger flagger/flagger \
  --namespace flagger-system \
  --set meshProvider=istio \
  --set metricsServer=http://prometheus.monitoring:9090 \
  --set slack.url=https://hooks.slack.com/services/xxx \
  --set slack.channel=release \
  --set slack.user=flagger

几个参数的意思:

  • meshProvider:mesh/ingress 类型,可选 istio | linkerd | appmesh:v1beta2 | contour | gloo | nginx | skipper | traefik | osm | kuma | gatewayapi
  • metricsServer:Prometheus 的 URL。这里填一个例子 http://prometheus.monitoring:9090,请替换成你自己集群的地址。
  • slack.*:告警通知渠道。不用 Slack 可以用 msteams.url / discord.url / webhook.url

4.3 安装 Flagger Loadtester(可选)
#

Flagger 自带一个负载测试工具 flagger-loadtester,webhook 里调用它可以在 canary 阶段主动产生流量,让指标有数据可算。生产环境强烈建议装。

helm upgrade -i flagger-loadtester flagger/loadtester \
  --namespace flagger-system \
  --set cmd.timeout=1h \
  --set cmd.namespaceRegexp=''

4.4 验证安装
#

kubectl -n flagger-system get pods
kubectl -n flagger-system logs deploy/flagger -f

看到 log 里有 Connected to metrics server http://prometheus.monitoring:9090 就表示 Flagger 和 Prometheus 通了。如果看到 failed to query Prometheus,先去查网络可达性和 Prometheus URL 是否正确。

4.5 Gateway API 模式下的区别
#

Gateway API 模式要多装一步,指定 Gateway 的 class:

helm upgrade -i flagger flagger/flagger \
  --namespace flagger-system \
  --set meshProvider=gatewayapi \
  --set metricsServer=http://prometheus.monitoring:9090

然后 Canary CR 里要填 gatewayRefs,这个后面会展开。

5. Canary CR 完整字段拆解
#

Canary 这个 CRD 字段非常多,下面把常用的都过一遍,每个字段都配简短解释。

5.1 顶层结构
#

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: frontend-api
  namespace: apps
spec:
  # 1. 目标资源
  provider: istio
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: frontend-api
  autoscalerRef:
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    name: frontend-api
  progressDeadlineSeconds: 600

  # 2. 服务与路由
  service:
    port: 80
    targetPort: 8080
    portName: http
    portDiscovery: true
    gateways:
      - public-gateway.istio-system.svc.cluster.local
      - mesh
    hosts:
      - api.example.com
    trafficPolicy:
      tls:
        mode: DISABLE
    retries:
      attempts: 3
      perTryTimeout: 1s
      retryOn: gateway-error,connect-failure,refused-stream
    headers:
      request:
        add:
          x-envoy-upstream-rq-timeout-ms: "15000"

  # 3. 分析配置
  analysis:
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
      - name: request-success-rate
        thresholdRange:
          min: 99
        interval: 1m
      - name: request-duration
        thresholdRange:
          max: 500
        interval: 1m
    webhooks:
      - name: acceptance-test
        type: pre-rollout
        url: http://flagger-loadtester.flagger-system/
        timeout: 30s
        metadata:
          type: bash
          cmd: "curl -sS http://frontend-api-canary.apps/healthz"
      - name: load-test
        url: http://flagger-loadtester.flagger-system/
        timeout: 5s
        metadata:
          cmd: "hey -z 1m -q 10 -c 2 http://frontend-api-canary.apps/"

5.2 provider
#

指定 mesh/ingress 类型。如果 Flagger 全局只有一个 provider,可以省略;多 provider 共存时必填。常见取值:istio | linkerd | nginx | contour | gatewayapi | appmesh:v1beta2 | gloo | traefik | osm | kuma

5.3 targetRef
#

指向被管理的 Deployment(也可以是 DaemonSet)。注意 Flagger 会接管这个 Deployment 的副本数,你不应该手动 kubectl scale

5.4 autoscalerRef
#

可选。如果服务有 HPA,这里声明一下,Flagger 会在 primary 上复制一份 HPA,保证 primary 有自己的弹性。不声明会导致 primary 无 HPA,canary 阶段一旦突发流量,primary 扛不住。

5.5 progressDeadlineSeconds
#

一次发布的总超时。超过这个时间 analysis 还没完,整体判定失败回滚。默认 600 秒。按你的 analysis 长度估算后设置,建议 = interval * (maxWeight / stepWeight) * 1.5

5.6 service.port / targetPort / portName
#

和 Service 的字段一致。portName 在 Istio 场景下必须以 http-grpc- 开头(Istio 约定),否则不会走 mesh。

5.7 service.portDiscovery
#

如果设为 true,Flagger 会自动发现 Deployment 其他端口并加到 Service 上。用于一个 Pod 暴露多个端口的情况。

5.8 service.gateways / hosts
#

Istio 专属。gateways 填要关联的 Istio Gateway 名,hosts 填 host 列表。Flagger 会把它们写到自动生成的 VirtualService 里。

5.9 service.trafficPolicy / retries / headers
#

这些字段会透传到 Istio DestinationRule / VirtualService。需要 CORS、超时、重试等高级配置,在这里填即可。

5.10 analysis.interval
#

每次指标检查的间隔。建议 1m 起步,指标太稀疏的场景可以到 2m。低于 30 秒基本没意义,PromQL 窗口太小误差大。

5.11 analysis.threshold
#

指标连续失败多少次判定整体失败。默认 10。实战建议降到 3-5,避免发布拖得太久。

5.12 analysis.maxWeight / stepWeight
#

maxWeight 是 canary 的最大权重。到了这个权重且指标全部通过,就进入 promotion 阶段(primary 被同步成 canary 内容)。stepWeight 是每次推进的增量。经典配置:maxWeight=50, stepWeight=10,意味着 10% → 20% → 30% → 40% → 50%,每一步停留 interval 秒。

注意 maxWeight 不必到 100,50 就够了。因为到了 50% 如果没问题,继续推到 100 也不会发现新问题,不如直接 promote。

5.13 analysis.iterations(Blue/Green)
#

iterations 被设置时,canary 会以 0% 或 100% 两种状态跑,每次 interval 做一次指标检查,跑满 iterations 次就 promote。这是蓝绿模式。不能和 stepWeight 同时出现。

5.14 analysis.match(A/B)
#

match 被设置时,Flagger 会基于请求匹配规则把特定流量转到 canary,其余走 primary。match 的语法是 Istio VirtualService 的 match,支持 header、uri、scheme 等。也是和 stepWeight 互斥。

5.15 analysis.metrics[]
#

指标列表,每个元素指向一个内置指标或 MetricTemplate。每个指标有一个 thresholdRange(min 或 max)和一个 interval。任意一个指标在 threshold 次连续检查里失败,整体失败。

5.16 analysis.webhooks[]
#

钩子列表,每个钩子有个 type,决定在什么时候调用:

  • confirm-rollout:开始 analysis 前等待人工确认。HTTP 200 才继续。
  • pre-rollout:analysis 开始前调用一次,失败则不开始。
  • rollout:每次 interval 都会调一次,适合跑 smoke test。
  • confirm-promotion:promotion 前的人工审批。
  • post-rollout:promotion 之后,无论成功失败都调一次。
  • rollback:失败回滚时调用。
  • event:Canary 状态变化事件(通知用)。

5.17 analysis.alerts[]
#

指定告警渠道(通过 AlertProvider CRD 引用),可以为单个 Canary 覆盖全局默认渠道。

5.18 skipAnalysis
#

设为 true 时跳过所有分析,直接 promote。应急使用,不建议生产长期打开。

6. 金丝雀策略完整模板(Istio)
#

下面给一套可以直接 apply 的 YAML,服务名统一为 frontend-api,命名空间 apps

6.1 Deployment
#

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-api
  namespace: apps
  labels:
    app: frontend-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: frontend-api
  template:
    metadata:
      labels:
        app: frontend-api
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9090"
    spec:
      containers:
        - name: app
          image: registry.example.com/frontend-api:1.0.0
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
            - name: metrics
              containerPort: 9090
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 10
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi

注意 containerPort: 8080name: http,后面 Canary 里 targetPort: 8080 要对得上。

6.2 HPA
#

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: frontend-api
  namespace: apps
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: frontend-api
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

6.3 Canary
#

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: frontend-api
  namespace: apps
spec:
  provider: istio
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: frontend-api
  autoscalerRef:
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    name: frontend-api
  progressDeadlineSeconds: 900
  service:
    port: 80
    targetPort: 8080
    portName: http
    gateways:
      - public-gateway.istio-system.svc.cluster.local
      - mesh
    hosts:
      - api.example.com
    retries:
      attempts: 3
      perTryTimeout: 2s
      retryOn: gateway-error,connect-failure,refused-stream
  analysis:
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
      - name: request-success-rate
        thresholdRange:
          min: 99
        interval: 1m
      - name: request-duration
        thresholdRange:
          max: 500
        interval: 1m
    webhooks:
      - name: acceptance-test
        type: pre-rollout
        url: http://flagger-loadtester.flagger-system/
        timeout: 30s
        metadata:
          type: bash
          cmd: "curl -sS http://frontend-api-canary.apps/healthz"
      - name: load-test
        url: http://flagger-loadtester.flagger-system/
        timeout: 5s
        metadata:
          cmd: "hey -z 1m -q 20 -c 4 http://frontend-api-canary.apps/"

apply 之后 Flagger 会:

  1. 创建 frontend-api-primary Deployment,副本数 3。
  2. 创建 frontend-api-primary / frontend-api-canary Service。
  3. 把用户定义的 frontend-api Deployment 副本数降为 0。
  4. 创建 VirtualService + DestinationRule。
  5. 把 Canary status 置为 Initialized

之后你改 frontend-api Deployment 的镜像 tag 到 1.1.0,Flagger 会:

  1. frontend-api Deployment 副本数恢复为 3,启动新版本 Pod。
  2. 每 1 分钟推进 10% 权重,检查指标。
  3. 任何指标连续 5 次失败则整体失败,权重清零,canary 副本数归零。
  4. 权重到 50% 且指标全绿,进入 promotion:把 frontend-api-primary 的镜像同步到 1.1.0,primary 滚动更新。
  5. primary 完成后,权重切回 0,canary 副本归零,发布成功。

7. 蓝绿策略完整模板
#

蓝绿的本质是"不切权重,只切指标"。用 iterations 代替 stepWeight

7.1 Canary(Blue/Green 模式)
#

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: frontend-api
  namespace: apps
spec:
  provider: istio
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: frontend-api
  autoscalerRef:
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    name: frontend-api
  progressDeadlineSeconds: 1800
  service:
    port: 80
    targetPort: 8080
    portName: http
    gateways:
      - public-gateway.istio-system.svc.cluster.local
      - mesh
    hosts:
      - api.example.com
  analysis:
    interval: 1m
    threshold: 2
    iterations: 10
    metrics:
      - name: request-success-rate
        thresholdRange:
          min: 99
        interval: 1m
      - name: request-duration
        thresholdRange:
          max: 500
        interval: 1m
    webhooks:
      - name: smoke-test
        type: pre-rollout
        url: http://flagger-loadtester.flagger-system/
        timeout: 2m
        metadata:
          type: bash
          cmd: "curl -sS -f http://frontend-api-canary.apps/api/v1/ready"
      - name: confirm-promotion
        type: confirm-promotion
        url: http://ops-webhook.example.com/approve
        timeout: 1h
        metadata:
          message: "frontend-api ready for promotion, please approve"

注意:

  • iterations: 10 表示做 10 轮指标检查,每轮 1 分钟,总共 10 分钟。
  • threshold: 2 表示单个指标连续 2 次失败就整体失败。
  • confirm-promotion webhook 加了人工审批,在 10 轮检查通过后、真正 promote 之前停住等人点头。

蓝绿模式下 canary 不接生产流量(权重始终是 0),所以 load-test webhook 在这里仍然有意义:它往 frontend-api-canary Service 上打流量让指标有值,否则 10 轮检查都在处理空数据。

8. A/B 测试策略
#

A/B 模式基于请求匹配,不是权重。典型场景是:给带 x-experiment: canary header 的请求走新版本。

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: frontend-api
  namespace: apps
spec:
  provider: istio
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: frontend-api
  autoscalerRef:
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    name: frontend-api
  progressDeadlineSeconds: 1800
  service:
    port: 80
    targetPort: 8080
    portName: http
    gateways:
      - public-gateway.istio-system.svc.cluster.local
      - mesh
    hosts:
      - api.example.com
  analysis:
    interval: 1m
    threshold: 5
    iterations: 20
    match:
      - headers:
          x-experiment:
            exact: canary
      - headers:
          cookie:
            regex: ".*experiment=canary.*"
    metrics:
      - name: request-success-rate
        thresholdRange:
          min: 99
        interval: 1m
      - name: request-duration
        thresholdRange:
          max: 500
        interval: 1m
    webhooks:
      - name: generate-traffic
        type: rollout
        url: http://flagger-loadtester.flagger-system/
        timeout: 1m
        metadata:
          cmd: |
            hey -z 1m -q 5 -c 2 -H 'x-experiment: canary' http://frontend-api.apps/

match 里列出的所有规则是 OR 关系,满足任意一条就走 canary。iterations 和 A/B 一起用,表示做 20 轮检查,每轮 1 分钟。

A/B 模式下 canary 始终只接满足条件的请求,其余流量还是走 primary。promotion 时 primary 被同步成 canary 内容,match 规则解除,canary 副本归零。

9. Metrics Provider 接入
#

9.1 Prometheus(默认)
#

Helm 安装时 metricsServer 参数指向 Prometheus URL。所有 MetricTemplate 默认用这个连接。如果有多个 Prometheus,可以在 MetricTemplate 里按 provider 覆盖。

9.2 Datadog
#

apiVersion: v1
kind: Secret
metadata:
  name: datadog
  namespace: apps
data:
  datadog_api_key: <base64>
  datadog_application_key: <base64>
---
apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
  name: frontend-api-datadog-success
  namespace: apps
spec:
  provider:
    type: datadog
    address: https://api.datadoghq.com
    secretRef:
      name: datadog
  query: |
    100 - (
      sum:trace.http.request.errors{service:{{ target }}}.as_count() /
      sum:trace.http.request.hits{service:{{ target }}}.as_count()
    ) * 100

9.3 New Relic
#

apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
  name: frontend-api-newrelic
  namespace: apps
spec:
  provider:
    type: newrelic
    secretRef:
      name: newrelic
  query: |
    SELECT percentage(count(*), WHERE httpResponseCode NOT LIKE '5%')
    FROM Transaction WHERE appName = '{{ target }}'

9.4 CloudWatch
#

apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
  name: frontend-api-cloudwatch
  namespace: apps
spec:
  provider:
    type: cloudwatch
    region: us-west-2
  query: |
    [
      {
        "Id": "e1",
        "Expression": "m1 / m2 * 100",
        "Label": "success-rate"
      },
      {
        "Id": "m1",
        "MetricStat": {
          "Metric": {
            "Namespace": "AWS/ApplicationELB",
            "MetricName": "HTTPCode_Target_2XX_Count",
            "Dimensions": [
              {"Name": "LoadBalancer", "Value": "app/alb/xxx"}
            ]
          },
          "Period": 60,
          "Stat": "Sum"
        },
        "ReturnData": false
      },
      {
        "Id": "m2",
        "MetricStat": {
          "Metric": {
            "Namespace": "AWS/ApplicationELB",
            "MetricName": "RequestCount",
            "Dimensions": [
              {"Name": "LoadBalancer", "Value": "app/alb/xxx"}
            ]
          },
          "Period": 60,
          "Stat": "Sum"
        },
        "ReturnData": false
      }
    ]

9.5 Graphite
#

apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
  name: frontend-api-graphite
  namespace: apps
spec:
  provider:
    type: graphite
    address: http://graphite.monitoring:8080
  query: |
    target=alias(asPercent(
      sumSeries(stats.counters.{{ target }}.ok.count),
      sumSeries(stats.counters.{{ target }}.all.count)
    ), 'success-rate')

无论用哪个 provider,最后都在 Canary 的 analysis.metrics[].templateRef 里引用 MetricTemplate。

10. 自定义 MetricTemplate 深入
#

10.1 为什么需要自定义
#

内置的 request-success-rate 只看 HTTP 5xx,很多业务需要看:

  • 业务层错误码(HTTP 200 但 body 里有 code != 0
  • 下游依赖错误率(数据库连接失败、外部 API 失败)
  • P99 延迟,不是平均延迟
  • 消息队列消费延迟
  • 缓存命中率

这些都需要自己写 PromQL。Flagger 用 Go template 语法提供变量:

  • {{ namespace }}:Canary 所在 ns
  • {{ target }}:targetRef 名
  • {{ interval }}:analysis interval
  • {{ variables.xxx }}:用户自定义变量

10.2 业务错误码 MetricTemplate
#

apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
  name: business-success-rate
  namespace: apps
spec:
  provider:
    type: prometheus
    address: http://prometheus.monitoring:9090
  query: |
    100 - (
      sum(rate(
        http_requests_total{
          namespace="{{ namespace }}",
          workload="{{ target }}",
          business_code!="0"
        }[{{ interval }}]
      )) /
      sum(rate(
        http_requests_total{
          namespace="{{ namespace }}",
          workload="{{ target }}"
        }[{{ interval }}]
      ))
    ) * 100

在 Canary 里这样用:

analysis:
  metrics:
    - name: "business success rate"
      templateRef:
        name: business-success-rate
        namespace: apps
      thresholdRange:
        min: 99.5
      interval: 1m

10.3 P99 延迟 MetricTemplate
#

apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
  name: http-p99-latency
  namespace: apps
spec:
  provider:
    type: prometheus
    address: http://prometheus.monitoring:9090
  query: |
    histogram_quantile(0.99,
      sum(rate(
        istio_request_duration_milliseconds_bucket{
          reporter="destination",
          destination_workload_namespace="{{ namespace }}",
          destination_workload="{{ target }}"
        }[{{ interval }}]
      )) by (le)
    )

10.4 下游依赖错误率
#

apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
  name: db-error-rate
  namespace: apps
spec:
  provider:
    type: prometheus
    address: http://prometheus.monitoring:9090
  query: |
    sum(rate(
      db_client_errors_total{
        namespace="{{ namespace }}",
        workload="{{ target }}"
      }[{{ interval }}]
    )) /
    sum(rate(
      db_client_requests_total{
        namespace="{{ namespace }}",
        workload="{{ target }}"
      }[{{ interval }}]
    )) * 100

threshold 就写 max: 1(错误率不能超过 1%)。

10.5 变量化的 MetricTemplate
#

Flagger 0.30+ 支持 variables,让一个 template 被多个 Canary 复用:

apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
  name: http-error-rate-by-route
  namespace: apps
spec:
  provider:
    type: prometheus
    address: http://prometheus.monitoring:9090
  query: |
    sum(rate(
      http_requests_total{
        namespace="{{ namespace }}",
        workload="{{ target }}",
        route="{{ variables.route }}",
        status=~"5.."
      }[{{ interval }}]
    )) /
    sum(rate(
      http_requests_total{
        namespace="{{ namespace }}",
        workload="{{ target }}",
        route="{{ variables.route }}"
      }[{{ interval }}]
    )) * 100

Canary 里传参:

analysis:
  metrics:
    - name: "error rate (/api/v1/users)"
      templateRef:
        name: http-error-rate-by-route
      thresholdRange:
        max: 1
      interval: 1m
      templateVariables:
        route: /api/v1/users

11. Webhook 钩子实战
#

11.1 钩子类型速查
#

类型时机用途
confirm-rolloutanalysis 开始前人工审批 / 发布窗口判断
pre-rolloutanalysis 第一次 interval 前冒烟测试 / 数据库 migrate
rollout每次 interval负载测试 / smoke test
confirm-traffic-increase每次 stepWeight 前人工控制切流节奏
confirm-promotionanalysis 结束、promote 前人工确认 promote
post-rolloutpromote 完成后通知 / 清理
rollback失败回滚时通知 / 审计
event状态变化外部监控

11.2 调 flagger-loadtester 跑压测
#

flagger-loadtester 暴露一个 HTTP API,接收 JSON 请求,在容器内跑命令。内置 heywrkghzbombardier 等工具。

webhooks:
  - name: load-test-http
    type: rollout
    url: http://flagger-loadtester.flagger-system/
    timeout: 5s
    metadata:
      cmd: "hey -z 1m -q 50 -c 10 http://frontend-api-canary.apps/"
  - name: load-test-grpc
    type: rollout
    url: http://flagger-loadtester.flagger-system/
    timeout: 5s
    metadata:
      cmd: "ghz --insecure --proto /tmp/app.proto --call app.Service/Get -d '{}' -c 5 -n 1000 frontend-api-canary.apps:8080"

11.3 调外部 webhook 做业务 smoke test
#

假设你有个内部的回归测试服务 qa-smoke.example.com,接收 {service, version} 参数然后跑一套用例。接法:

webhooks:
  - name: smoke-test
    type: pre-rollout
    url: https://qa-smoke.example.com/run
    timeout: 5m
    metadata:
      service: frontend-api
      suite: critical-path

回归服务返回非 2xx 表示失败,Flagger 会判定 pre-rollout 失败,直接取消本次发布。

11.4 人工审批门禁
#

webhooks:
  - name: manual-gate
    type: confirm-rollout
    url: http://flagger-loadtester.flagger-system/gate/check
    timeout: 1h

这个 URL 对应 loadtester 的 gate API。默认状态是 open,如果要求发布前人工确认,运维先调:

curl -X POST http://flagger-loadtester.flagger-system/gate/close

Flagger 在 confirm-rollout 阶段会卡住。确认可以发了再:

curl -X POST http://flagger-loadtester.flagger-system/gate/open

12. 与 Argo Rollouts 深度对比
#

这是选型最常问的问题。两者都能做渐进式交付,但设计哲学不一样。

12.1 架构差异
#

Argo Rollouts:引入新的 Rollout CRD 替代 Deployment。你原来的 Deployment 要改成 Rollout,因为 Rollout 内嵌了发布策略字段(strategy.canary.steps)。策略是资源本身的一部分。

Flagger:不改 Deployment,外挂一个 Canary CR 指向 Deployment。Deployment 仍然是 Deployment,可以脱离 Flagger 独立工作。

这个差异的影响非常大:

  • Argo Rollouts 的方式对现有系统侵入性强。Helm chart / Kustomize / Operator 都得改,把 Deployment 换成 Rollout。
  • Flagger 的方式是叠加的,关掉 Flagger 服务还是服务,不受影响。

12.2 Mesh 支持
#

ProviderFlaggerArgo Rollouts
Istio原生原生
Linkerd原生原生
NGINX Ingress原生原生
AWS App Mesh原生原生
Contour原生需插件
Gloo原生原生
Traefik原生原生
SMI原生原生
Gateway API原生原生(1.6+)
Kuma原生社区插件

两者目前支持都不错,但 Flagger 多年来始终以 mesh-agnostic 为卖点,覆盖略广一点。

12.3 分析机制
#

Flagger:MetricTemplate 是集群范围的资源,Canary 引用 template 填参。逻辑:template 是模板,canary 填参

Argo Rollouts:AnalysisTemplate / ClusterAnalysisTemplate 定义分析模板,Rollout 在特定 step 启动一个 AnalysisRun。分析是一个独立的生命周期对象,结果可以查询、追溯。逻辑:analysis 是一次运行,有独立的对象

Rollouts 的 AnalysisRun 独立对象设计,好处是每次分析可审计、可重跑、可和 Rollout 解耦。Flagger 的 MetricTemplate 更轻量,但分析结果不是独立资源,只能从 Canary status 看。

12.4 发布策略表达能力
#

Argo Rollouts 的 canary steps 可以用 DSL 自由编排,比如:

steps:
  - setWeight: 5
  - pause: {duration: 2m}
  - setWeight: 20
  - pause: {}            # 无限停留,等人推进
  - analysis:
      templates:
        - templateName: success-rate
  - setWeight: 50
  - pause: {duration: 5m}

可以交叉混用 pause / setWeight / analysis / experiment / setHeaderRoute,非常灵活。

Flagger 的 canary 策略表达能力偏"规则化":stepWeight + maxWeight + interval + threshold 四个旋钮,均匀推进。要做非均匀步长、中间加暂停,需要组合 webhook 和 gate。

如果你的发布流程复杂(比如 5% → 人工确认 → 20% → 30 分钟观察 → 50% → A/B 实验),Rollouts 更自然。如果你的流程是统一的、标准化的,Flagger 更简洁。

12.5 A/B / Experiment
#

Rollouts 有独立的 Experiment CRD,可以启动"实验"——临时拉起一套带特定 label 的 Pod 接流量跑一段时间然后销毁。非常适合 shadow / dark launch。

Flagger 通过 A/B 模式的 match 做类似的事,但没有独立 Experiment 对象的生命周期。

12.6 选型建议
#

  • 如果已经用了 Flux 或 Weave 生态 → 选 Flagger,一家人无缝衔接。
  • 如果已经用了 Argo CD → Rollouts 集成更紧密(比如 Argo CD UI 可以直接展示 Rollout 状态条),但 Flagger 也完全可用。
  • 不想改 Deployment → Flagger。
  • 要做复杂的发布编排(手动 gate + 多阶段) → Rollouts 的 steps DSL 表达力更强。
  • mesh/ingress 种类多 → Flagger provider 覆盖稍广。
  • 需要独立的 Experiment / AnalysisRun 审计对象 → Rollouts。
  • 团队规模小、追求标准化 → Flagger(配置量少)。
  • 业务差异大、需要每个服务独立定制发布流程 → Rollouts。

我的经验结论:小规模 / 多服务 / 流程标准化的团队用 Flagger;大规模 / 少量核心服务 / 每个服务发布流程定制化的团队用 Rollouts

13. Istio 集成细节
#

13.1 自动生成的 VirtualService
#

apply 上面 6.3 的 Canary 之后,Flagger 会生成类似这样的 VirtualService:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: frontend-api
  namespace: apps
  ownerReferences:
    - apiVersion: flagger.app/v1beta1
      kind: Canary
      name: frontend-api
spec:
  hosts:
    - api.example.com
    - frontend-api
  gateways:
    - public-gateway.istio-system.svc.cluster.local
    - mesh
  http:
    - retries:
        attempts: 3
        perTryTimeout: 2s
        retryOn: gateway-error,connect-failure,refused-stream
      route:
        - destination:
            host: frontend-api-primary
          weight: 100
        - destination:
            host: frontend-api-canary
          weight: 0

analysis 推进时 Flagger 只改这两个 weight,不动其余字段。

13.2 DestinationRule
#

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: frontend-api-primary
  namespace: apps
spec:
  host: frontend-api-primary
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: frontend-api-canary
  namespace: apps
spec:
  host: frontend-api-canary

两个 DR 分别管 primary / canary 的连接池、熔断、mTLS。如果你在 Canary 的 service.trafficPolicy 里配置了连接池,会写到这两个 DR 里。

13.3 流量流向图
#

client → Ingress Gateway
         → VirtualService(frontend-api)
             ├─ weight=90 → DR(primary) → Service(primary) → Pod(primary)
             └─ weight=10 → DR(canary)  → Service(canary)  → Pod(canary)

Flagger 每分钟把 10 递增到 20 → 30 → 40 → 50,同时查 Prometheus 的 istio_requests_total 确认错误率。

13.4 Istio Sidecar 注入
#

命名空间要打 istio-injection=enabled,否则 Pod 没有 sidecar,istio_requests_total 不会有数据,Flagger 的指标查询始终为空,analysis 会卡死。

kubectl label ns apps istio-injection=enabled

14. NGINX Ingress 集成
#

没有 service mesh 的集群也可以做 canary。NGINX Ingress controller 原生支持 canary annotation(nginx.ingress.kubernetes.io/canary),Flagger 利用这个能力实现切流。

14.1 Canary CR
#

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: frontend-api
  namespace: apps
spec:
  provider: nginx
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: frontend-api
  ingressRef:
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    name: frontend-api
  autoscalerRef:
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    name: frontend-api
  progressDeadlineSeconds: 900
  service:
    port: 80
    targetPort: 8080
  analysis:
    interval: 30s
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
      - name: request-success-rate
        thresholdRange:
          min: 99
        interval: 1m
      - name: request-duration
        thresholdRange:
          max: 500
        interval: 1m
    webhooks:
      - name: load-test
        type: rollout
        url: http://flagger-loadtester.flagger-system/
        metadata:
          cmd: "hey -z 30s -q 20 -c 2 -host api.example.com http://ingress-nginx-controller.ingress-nginx/"

14.2 对应的 Ingress
#

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: frontend-api
  namespace: apps
  labels:
    app: frontend-api
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-api
                port:
                  number: 80

Flagger 会基于这个 Ingress 自动复制一个 frontend-api-canary Ingress,带:

metadata:
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"

analysis 推进时只改 canary-weight 这个 annotation 的值,NGINX Ingress controller 自动重载配置。

14.3 NGINX Ingress 的限制
#

  • session affinity 不兼容:如果你的 Ingress 开了 nginx.ingress.kubernetes.io/affinity: cookie,NGINX 会把用户固定到某个后端,canary 权重就失效了。要么关掉 affinity,要么用 mesh provider。
  • 指标来源是 NGINX:Flagger 查 nginx_ingress_controller_requests,要确保 NGINX controller 开了 Prometheus metrics。
  • canary-weight 粒度 1%:步长最小 1%,实际 NGINX 的分流是基于随机数,不是精确计数,样本小的时候会有偏差。

15. Gateway API 集成
#

Gateway API 是 Kubernetes 官方在推的下一代 Ingress 规范。Flagger 从 1.23 开始原生支持。

15.1 前置
#

先装一个 Gateway API 实现(Istio / Contour / Envoy Gateway / Cilium / Traefik / Kong 等都有),并创建一个 Gateway 资源。

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: public
  namespace: gateway-system
spec:
  gatewayClassName: envoy
  listeners:
    - name: http
      port: 80
      protocol: HTTP
      allowedRoutes:
        namespaces:
          from: All

15.2 Canary CR
#

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: frontend-api
  namespace: apps
spec:
  provider: gatewayapi
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: frontend-api
  autoscalerRef:
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    name: frontend-api
  progressDeadlineSeconds: 900
  service:
    port: 80
    targetPort: 8080
    hosts:
      - api.example.com
    gatewayRefs:
      - name: public
        namespace: gateway-system
  analysis:
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
      - name: request-success-rate
        thresholdRange:
          min: 99
        interval: 1m
      - name: request-duration
        thresholdRange:
          max: 500
        interval: 1m
    webhooks:
      - name: load-test
        type: rollout
        url: http://flagger-loadtester.flagger-system/
        metadata:
          cmd: "hey -z 1m -q 20 -c 2 -host api.example.com http://envoy-gateway.gateway-system/"

15.3 自动生成的 HTTPRoute
#

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: frontend-api
  namespace: apps
  ownerReferences:
    - apiVersion: flagger.app/v1beta1
      kind: Canary
      name: frontend-api
spec:
  parentRefs:
    - name: public
      namespace: gateway-system
  hostnames:
    - api.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: frontend-api-primary
          port: 80
          weight: 100
        - name: frontend-api-canary
          port: 80
          weight: 0

Flagger 改 weight 字段实现切流。

15.4 Gateway API 指标来源
#

Gateway API 的 Prometheus 指标取决于底层实现:

  • Istio 的 Gateway API 实现 → 还是 istio_requests_total
  • Envoy Gateway → envoy_http_downstream_rq_total
  • Contour → contour_httpproxy_total

Flagger 默认的 MetricTemplate 在 Gateway API 模式下会用一个统一的模板,基于 istio_requests_total(假设底层是 Istio 的 ingress impl)。如果你用 Envoy Gateway 或其他实现,要自己写 MetricTemplate。

16. 从滚动更新迁移到 Flagger:三步走
#

大多数团队是从"kubectl rolling update + 手动观察"迁到 Flagger。我的推荐路径:

16.1 Step 1:灰度启用(影子模式)
#

先在一个非核心服务上启用 Flagger,设置 skipAnalysis: true 或把 threshold 调得很宽松,让 Flagger 走完整套流程但不阻塞发布。目的:

  • 验证 primary/canary 资源创建正常
  • 验证 Prometheus 连通性
  • 验证 webhook 可达
  • 让运维熟悉 Canary status 的观察方式

建议持续 1-2 周,期间发布 5 次以上。

16.2 Step 2:全量接入
#

扩展到全部服务。这一阶段要做的事:

  • 统一 Canary template,通过 Helm / Kustomize 生成
  • 指标门禁先松后紧:第一周 min: 90,观察没有误杀就收紧到 min: 99
  • 接入通知(Slack/钉钉),让发布状态可视化
  • 准备手动 kubectl edit 强制 promote / abort 的运维手册

建议持续 2-4 周。

16.3 Step 3:建立发布门禁
#

把 Flagger 纳入 CI/CD pipeline,而不是只作为运维工具:

  • Pipeline push 镜像后,kubectl apply 新的 Deployment
  • 外部流程监听 Canary 的 .status.phase,直到 SucceededFailed
  • 失败自动通知对应服务 owner
  • 建立发布窗口限制(例如晚高峰不能发)通过 confirm-rollout webhook 实现

这一步的关键是把"发布"从运维职责转变为开发职责,运维只负责基础设施。

16.4 常见阻力
#

  • 开发抵触:说"我本来 kubectl apply 30 秒搞定,现在要 10 分钟才能看到效果"。回应:10 分钟是指标检查时间,节省的是事后回滚的 2 小时。
  • 测试环境不愿意上:测试环境指标稀疏,容易指标查询为空导致卡死。解决办法:测试环境用 skipAnalysis: true,只用 Flagger 做流量切分不做门禁。
  • HPA 和 Flagger 冲突:没有在 Canary 里声明 autoscalerRef 导致 primary 没有 HPA,放量时 primary 扛不住。必须声明。

17. 监控
#

17.1 Flagger 自身指标
#

Flagger controller 在 8080 端口暴露 Prometheus 指标。关键指标:

  • flagger_canary_total{namespace, name}:每个 Canary 的存在计数(用作 service discovery)
  • flagger_canary_status{namespace, name, phase}:当前 phase(Initialized/Progressing/Succeeded/Failed 等)
  • flagger_canary_weight{namespace, name}:当前 canary 权重
  • flagger_canary_duration_seconds:发布耗时
  • flagger_canary_metric_analysis{namespace, name, metric}:单个指标的最近值

17.2 Grafana Dashboard
#

Flagger 官方提供了一个 Grafana dashboard,ID 是 10466(可在 grafana.com/dashboards 搜 “Flagger”)。可以看到每个 Canary 当前权重、状态、成功率、延迟趋势。

17.3 告警项
#

建议的告警规则:

groups:
  - name: flagger
    rules:
      - alert: FlaggerCanaryFailed
        expr: flagger_canary_status{phase="Failed"} == 1
        for: 1m
        annotations:
          summary: "Canary {{ $labels.namespace }}/{{ $labels.name }} failed"
      - alert: FlaggerCanaryStuck
        expr: flagger_canary_status{phase="Progressing"} == 1
        for: 30m
        annotations:
          summary: "Canary {{ $labels.namespace }}/{{ $labels.name }} has been progressing for 30m"
      - alert: FlaggerControllerDown
        expr: up{job="flagger"} == 0
        for: 5m

17.4 日志关键字
#

Flagger 的日志是结构化的 JSON。关键字:

  • "Starting canary analysis":开始一次发布
  • "Advance canary weight":推进了一步
  • "Halt advancement":某次指标检查失败(不代表整体失败)
  • "Rolling back":整体失败,回滚中
  • "Promotion completed":发布成功

排障先看 controller 日志:

kubectl -n flagger-system logs deploy/flagger --tail 200 | grep frontend-api

18. 坑位合集
#

18.1 primary 初始化卡住
#

症状:kubectl get canary 一直显示 Initializing

原因和解法:

  • 原 Deployment 没有 readinessProbe:Flagger 要求必须有,否则等不到 ready。加上。
  • 原 Deployment 副本数是 0:Flagger 拒绝初始化。先 scale 到 ≥1。
  • Pod 没有 istio sidecar(Istio 模式下):ns 没打 injection label,primary Pod 起来了但 Service 走不通。
  • 网络策略(NetworkPolicy)阻断:primary 和 canary Service 之间互通被阻断。检查 NP。

18.2 指标查询为空导致发布僵死
#

症状:canary 权重卡在 10%,controller 日志看到 Halt advancement: no values found for metric request-success-rate

原因:canary 副本刚起来,Prometheus 还没抓到数据;或者压根没流量到 canary。

解法:

  • load-test webhook 主动打流量
  • 调大 analysis.interval 到 2m,让 Prometheus 有更多采样
  • 确认 Prometheus 已经抓到 <name>-canary 的 target
  • 在 MetricTemplate 的 PromQL 里用 or vector(100) 给默认值:
(sum(rate(...)) / sum(rate(...)) * 100) or vector(100)

最后一招要慎用,本质是"没有数据就当成 100% 成功",会掩盖问题。

18.3 流量权重与 HPA 冲突
#

症状:canary 到 50%,突发流量,primary 的 Pod 数没有增加,延迟飙升。

原因:没有声明 autoscalerRef,Flagger 没给 primary 复制 HPA,primary 只有初始副本数。

解法:Canary CR 里加 autoscalerRef,重新 apply。Flagger 会创建 <name>-primary HPA。

18.4 session affinity 与 canary 权重冲突
#

症状:NGINX Ingress 模式下,canary-weight 设了 20%,但 canary Pod 收到的流量远小于 20%。

原因:Ingress 开了 cookie-based session affinity,老用户全部被粘到 primary,只有新连接才按权重分。

解法:

  • 关闭 affinity(影响功能)
  • 换成 mesh provider(Istio 的权重切流对 affinity 免疫)
  • 或者把 canary 的 affinity 也关掉,只保留读路径

18.5 Gateway API backendRefs 顺序
#

症状:Gateway API 模式下,Flagger 推进权重但流量完全没变化。

原因:某些 Gateway API 实现(早期 Contour)对 backendRefs 顺序敏感,权重改了但实现不 reload。

解法:升级实现版本,或在 bug 修复前回退到 Istio 模式。

18.6 Deployment 的 revisionHistoryLimit 设太小
#

症状:发布失败回滚时,primary 的老版本 ReplicaSet 已经被清理,找不到可回滚的 image。

解法:Deployment 的 revisionHistoryLimit 不要低于 10。

18.7 webhook 超时设置过短
#

症状:pre-rollout webhook 设了 30s,但 smoke test 要 2 分钟跑完,每次都失败。

解法:timeout 设成实际耗时的 1.5 倍。注意 webhook timeout 最长 1 小时。

18.8 MetricTemplate 写错导致所有发布失败
#

症状:新加了一个 MetricTemplate,从那以后所有发布都卡在 Progressing。

原因:PromQL 语法错误或指标不存在,每次查询返回错误,Flagger 把"查询出错"当作"指标失败"。

解法:先单独 curl Prometheus API 验证 PromQL,再放进 MetricTemplate。可以用:

curl -G http://prometheus.monitoring:9090/api/v1/query \
  --data-urlencode 'query=sum(rate(istio_requests_total{destination_workload="frontend-api"}[1m]))'

18.9 多 Canary 共享同一个 Deployment
#

症状:两个 Canary 都 targetRef 到同一个 Deployment,行为异常。

原因:不允许。一个 Deployment 只能被一个 Canary 管理。

解法:每个 Deployment 独立一个 Canary。

18.10 Canary 删除后资源没清理
#

症状:kubectl delete canary frontend-api 后,primary Deployment、Service、VirtualService 都还在。

原因:这是设计如此。删除 Canary 不会删 primary,避免误操作导致服务中断。

解法:确实要清理,手动删:

kubectl -n apps delete deploy frontend-api-primary
kubectl -n apps delete svc frontend-api-primary frontend-api-canary
kubectl -n apps delete vs frontend-api
kubectl -n apps delete dr frontend-api-primary frontend-api-canary

19. 生产落地 Checklist
#

上生产前对照下面这个 checklist 过一遍:

19.1 基础设施
#

  • Prometheus 有稳定的 endpoint,Flagger 可达
  • Prometheus 抓取 mesh/ingress 的指标,确认 istio_requests_total 或等价物有数据
  • Flagger 和 flagger-loadtester 都装好
  • Slack/钉钉/Teams 通知渠道接通
  • Grafana dashboard 导入完成

19.2 服务就绪
#

  • 每个待接入服务都有健康的 readinessProbe 和 livenessProbe
  • 每个服务都有 HPA,且 Canary CR 里声明了 autoscalerRef
  • Deployment 的 revisionHistoryLimit ≥ 10
  • 命名空间 istio-injection 已开启(Istio 模式)
  • Service 的 port 命名符合 mesh 约定(http-* / grpc-*

19.3 Canary 配置
#

  • progressDeadlineSeconds 合理(= interval × stepCount × 1.5)
  • interval ≥ 1m
  • threshold 在 3-5 之间
  • maxWeight ≤ 50(不是必须,但经验上够用)
  • 至少两个 metric:成功率 + 延迟
  • 业务关键服务增加业务 metric(错误码、下游依赖)
  • pre-rollout webhook 跑 smoke test
  • rollout webhook 跑 load test(对于低流量服务)
  • 重要服务加 confirm-rollout / confirm-promotion 人工门禁

19.4 监控告警
#

  • FlaggerCanaryFailed 告警
  • FlaggerCanaryStuck 告警
  • FlaggerControllerDown 告警
  • 发布成功/失败通知到 release 频道
  • 每个 Canary 都在 Grafana dashboard 可见

19.5 运维能力
#

  • 团队知道如何看 kubectl describe canary 排障
  • 团队知道如何强制 promote(kubectl annotate canary xxx skipAnalysis=true 或改 analysis 阈值)
  • 团队知道如何手动 abort(降 stepWeight 到 0 或删 Canary)
  • 有回滚演练记录
  • 有灰度失败时的应急流程文档

19.6 迁移计划
#

  • 先非核心后核心,分批接入
  • 前 1-2 周指标阈值放宽,避免误杀
  • 每批发布数量 ≥ 5 次再进入下一批
  • 每周复盘:卡单原因、误杀原因、指标调整
  • 最终目标:所有生产发布经过 Canary,人工发布作为 fallback

20. 写在最后
#

Flagger 只摊薄"发布动作本身"的风险。它管不了:

  • 架构设计错误:新功能从一开始设计就错,指标再好看也白搭。
  • 需求错误:产品要的东西本身有问题。
  • 数据层变更:DB schema、数据迁移它看不见。
  • 跨服务事务:多服务原子变更靠手工协调或 feature flag。

把边界认清之后,装上、用熟、纳入 CI/CD 就够了。我自己记它只用一句:别信发布之前的测试,信发布之中的指标。 Canary、MetricTemplate、Webhook 都是为这句话服务的。

参考资料:

  • 官方文档 https://docs.flagger.app
  • CNCF Flagger 项目主页
  • Istio traffic management 文档
  • Kubernetes Gateway API 规范
  • Argo Rollouts 官方文档(对比阅读)
Wenzhuo Huang
作者
Wenzhuo Huang
搞运维的工程师,写代码的运维人。专注 Kubernetes、AWS、GitOps 与基础设施可靠性。这个博客既是我的技术笔记本,也是我踩过的坑的受害者档案。

相关文章

FluxCD vs ArgoCD 深度对比与迁移实战:架构、语义、多租户与选型决策

·4855 字·23 分钟
GitOps 的两条主流路线——FluxCD 与 ArgoCD——在架构、语义、运维成本和扩展性上有显著差异。本文基于官方文档和生产实战,按同步模型、应用抽象、多租户隔离、Helm 支持、可观测性、扩展机制逐项对比,给出选型决策树,并提供一套可复用的从 ArgoCD 迁移到 FluxCD 的操作手册。

Istio Ambient Mode 无 Sidecar 服务网格实践

·1464 字·7 分钟
Sidecar 模式已经陪我们走了六七年,但它的问题也越来越难以忽视。Ambient Mode 不是缝缝补补,而是从架构层面重新设计了服务网格的数据面。本文从实际运维视角深入拆解 ztunnel + Waypoint 两层架构,并给出从 Sidecar 迁移到 Ambient 的完整路径。