Harness 教程 12:高级部署策略:蓝绿 / 滚动 / 金丝雀:国内网络环境落地版

112 阅读20分钟

一:教程定位

在前 11 篇教程中,我们已经完成了 Harness 基础环境、CI/CD 流水线、触发器、测试制品管理、Kubernetes 部署、多环境参数化、审批、日志排查、CLI/YAML 工程化、多阶段流水线,以及基础回滚策略。

第 12 篇进入高级部署策略:如何在不中断业务的前提下发布新版本,并在异常时自动停止、回滚或切换流量。

常见高级发布策略包括:

滚动发布 Rolling Deployment 蓝绿发布 Blue-Green Deployment 金丝雀发布 Canary Deployment

它们解决的问题不同:

滚动发布: 逐步替换旧 Pod,资源成本低,适合大多数无状态服务。

蓝绿发布: 同时保留旧版本和新版本,新版本验证通过后一次性切流,适合需要零停机和快速回退的服务。

金丝雀发布: 先让少量流量进入新版本,观察指标正常后逐步扩大,适合生产风险较高的服务。

本篇会同时提供两种落地方式:

方式一:使用 Harness 内置 Kubernetes 部署策略 方式二:使用 Kubernetes 原生 YAML + Shell Script 实现可落地版本

这样即使你的 Harness 账号暂时没有启用全部高级能力,也可以先用 Kubernetes 原生能力完成企业初期落地。


二:适合人群

本文适合中级用户:

DevOps 工程师 SRE 平台工程师 Kubernetes 运维工程师 技术负责人 正在设计生产发布体系的团队

建议已经具备:

理解 Harness Pipeline / Stage / Step 理解 Harness Service / Environment / Infrastructure 理解 Kubernetes Deployment / Service / Ingress 理解 Docker 镜像和 Harbor 了解 readinessProbe / livenessProbe 了解 Prometheus 基础查询 已完成前 11 篇教程


三:学习目标

完成本文后,你应该能够:

理解滚动、蓝绿、金丝雀三种发布策略的区别 配置 Kubernetes Rolling Deployment 使用 Harness K8s Rolling Deploy 实现滚动发布 使用蓝绿 Deployment + Service selector 实现零停机切换 使用 Harness Blue Green 或 Shell Script 实现蓝绿发布 使用 stable/canary Deployment 实现金丝雀发布 理解 Kubernetes Service 原生流量分配的限制 理解 Ingress / Service Mesh 才能实现精确权重流量 编写异常阈值验证脚本 基于错误率、P95 延迟、健康检查结果决定是否切流 配置异常阈值不通过时自动回滚 设计 dev/test/pre/prod 不同环境的发布策略


四:三种发布策略对比

策略发布方式优点缺点适合场景
滚动发布逐批替换旧 Pod资源成本低,K8s 原生支持新旧版本会短暂共存,回滚依赖 rollout大多数无状态服务
蓝绿发布同时部署 blue/green,验证后切 Service切换快,回退快,接近零停机资源成本高,需要同时跑两套核心系统、窗口发布
金丝雀发布少量流量先进入新版本风险低,可逐步放量需要监控和流量权重能力生产高风险变更

简单判断:

如果服务简单、无状态、用户量不大: 先用滚动发布。

如果要求零停机、快速切回旧版本: 用蓝绿发布。

如果需要精确 1%、5%、20% 流量: 需要 Ingress Controller、Service Mesh、Gateway API 或专业流量治理能力。


五:国内网络环境下的发布架构

本文使用国内企业常见架构:

GitLab / Gitee ↓ Harness Pipeline ↓ Harness Delegate ↓ Harbor / ACR / TCR / SWR ↓ Kubernetes 集群 ↓ Prometheus / Grafana / APM

生产发布闭环:

构建镜像 ↓ 推送 Harbor ↓ 部署新版本 ↓ 健康检查 ↓ 流量切换或放量 ↓ 指标验证 ↓ 成功:继续发布 ↓ 失败:自动回滚或人工介入

国内环境重点:

Harbor 基础镜像要提前同步 Kubernetes 节点必须能访问 Harbor Prometheus/Grafana 通常部署在内网 Harness Delegate 必须能访问 K8s API 和 Prometheus 企业代理不要错误代理 kubernetes.default.svc 生产发布通知优先企业微信、钉钉、飞书


六:示例应用和目录结构

推荐目录:

harness-demo-app/ ├── app/ │ ├── package.json │ ├── server.js │ └── server.test.js ├── docker/ │ └── Dockerfile ├── k8s/ │ ├── rolling/ │ │ ├── deployment.yaml │ │ └── service.yaml │ ├── bluegreen/ │ │ ├── deployment-blue.yaml │ │ ├── deployment-green.yaml │ │ └── service.yaml │ ├── canary/ │ │ ├── deployment-stable.yaml │ │ ├── deployment-canary.yaml │ │ └── service.yaml │ └── ingress/ │ └── nginx-canary.yaml ├── scripts/ │ ├── deploy-rolling.sh │ ├── verify-health.sh │ ├── deploy-bluegreen.sh │ ├── switch-bluegreen.sh │ ├── rollback-bluegreen.sh │ ├── deploy-canary.sh │ ├── verify-metrics.sh │ ├── promote-canary.sh │ └── rollback-canary.sh └── harness/ └── pipelines/ ├── rolling-demo.yaml ├── bluegreen-demo.yaml └── canary-demo.yaml


第一部分:滚动发布 Rolling Deployment

七:滚动发布原理

滚动发布是 Kubernetes Deployment 默认支持的发布方式。

核心逻辑:

旧 ReplicaSet 逐步缩容 新 ReplicaSet 逐步扩容 Service 始终选择 app 标签 Ready 的新 Pod 逐步接收流量 如果新 Pod 不 Ready,发布会卡住或失败

典型参数:

maxSurge: 发布过程中最多可以额外创建多少 Pod

maxUnavailable: 发布过程中最多允许多少 Pod 不可用

