一:教程定位
在前 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:新版本部署环境
发布流程:
- 当前 Service 指向 blue
- 部署 green 新版本
- 验证 green 的健康检查和指标
- 验证通过后,将 Service selector 切到 green
- 如果 green 异常,将 Service selector 切回 blue
- 下一次发布时,角色互换
优点:
切换快 回退快 新版本可在不接生产流量时先验证 接近零停机
缺点:
需要同时运行两套资源 资源成本更高 数据库变更必须兼容 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 发布新版本。
步骤:
- 配置 maxSurge=1、maxUnavailable=0
- 部署 v1.0.0
- 部署 v1.1.0
- 观察 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 部署新版本,验证后切流。
步骤:
- 部署 blue v1.0.0
- Service selector 指向 blue
- 部署 green v1.1.0
- 验证 green /health
- Service selector 切到 green
- 模拟异常后切回 blue
验收:
切流前 Service endpoints 是 blue Pod 切流后 Service endpoints 是 green Pod 回滚后 Service endpoints 回到 blue Pod
四十四:练习 3:配置金丝雀发布
目标:
stable 9 副本,canary 1 副本,近似 10% 流量。
步骤:
- 部署 stable v1.0.0 replicas=9
- 部署 canary v1.1.0 replicas=1
- Service selector 选择 app=harness-demo-app
- 访问服务并观察版本返回
- 指标正常后 Promote
- 指标异常后 Rollback
验收:
stable 和 canary 同时存在 Service endpoints 包含 stable 和 canary Pod Rollback 后 canary Deployment 被删除
四十五:练习 4:配置异常阈值自动回滚
目标:
错误率或 P95 超阈值时自动停止发布并回滚 canary。
步骤:
- 准备 Prometheus
- 修改 verify-metrics.sh 中 PromQL
- 设置 ERROR_RATE_THRESHOLD=0.01
- 设置 P95_THRESHOLD=0.5
- 制造错误接口或延迟
- 观察 Harness Step 失败
- 执行 rollback-canary.sh
验收:
指标正常时 Promote 指标异常时 Rollback Rollback 后 Service 只指向 stable
四十六:练习 5:使用 Nginx Ingress Canary 权重
目标:
使用 Ingress canary-weight 实现 10%、30%、50% 放量。
步骤:
- 准备 Nginx Ingress Controller
- 创建 stable Ingress
- 创建 canary Ingress
- 设置 canary-weight=10
- 验证指标
- 调整 canary-weight=30
- 验证指标
- 最终 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 和通知