readinessProbe: 决定新 Pod 是否可以接流量

progressDeadlineSeconds: 多久未完成 rollout 则判定失败

推荐生产配置:

replicas >= 2 maxSurge = 1 maxUnavailable = 0 readinessProbe 必须配置 livenessProbe 必须配置 progressDeadlineSeconds 建议 120~300 秒


八:Rolling Deployment YAML

k8s/rolling/deployment.yaml

yaml id="sgkgvf"复制

apiVersion: apps/v1
kind: Deployment
metadata:
  name: harness-demo-app
  namespace: prod
  labels:
    app: harness-demo-app
spec:
  replicas: 3
  revisionHistoryLimit: 5
  progressDeadlineSeconds: 180
  selector:
    matchLabels:
      app: harness-demo-app
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: harness-demo-app
    spec:
      imagePullSecrets:
        - name: harbor-pull-secret
      containers:
        - name: harness-demo-app
          image: harbor.company.com/devops/harness-demo-app:IMAGE_TAG
          ports:
            - containerPort: 3000
          env:
            - name: APP_ENV
              value: prod
            - name: APP_VERSION
              value: IMAGE_TAG
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10
            timeoutSeconds: 2
            failureThreshold: 6
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 20
            periodSeconds: 20
            timeoutSeconds: 2
            failureThreshold: 3
          resources:
            requests:
              cpu: "200m"
              memory: "256Mi"
            limits:
              cpu: "1"
              memory: "1Gi"

k8s/rolling/service.yaml

yaml id="g2hdf2"复制

apiVersion: v1
kind: Service
metadata:
  name: harness-demo-app
  namespace: prod
spec:
  type: ClusterIP
  selector:
    app: harness-demo-app
  ports:
    - name: http
      port: 80
      targetPort: 3000

九:滚动发布脚本

scripts/deploy-rolling.sh

bash id="k44s4k"复制

#!/usr/bin/env bash
set -e

APP_NAME="${APP_NAME:-harness-demo-app}"
NAMESPACE="${NAMESPACE:-prod}"
IMAGE_TAG="${IMAGE_TAG:?IMAGE_TAG is required}"

WORKDIR="/tmp/${APP_NAME}-rolling"

rm -rf "${WORKDIR}"
mkdir -p "${WORKDIR}"

cp k8s/rolling/deployment.yaml "${WORKDIR}/deployment.yaml"
cp k8s/rolling/service.yaml "${WORKDIR}/service.yaml"

sed -i "s|IMAGE_TAG|${IMAGE_TAG}|g" "${WORKDIR}/deployment.yaml"

echo "开始滚动发布:${APP_NAME}:${IMAGE_TAG}"

kubectl apply -f "${WORKDIR}/service.yaml"
kubectl apply -f "${WORKDIR}/deployment.yaml"

kubectl rollout status deployment/${APP_NAME} -n ${NAMESPACE} --timeout=300s

kubectl get deploy ${APP_NAME} -n ${NAMESPACE} -o wide
kubectl get pods -n ${NAMESPACE} -l app=${APP_NAME} -o wide

echo "滚动发布完成"

十:滚动发布失败验证

模拟错误镜像:

bash id="wpmgun"复制

IMAGE_TAG=not-exist-tag NAMESPACE=prod scripts/deploy-rolling.sh

预期:

新 Pod 出现 ImagePullBackOff rollout status 超时 Harness Step 失败 Failure Strategy 触发 StageRollback 或 Manual Intervention

排查:

bash id="ya9hze"复制

kubectl get pods -n prod -l app=harness-demo-app
kubectl describe pod <pod-name> -n prod
kubectl rollout history deployment/harness-demo-app -n prod

十一:Harness Rolling Deploy 设计

推荐 Harness Stage:

Stage: Deploy Rolling Step 1: Check Image Tag Step 2: K8s Rolling Deploy Step 3: Verify Health Failure Strategy: AllErrors → StageRollback

生产环境必须增加镜像 Tag 校验:

bash id="nng8rn"复制

set -e

IMAGE_TAG="<+pipeline.variables.image_tag>"

if [ "${IMAGE_TAG}" = "latest" ]; then
  echo "生产环境禁止使用 latest"
  exit 1
fi

echo "镜像 Tag 校验通过:${IMAGE_TAG}"

Stage 级失败策略:

yaml id="n690fb"复制

failureStrategies:
  - onFailure:
      errors:
        - AllErrors
      action:
        type: StageRollback

第二部分:蓝绿发布 Blue-Green Deployment

十二:蓝绿发布原理

蓝绿发布维护两套版本:

blue:当前承载生产流量 green:新版本部署环境

发布流程:

  1. 当前 Service 指向 blue
  2. 部署 green 新版本
  3. 验证 green 的健康检查和指标
  4. 验证通过后,将 Service selector 切到 green
  5. 如果 green 异常,将 Service selector 切回 blue
  6. 下一次发布时,角色互换

优点:

切换快 回退快 新版本可在不接生产流量时先验证 接近零停机

缺点:

需要同时运行两套资源 资源成本更高 数据库变更必须兼容 blue 和 green Service selector 设计必须清晰


十三:蓝绿发布标签设计

推荐标签:

app: harness-demo-app color: blue version: v1.0.0

和:

app: harness-demo-app color: green version: v1.1.0

Service 通过 selector 控制流量:

yaml id="hdy02w"复制

selector:
  app: harness-demo-app
  color: blue

切换到 green:

yaml id="vtue52"复制

selector:
  app: harness-demo-app
  color: green

十四:Blue Deployment

k8s/bluegreen/deployment-blue.yaml

yaml id="jlnwmb"复制

apiVersion: apps/v1
kind: Deployment
metadata:
  name: harness-demo-app-blue
  namespace: prod
  labels:
    app: harness-demo-app
    color: blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: harness-demo-app
      color: blue
  template:
    metadata:
      labels:
        app: harness-demo-app
        color: blue
    spec:
      imagePullSecrets:
        - name: harbor-pull-secret
      containers:
        - name: harness-demo-app
          image: harbor.company.com/devops/harness-demo-app:BLUE_IMAGE_TAG
          ports:
            - containerPort: 3000
          env:
            - name: APP_COLOR
              value: blue
            - name: APP_VERSION
              value: BLUE_IMAGE_TAG
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10

十五:Green Deployment

k8s/bluegreen/deployment-green.yaml

yaml id="jm669i"复制

apiVersion: apps/v1
kind: Deployment
metadata:
  name: harness-demo-app-green
  namespace: prod
  labels:
    app: harness-demo-app
    color: green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: harness-demo-app
      color: green
  template:
    metadata:
      labels:
        app: harness-demo-app
        color: green
    spec:
      imagePullSecrets:
        - name: harbor-pull-secret
      containers:
        - name: harness-demo-app
          image: harbor.company.com/devops/harness-demo-app:GREEN_IMAGE_TAG
          ports:
            - containerPort: 3000
          env:
            - name: APP_COLOR
              value: green
            - name: APP_VERSION
              value: GREEN_IMAGE_TAG
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10

十六:Blue-Green Service

k8s/bluegreen/service.yaml

yaml id="4i206h"复制

apiVersion: v1
kind: Service
metadata:
  name: harness-demo-app
  namespace: prod
spec:
  type: ClusterIP
  selector:
    app: harness-demo-app
    color: ACTIVE_COLOR
  ports:
    - name: http
      port: 80
      targetPort: 3000

当前生产流量指向 blue:

bash id="x45sdp"复制

kubectl patch service harness-demo-app -n prod \
  -p '{"spec":{"selector":{"app":"harness-demo-app","color":"blue"}}}'

切到 green:

bash id="pa7bxf"复制

kubectl patch service harness-demo-app -n prod \
  -p '{"spec":{"selector":{"app":"harness-demo-app","color":"green"}}}'

回滚到 blue:

bash id="pysddj"复制

kubectl patch service harness-demo-app -n prod \
  -p '{"spec":{"selector":{"app":"harness-demo-app","color":"blue"}}}'

十七:蓝绿部署脚本

scripts/deploy-bluegreen.sh

bash id="2cfi7b"复制

#!/usr/bin/env bash
set -e

APP_NAME="${APP_NAME:-harness-demo-app}"
NAMESPACE="${NAMESPACE:-prod}"
TARGET_COLOR="${TARGET_COLOR:?TARGET_COLOR is required}"
IMAGE_TAG="${IMAGE_TAG:?IMAGE_TAG is required}"

if [ "${TARGET_COLOR}" != "blue" ] && [ "${TARGET_COLOR}" != "green" ]; then
  echo "TARGET_COLOR 只能是 blue 或 green"
  exit 1
fi

WORKDIR="/tmp/${APP_NAME}-bluegreen"
rm -rf "${WORKDIR}"
mkdir -p "${WORKDIR}"

if [ "${TARGET_COLOR}" = "blue" ]; then
  cp k8s/bluegreen/deployment-blue.yaml "${WORKDIR}/deployment.yaml"
  sed -i "s|BLUE_IMAGE_TAG|${IMAGE_TAG}|g" "${WORKDIR}/deployment.yaml"
else
  cp k8s/bluegreen/deployment-green.yaml "${WORKDIR}/deployment.yaml"
  sed -i "s|GREEN_IMAGE_TAG|${IMAGE_TAG}|g" "${WORKDIR}/deployment.yaml"
fi

echo "部署 ${TARGET_COLOR} 环境,镜像版本:${IMAGE_TAG}"

kubectl apply -f "${WORKDIR}/deployment.yaml"

kubectl rollout status deployment/${APP_NAME}-${TARGET_COLOR} -n ${NAMESPACE} --timeout=300s

kubectl get deploy -n ${NAMESPACE} -l app=${APP_NAME}
kubectl get pods -n ${NAMESPACE} -l app=${APP_NAME},color=${TARGET_COLOR} -o wide

echo "${TARGET_COLOR} 环境部署完成"

十八:蓝绿切流脚本

scripts/switch-bluegreen.sh

bash id="xkml7f"复制

#!/usr/bin/env bash
set -e

APP_NAME="${APP_NAME:-harness-demo-app}"
NAMESPACE="${NAMESPACE:-prod}"
TARGET_COLOR="${TARGET_COLOR:?TARGET_COLOR is required}"

if [ "${TARGET_COLOR}" != "blue" ] && [ "${TARGET_COLOR}" != "green" ]; then
  echo "TARGET_COLOR 只能是 blue 或 green"
  exit 1
fi

echo "切换 Service 流量到:${TARGET_COLOR}"

kubectl patch service ${APP_NAME} -n ${NAMESPACE} \
  -p "{\"spec\":{\"selector\":{\"app\":\"${APP_NAME}\",\"color\":\"${TARGET_COLOR}\"}}}"

echo "查看 Service selector"
kubectl get svc ${APP_NAME} -n ${NAMESPACE} -o yaml | grep -A5 selector

echo "查看 Endpoints"
kubectl get endpoints ${APP_NAME} -n ${NAMESPACE}

echo "流量已切换到:${TARGET_COLOR}"

十九:蓝绿回滚脚本

scripts/rollback-bluegreen.sh

bash id="h68jps"复制

#!/usr/bin/env bash
set -e

APP_NAME="${APP_NAME:-harness-demo-app}"
NAMESPACE="${NAMESPACE:-prod}"
ROLLBACK_COLOR="${ROLLBACK_COLOR:?ROLLBACK_COLOR is required}"

echo "开始蓝绿回滚,切回:${ROLLBACK_COLOR}"

kubectl patch service ${APP_NAME} -n ${NAMESPACE} \
  -p "{\"spec\":{\"selector\":{\"app\":\"${APP_NAME}\",\"color\":\"${ROLLBACK_COLOR}\"}}}"

kubectl get svc ${APP_NAME} -n ${NAMESPACE} -o yaml | grep -A5 selector
kubectl get endpoints ${APP_NAME} -n ${NAMESPACE}

echo "蓝绿回滚完成"

二十:蓝绿发布流程

假设当前生产指向 blue:

active = blue idle = green

发布 green:

bash id="pu9wkz"复制

TARGET_COLOR=green IMAGE_TAG=v1.1.0 NAMESPACE=prod scripts/deploy-bluegreen.sh

验证 green:

bash id="nokkmv"复制

kubectl get pods -n prod -l app=harness-demo-app,color=green

可以临时端口转发 green Deployment 验证:

bash id="v9jw08"复制

kubectl port-forward deployment/harness-demo-app-green 18080:3000 -n prod
curl -f http://127.0.0.1:18080/health

切流:

bash id="klvi8l"复制

TARGET_COLOR=green NAMESPACE=prod scripts/switch-bluegreen.sh

如果发现异常,切回 blue:

bash id="ew7sq0"复制

ROLLBACK_COLOR=blue NAMESPACE=prod scripts/rollback-bluegreen.sh

二十一:Harness 蓝绿 Pipeline 设计

推荐 Stage:

Stage: Blue Green Deploy Step 1: Check Image Tag Step 2: Deploy Idle Color Step 3: Verify Idle Color Step 4: Manual Approval 或 Metric Verification Step 5: Switch Traffic Step 6: Verify Active Traffic Failure Strategy: AllErrors → Rollback Traffic

变量:

yaml id="zdc789"复制

variables:
  - name: app_name
    type: String
    value: harness-demo-app
  - name: namespace
    type: String
    value: prod
  - name: active_color
    type: String
    value: <+input>
  - name: target_color
    type: String
    value: <+input>
  - name: image_tag
    type: String
    value: <+input>

生产运行示例:

active_color = blue target_color = green image_tag = v1.1.0


第三部分:金丝雀发布 Canary Deployment

二十二:金丝雀发布原理

金丝雀发布不是一次性把所有流量切到新版本,而是逐步放量:

第 1 阶段:5% 流量进入新版本 第 2 阶段:20% 流量进入新版本 第 3 阶段:50% 流量进入新版本 第 4 阶段:100% 流量进入新版本

每个阶段都要验证指标:

错误率 P95 / P99 延迟 HTTP 5xx Pod 重启次数 CPU / Memory 业务核心指标 日志异常数

如果指标异常:

停止放量 回滚 canary 流量回到 stable 通知负责人


二十三:Kubernetes 原生金丝雀能力边界

Kubernetes Service 原生不能配置精确权重。

它只能通过 selector 选择一组 Pod,然后由 kube-proxy/iptables/ipvs 在这些 Endpoints 中转发。

因此:

stable replicas = 9 canary replicas = 1 大致接近 10% canary 流量

stable replicas = 4 canary replicas = 1 大致接近 20% canary 流量

但这不是严格权重控制。

如果要精确流量比例,需要:

Nginx Ingress Canary Traefik Weighted Services Istio VirtualService Linkerd / Kuma / Consul Connect Gateway API Argo Rollouts 专用 API Gateway

本文提供两套方案:

方案 A:Kubernetes 原生副本比例近似金丝雀 方案 B:Nginx Ingress Canary 精确比例金丝雀


二十四:Canary Stable Deployment

k8s/canary/deployment-stable.yaml

yaml id="5yj42w"复制

apiVersion: apps/v1
kind: Deployment
metadata:
  name: harness-demo-app-stable
  namespace: prod
  labels:
    app: harness-demo-app
    track: stable
spec:
  replicas: 9
  selector:
    matchLabels:
      app: harness-demo-app
      track: stable
  template:
    metadata:
      labels:
        app: harness-demo-app
        track: stable
    spec:
      imagePullSecrets:
        - name: harbor-pull-secret
      containers:
        - name: harness-demo-app
          image: harbor.company.com/devops/harness-demo-app:STABLE_IMAGE_TAG
          ports:
            - containerPort: 3000
          env:
            - name: APP_VERSION
              value: STABLE_IMAGE_TAG
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10

二十五:Canary Deployment

k8s/canary/deployment-canary.yaml

yaml id="bqun20"复制

apiVersion: apps/v1
kind: Deployment
metadata:
  name: harness-demo-app-canary
  namespace: prod
  labels:
    app: harness-demo-app
    track: canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: harness-demo-app
      track: canary
  template:
    metadata:
      labels:
        app: harness-demo-app
        track: canary
    spec:
      imagePullSecrets:
        - name: harbor-pull-secret
      containers:
        - name: harness-demo-app
          image: harbor.company.com/devops/harness-demo-app:CANARY_IMAGE_TAG
          ports:
            - containerPort: 3000
          env:
            - name: APP_VERSION
              value: CANARY_IMAGE_TAG
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10

二十六:Canary Service

k8s/canary/service.yaml

yaml id="885jue"复制

apiVersion: v1
kind: Service
metadata:
  name: harness-demo-app
  namespace: prod
spec:
  type: ClusterIP
  selector:
    app: harness-demo-app
  ports:
    - name: http
      port: 80
      targetPort: 3000

这个 Service 同时选择:

track=stable track=canary

因为二者都有:

app=harness-demo-app


二十七:Canary 部署脚本

scripts/deploy-canary.sh

bash id="heqfr4"复制

#!/usr/bin/env bash
set -e

APP_NAME="${APP_NAME:-harness-demo-app}"
NAMESPACE="${NAMESPACE:-prod}"
STABLE_IMAGE_TAG="${STABLE_IMAGE_TAG:?STABLE_IMAGE_TAG is required}"
CANARY_IMAGE_TAG="${CANARY_IMAGE_TAG:?CANARY_IMAGE_TAG is required}"
STABLE_REPLICAS="${STABLE_REPLICAS:-9}"
CANARY_REPLICAS="${CANARY_REPLICAS:-1}"

WORKDIR="/tmp/${APP_NAME}-canary"
rm -rf "${WORKDIR}"
mkdir -p "${WORKDIR}"

cp k8s/canary/deployment-stable.yaml "${WORKDIR}/deployment-stable.yaml"
cp k8s/canary/deployment-canary.yaml "${WORKDIR}/deployment-canary.yaml"
cp k8s/canary/service.yaml "${WORKDIR}/service.yaml"

sed -i "s|STABLE_IMAGE_TAG|${STABLE_IMAGE_TAG}|g" "${WORKDIR}/deployment-stable.yaml"
sed -i "s|CANARY_IMAGE_TAG|${CANARY_IMAGE_TAG}|g" "${WORKDIR}/deployment-canary.yaml"

sed -i "s|replicas: 9|replicas: ${STABLE_REPLICAS}|g" "${WORKDIR}/deployment-stable.yaml"
sed -i "s|replicas: 1|replicas: ${CANARY_REPLICAS}|g" "${WORKDIR}/deployment-canary.yaml"

kubectl apply -f "${WORKDIR}/service.yaml"
kubectl apply -f "${WORKDIR}/deployment-stable.yaml"
kubectl apply -f "${WORKDIR}/deployment-canary.yaml"

kubectl rollout status deployment/${APP_NAME}-stable -n ${NAMESPACE} --timeout=300s
kubectl rollout status deployment/${APP_NAME}-canary -n ${NAMESPACE} --timeout=300s

kubectl get deploy -n ${NAMESPACE} -l app=${APP_NAME}
kubectl get pods -n ${NAMESPACE} -l app=${APP_NAME} -o wide
kubectl get endpoints ${APP_NAME} -n ${NAMESPACE}

echo "canary 部署完成"

运行 10% 金丝雀:

bash id="oln8h9"复制

STABLE_IMAGE_TAG=v1.0.0 \
CANARY_IMAGE_TAG=v1.1.0 \
STABLE_REPLICAS=9 \
CANARY_REPLICAS=1 \
NAMESPACE=prod \
scripts/deploy-canary.sh

运行 20% 金丝雀:

bash id="b6eih9"复制

STABLE_REPLICAS=4 \
CANARY_REPLICAS=1 \
scripts/deploy-canary.sh

二十八:Nginx Ingress 精确权重金丝雀示例

如果企业使用 Nginx Ingress,可以用 canary annotation 做更接近精确比例的灰度。

stable Ingress:

yaml id="ra18ee"复制

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: harness-demo-app-stable
  namespace: prod
spec:
  ingressClassName: nginx
  rules:
    - host: demo.company.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: harness-demo-app-stable
                port:
                  number: 80

canary Ingress:

yaml id="85rj7w"复制

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: harness-demo-app-canary
  namespace: prod
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
  ingressClassName: nginx
  rules:
    - host: demo.company.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: harness-demo-app-canary
                port:
                  number: 80

调整到 30%:

bash id="n5ozbj"复制

kubectl annotate ingress harness-demo-app-canary \
  -n prod \
  nginx.ingress.kubernetes.io/canary-weight="30" \
  --overwrite

切到 100% 后,可以删除 canary Ingress,把 stable Service 切到新版本,或按企业标准流程 Promote。

注意:

Nginx Ingress canary 是网关层能力,不是 Kubernetes Service 原生能力。 如果没有 Nginx Ingress Controller,这些 annotation 不会生效。


第四部分:异常阈值自动切换

二十九:异常阈值设计

金丝雀是否继续放量,不能只看 Pod Ready,还要看运行指标。

建议阈值:

HTTP 5xx 错误率 < 1% P95 延迟 < 500ms Pod 重启次数 = 0 readinessProbe 全部通过 CPU 使用率 < 80% 内存使用率 < 85% 业务关键接口成功率 > 99%

入门阶段可先使用:

健康检查 + HTTP 错误率 + P95 延迟

Prometheus 指标前提:

应用已暴露 /metrics Prometheus 已采集应用指标 或者 Ingress / Service Mesh 已暴露请求指标 Harness Delegate 能访问 Prometheus API


三十:Prometheus 验证脚本

scripts/verify-metrics.sh

bash id="c2s071"复制

#!/usr/bin/env bash
set -e

PROM_URL="${PROM_URL:?PROM_URL is required}"
APP_NAME="${APP_NAME:-harness-demo-app}"
ERROR_RATE_THRESHOLD="${ERROR_RATE_THRESHOLD:-0.01}"
P95_THRESHOLD="${P95_THRESHOLD:-0.5}"

echo "开始验证 Prometheus 指标"
echo "PROM_URL=${PROM_URL}"
echo "APP_NAME=${APP_NAME}"
echo "ERROR_RATE_THRESHOLD=${ERROR_RATE_THRESHOLD}"
echo "P95_THRESHOLD=${P95_THRESHOLD}"

ERROR_QUERY="sum(rate(http_requests_total{app=\"${APP_NAME}\",status=~\"5..\"}[5m])) / sum(rate(http_requests_total{app=\"${APP_NAME}\"}[5m]))"

P95_QUERY="histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{app=\"${APP_NAME}\"}[5m])) by (le))"

ERROR_RATE=$(curl -sG "${PROM_URL}/api/v1/query" \
  --data-urlencode "query=${ERROR_QUERY}" \
  | sed -n 's/.*"value":\[[^,]*,"\([^"]*\)"\].*/\1/p')

P95=$(curl -sG "${PROM_URL}/api/v1/query" \
  --data-urlencode "query=${P95_QUERY}" \
  | sed -n 's/.*"value":\[[^,]*,"\([^"]*\)"\].*/\1/p')

if [ -z "${ERROR_RATE}" ]; then
  echo "未获取到 ERROR_RATE,可能是指标不存在或 Prometheus 查询失败"
  exit 1
fi

if [ -z "${P95}" ]; then
  echo "未获取到 P95,可能是指标不存在或 Prometheus 查询失败"
  exit 1
fi

echo "ERROR_RATE=${ERROR_RATE}"
echo "P95=${P95}"

awk -v val="${ERROR_RATE}" -v threshold="${ERROR_RATE_THRESHOLD}" 'BEGIN {
  if (val > threshold) {
    printf "错误率超阈值:%f > %f\n", val, threshold
    exit 1
  }
}'

awk -v val="${P95}" -v threshold="${P95_THRESHOLD}" 'BEGIN {
  if (val > threshold) {
    printf "P95 延迟超阈值:%f > %f\n", val, threshold
    exit 1
  }
}'

echo "Prometheus 指标验证通过"

注意:

这里假设你的应用或网关暴露了 http_requests_total 和 http_request_duration_seconds_bucket。 不同公司指标名不同,必须根据实际 Prometheus 指标修改 PromQL。 生产建议使用 jq 解析 JSON,不建议长期依赖 sed。


三十一:金丝雀 Promote 脚本

当金丝雀验证通过后,将 stable 更新到 canary 版本,并删除 canary。

scripts/promote-canary.sh

bash id="fim69e"复制

#!/usr/bin/env bash
set -e

APP_NAME="${APP_NAME:-harness-demo-app}"
NAMESPACE="${NAMESPACE:-prod}"
CANARY_IMAGE_TAG="${CANARY_IMAGE_TAG:?CANARY_IMAGE_TAG is required}"

echo "开始 Promote Canary:${CANARY_IMAGE_TAG}"

kubectl set image deployment/${APP_NAME}-stable \
  ${APP_NAME}=harbor.company.com/devops/${APP_NAME}:${CANARY_IMAGE_TAG} \
  -n ${NAMESPACE}

kubectl rollout status deployment/${APP_NAME}-stable -n ${NAMESPACE} --timeout=300s

echo "删除 canary Deployment"
kubectl delete deployment ${APP_NAME}-canary -n ${NAMESPACE} --ignore-not-found=true

kubectl get deploy -n ${NAMESPACE} -l app=${APP_NAME}
kubectl get pods -n ${NAMESPACE} -l app=${APP_NAME} -o wide
kubectl get endpoints ${APP_NAME} -n ${NAMESPACE}

echo "Canary Promote 完成"

三十二:金丝雀回滚脚本

scripts/rollback-canary.sh

bash id="wgr0tc"复制

#!/usr/bin/env bash
set -e

APP_NAME="${APP_NAME:-harness-demo-app}"
NAMESPACE="${NAMESPACE:-prod}"

echo "开始回滚 canary"

kubectl delete deployment ${APP_NAME}-canary -n ${NAMESPACE} --ignore-not-found=true

for i in $(seq 1 30); do
  COUNT=$(kubectl get pods -n ${NAMESPACE} \
    -l app=${APP_NAME},track=canary \
    --no-headers 2>/dev/null | wc -l | tr -d ' ')

  echo "剩余 canary Pod 数:${COUNT}"

  if [ "${COUNT}" = "0" ]; then
    break
  fi

  sleep 3
done

kubectl get deploy -n ${NAMESPACE} -l app=${APP_NAME}
kubectl get pods -n ${NAMESPACE} -l app=${APP_NAME} -o wide
kubectl get endpoints ${APP_NAME} -n ${NAMESPACE}

echo "canary 已回滚,流量回到 stable"

第五部分:Harness Pipeline 实战设计

三十三:Rolling Pipeline 结构

Pipeline: rolling-release

Stage 1: CI Build

  • Unit Test
  • Build Image
  • Push Harbor

Stage 2: Rolling Deploy

  • Check Image Tag
  • K8s Rolling Deploy
  • Verify Health Failure Strategy: AllErrors → StageRollback

适合:

dev/test 普通无状态服务 发布频繁的内部系统


三十四:Blue-Green Pipeline 结构

Pipeline: bluegreen-release

Stage 1: Deploy Idle Color

  • 部署 green 或 blue 空闲环境

Stage 2: Verify Idle Color

  • 健康检查
  • Prometheus 指标检查
  • 冒烟测试

Stage 3: Approval

  • 生产切流审批

Stage 4: Switch Traffic

  • Service selector 切换到 target_color

Stage 5: Verify Active Traffic

  • 验证 Service endpoints
  • 验证 /health
  • 验证 Prometheus 指标

Failure Strategy: AllErrors → rollback-bluegreen.sh

适合:

生产核心服务 要求快速回退 发布窗口严格 流量不能中断


三十五:Canary Pipeline 结构

Pipeline: canary-release

Stage 1: Deploy Canary 10%

  • stable 9 副本
  • canary 1 副本

Stage 2: Verify Canary

  • /health
  • 错误率
  • P95 延迟
  • Pod restart

Stage 3: Manual Approval 或 Auto Promote

  • 指标正常继续
  • 指标异常回滚

Stage 4: Expand Canary 30%

  • stable 7 副本
  • canary 3 副本

Stage 5: Verify Again

Stage 6: Promote Canary

  • stable 更新为新版本
  • 删除 canary

Failure Strategy: AllErrors → rollback-canary.sh

适合:

用户量较大 生产风险较高 希望逐步放量 有监控指标基础


三十六:Canary Harness YAML 参考

不同 Harness 版本的内置 Canary 字段可能不同,建议先在 Visual Editor 中选择 Kubernetes Canary 策略,然后切到 YAML 保存。下面提供一个 Shell Script 版通用参考:

yaml id="n4fj89"复制

pipeline:
  name: canary-release
  identifier: canary_release
  projectIdentifier: harness_demo
  orgIdentifier: devops_lab
  tags:
    tutorial: harness-12
    strategy: canary
  variables:
    - name: app_name
      type: String
      value: harness-demo-app
    - name: namespace
      type: String
      value: prod
    - name: stable_image_tag
      type: String
      value: <+input>
    - name: canary_image_tag
      type: String
      value: <+input>
    - name: prom_url
      type: String
      value: http://prometheus.monitoring.svc:9090
  stages:
    - stage:
        name: Deploy Canary 10 Percent
        identifier: deploy_canary_10
        type: Custom
        spec:
          execution:
            steps:
              - step:
                  name: Deploy Canary
                  identifier: deploy_canary
                  type: ShellScript
                  timeout: 10m
                  spec:
                    shell: Bash
                    onDelegate: true
                    source:
                      type: Inline
                      spec:
                        script: |
                          set -e
                          export KUBECONFIG=${HARNESS_KUBE_CONFIG_PATH}

                          chmod +x scripts/deploy-canary.sh

                          APP_NAME="<+pipeline.variables.app_name>" \
                          NAMESPACE="<+pipeline.variables.namespace>" \
                          STABLE_IMAGE_TAG="<+pipeline.variables.stable_image_tag>" \
                          CANARY_IMAGE_TAG="<+pipeline.variables.canary_image_tag>" \
                          STABLE_REPLICAS=9 \
                          CANARY_REPLICAS=1 \
                          scripts/deploy-canary.sh

              - step:
                  name: Verify Canary Metrics
                  identifier: verify_canary_metrics
                  type: ShellScript
                  timeout: 10m
                  spec:
                    shell: Bash
                    onDelegate: true
                    source:
                      type: Inline
                      spec:
                        script: |
                          set -e

                          chmod +x scripts/verify-metrics.sh

                          PROM_URL="<+pipeline.variables.prom_url>" \
                          APP_NAME="<+pipeline.variables.app_name>" \
                          ERROR_RATE_THRESHOLD="0.01" \
                          P95_THRESHOLD="0.5" \
                          scripts/verify-metrics.sh

              - step:
                  name: Promote Canary
                  identifier: promote_canary
                  type: ShellScript
                  timeout: 10m
                  spec:
                    shell: Bash
                    onDelegate: true
                    source:
                      type: Inline
                      spec:
                        script: |
                          set -e
                          export KUBECONFIG=${HARNESS_KUBE_CONFIG_PATH}

                          chmod +x scripts/promote-canary.sh

                          APP_NAME="<+pipeline.variables.app_name>" \
                          NAMESPACE="<+pipeline.variables.namespace>" \
                          CANARY_IMAGE_TAG="<+pipeline.variables.canary_image_tag>" \
                          scripts/promote-canary.sh
        failureStrategies:
          - onFailure:
              errors:
                - AllErrors
              action:
                type: ManualIntervention
                spec:
                  timeout: 10m
                  onTimeout:
                    action:
                      type: Abort

说明:

该 YAML 用 Custom Stage + Shell Script 实现金丝雀。 如果你的 Harness 已支持内置 Kubernetes Canary 策略,请优先使用内置策略。


三十七:异常阈值自动切换逻辑

自动切换可以按以下规则:

如果错误率 <= 1% 且 P95 <= 500ms: 继续放量或 Promote

如果错误率 > 1% 或 P95 > 500ms: 停止发布 删除 canary 保持 stable 发送告警

伪代码:

bash id="y2o4yd"复制

if verify_metrics; then
  promote_canary
else
  rollback_canary
  exit 1
fi

在 Harness 中可以拆成:

Verify Metrics Step: 成功 → Promote Canary 失败 → Failure Strategy → Rollback Canary / Manual Intervention

生产建议:

自动 Promote 前增加人工确认 异常自动回滚后必须通知 阈值必须先在 test/pre 环境验证 不要第一次就在 prod 使用自动阈值切换


三十八:蓝绿与金丝雀中的数据库变更问题

发布策略只能解决应用流量切换,不能自动解决数据库兼容问题。

数据库变更必须遵守:

先兼容旧版本 再发布新应用 确认稳定后再清理旧字段 禁止新版本依赖立即删除的字段 禁止不可逆变更和应用发布强绑定

推荐流程:

第 1 次发布: 添加新字段,旧代码不受影响

第 2 次发布: 新代码开始写新字段,同时兼容旧字段

第 3 次发布: 数据迁移完成

第 4 次发布: 清理旧字段

否则:

蓝绿切回旧版本可能失败 金丝雀回滚后旧代码不兼容新数据库 滚动发布中新旧版本同时运行时可能出错


三十九:生产发布策略选择建议

1. 内部系统

推荐:Rolling 原因:简单、成本低、K8s 原生支持

2. 用户量较小的业务系统

推荐:Rolling + 健康检查 + 自动回滚

3. 核心交易类系统

推荐:Blue-Green 或 Canary 必须:审批、监控验证、回滚预案、数据库兼容

4. 高流量互联网系统

推荐:Canary + Ingress/Service Mesh 精确流量 + 自动验证

5. 状态复杂或数据库变更较多的系统

推荐:人工审批 + 小流量 Canary + 明确回滚方案 不建议:完全自动 Promote


四十:常见问题排查

1. 滚动发布卡住

常见原因:

readinessProbe 不通过 镜像拉取失败 应用启动失败 资源不足 progressDeadlineSeconds 超时

排查:

bash id="l1ybsl"复制

kubectl rollout status deployment/harness-demo-app -n prod
kubectl describe pod <pod-name> -n prod
kubectl logs <pod-name> -n prod

2. 蓝绿切流后访问异常

常见原因:

Service selector 写错 green Pod 未 Ready targetPort 错误 应用健康检查假成功 Ingress 缓存或上游配置未刷新

排查:

bash id="g18dtz"复制

kubectl get svc harness-demo-app -n prod -o yaml
kubectl get endpoints harness-demo-app -n prod
kubectl get pods -n prod -l app=harness-demo-app --show-labels

3. 蓝绿回滚后仍然访问新版本

常见原因:

Service selector 未真正切回 Ingress 指向了另一个 Service 客户端缓存 网关缓存 多个入口配置不一致

排查:

bash id="enxmno"复制

kubectl get ingress -n prod
kubectl describe ingress <ingress-name> -n prod
kubectl get svc -n prod

4. 金丝雀流量比例不准确

原因:

使用的是 Kubernetes Service selector 它不是权重流量控制器 流量只是在 endpoints 间转发 长连接会导致比例偏差

解决:

使用 Nginx Ingress canary 使用 Istio VirtualService 使用 Gateway API 使用专业灰度发布控制器


5. Prometheus 查询没有数据

常见原因:

应用没有暴露 /metrics Prometheus 没有 scrape 该服务 指标名和脚本不一致 label 不一致 Delegate 无法访问 Prometheus

排查:

bash id="lfu5t4"复制

curl http://prometheus.monitoring.svc:9090/api/v1/targets

curl -G http://prometheus.monitoring.svc:9090/api/v1/query \
  --data-urlencode 'query=up'

6. 异常阈值误判

常见原因:

流量太小,指标样本不足 查询窗口太短 指标延迟上报 新旧版本 label 混在一起 PromQL 只看了整体,不是 canary

建议:

设置最小观察时间 设置最小请求量 按 version / track label 区分 canary 不要只看单个瞬时指标


四十一:企业最佳实践

1. 发布策略分级

dev: Rolling

test: Rolling + 自动验证

pre: Blue-Green 或 Canary 预演

prod: Blue-Green / Canary + 审批 + 自动验证 + 回滚

2. 所有高级发布都必须有探针

必须配置:

readinessProbe livenessProbe resources requests/limits progressDeadlineSeconds

3. 生产禁止 latest

必须使用:

v1.0.0 release-20260618-001 commit sha

4. 金丝雀必须有监控指标

最低要求:

HTTP 5xx P95 延迟 Pod restart 健康检查成功率

进阶要求:

业务成功率 订单失败率 登录失败率 支付异常率 错误日志数 APM trace 异常

5. 数据库变更必须兼容

高级发布不是数据库回滚工具。

必须:

向前兼容 向后兼容 分阶段迁移 禁止不可逆强绑定

6. 回滚也要自动化

每种发布策略都要有:

rollback-rolling.sh rollback-bluegreen.sh rollback-canary.sh


四十二:练习 1:配置滚动发布

目标:

使用 Rolling Deployment 发布新版本。

步骤:

  1. 配置 maxSurge=1、maxUnavailable=0
  2. 部署 v1.0.0
  3. 部署 v1.1.0
  4. 观察 ReplicaSet 和 Pod 替换过程

命令:

bash id="q8ej07"复制

kubectl get rs -n prod -l app=harness-demo-app
kubectl get pods -n prod -l app=harness-demo-app -w

验收:

新 Pod Ready 后旧 Pod 逐步减少 Service 始终可访问


四十三:练习 2:配置蓝绿发布

目标:

blue 当前承载流量,green 部署新版本,验证后切流。

步骤:

  1. 部署 blue v1.0.0
  2. Service selector 指向 blue
  1. 部署 green v1.1.0
  2. 验证 green /health
  3. Service selector 切到 green
  4. 模拟异常后切回 blue

验收:

切流前 Service endpoints 是 blue Pod 切流后 Service endpoints 是 green Pod 回滚后 Service endpoints 回到 blue Pod


四十四:练习 3:配置金丝雀发布

目标:

stable 9 副本,canary 1 副本,近似 10% 流量。

步骤:

  1. 部署 stable v1.0.0 replicas=9
  2. 部署 canary v1.1.0 replicas=1
  3. Service selector 选择 app=harness-demo-app
  4. 访问服务并观察版本返回
  5. 指标正常后 Promote
  6. 指标异常后 Rollback

验收:

stable 和 canary 同时存在 Service endpoints 包含 stable 和 canary Pod Rollback 后 canary Deployment 被删除


四十五:练习 4:配置异常阈值自动回滚

目标:

错误率或 P95 超阈值时自动停止发布并回滚 canary。

步骤:

  1. 准备 Prometheus
  2. 修改 verify-metrics.sh 中 PromQL
  3. 设置 ERROR_RATE_THRESHOLD=0.01
  4. 设置 P95_THRESHOLD=0.5
  5. 制造错误接口或延迟
  6. 观察 Harness Step 失败
  7. 执行 rollback-canary.sh

验收:

指标正常时 Promote 指标异常时 Rollback Rollback 后 Service 只指向 stable


四十六:练习 5:使用 Nginx Ingress Canary 权重

目标:

使用 Ingress canary-weight 实现 10%、30%、50% 放量。

步骤:

  1. 准备 Nginx Ingress Controller
  2. 创建 stable Ingress
  3. 创建 canary Ingress
  4. 设置 canary-weight=10
  5. 验证指标
  6. 调整 canary-weight=30
  7. 验证指标
  8. 最终 Promote 或 Rollback

验收:

canary-weight 修改后流量比例变化 异常时可删除 canary Ingress 回滚


四十七:验收标准

完成本文后,应达到:

已理解 Rolling、Blue-Green、Canary 的区别 已能配置 Kubernetes Rolling Deployment 已能使用 Harness Rolling Deploy 已能编写蓝绿 Deployment 和 Service selector 已能完成蓝绿切流和回滚 已能使用 stable/canary Deployment 做基础金丝雀 已理解 Kubernetes Service 不能做精确权重流量 已了解 Nginx Ingress / Service Mesh 才能做精确权重 已能编写 Prometheus 异常阈值验证脚本 已能在指标异常时停止发布并回滚 已能为 dev/test/pre/prod 选择合适发布策略


四十八:本篇总结

本篇完成了 Harness 高级部署策略的系统讲解。

你需要重点记住:

滚动发布最简单,适合大多数无状态服务 蓝绿发布切换快、回退快,但资源成本高 金丝雀发布风险最低,但需要监控和流量治理能力 Kubernetes Service 原生不能做精确权重流量 精确金丝雀需要 Ingress、Service Mesh、Gateway 或专业流量控制组件 生产环境必须禁止 latest 高级发布必须配置 readinessProbe 和 livenessProbe 异常阈值不能只看 Pod Ready,还要看错误率、延迟和业务指标 数据库变更必须兼容蓝绿和金丝雀 失败策略必须配合 Rollback、Manual Intervention 和通知