蓝绿部署 / 金丝雀发布 / 滚动更新
滚动更新(Rolling Update)
原理
滚动更新是 Kubernetes Deployment 默认的更新策略,其核心思想是分批替换服务实例,逐步完成全量升级。在更新过程中,新旧版本的实例同时存在,但始终保证服务的整体可用性。
初始状态: 5 个实例全部运行 V1 版本
[V1] [V1] [V1] [V1] [V1]
Step 1: 启动 1 个 V2 实例,终止 1 个 V1 实例
[V2] [V1] [V1] [V1] [V1] (总实例数 5,可用实例 5)
Step 2: 启动 1 个 V2 实例,终止 1 个 V1 实例
[V2] [V2] [V1] [V1] [V1] (总实例数 5,可用实例 5)
Step 3: 启动 1 个 V2 实例,终止 1 个 V1 实例
[V2] [V2] [V2] [V1] [V1] (总实例数 5,可用实例 5)
Step 4: 启动 1 个 V2 实例,终止 1 个 V1 实例
[V2] [V2] [V2] [V2] [V1] (总实例数 5,可用实例 5)
完成: 全部升级为 V2
[V2] [V2] [V2] [V2] [V2]Deployment 滚动更新策略
Kubernetes 通过 strategy.rollingUpdate 下的两个核心参数控制滚动更新的行为。
参数说明
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
maxSurge | 数字或百分比 | 25% | 更新过程中最多允许超出期望副本数的实例数 |
maxUnavailable | 数字或百分比 | 25% | 更新过程中最多允许不可用的实例数 |
完整配置示例
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
namespace: production
labels:
app: user-service
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 3 # 最多允许超出 3 个实例(即最多同时 13 个实例运行)
maxUnavailable: 1 # 最多允许 1 个实例不可用(即至少 9 个实例可用)
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
version: v2
spec:
containers:
- name: user-service
image: registry.example.com/user-service:v2
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"参数组合行为图解
期望副本数 replicas = 10
case 1: maxSurge=3, maxUnavailable=1
- 最大总实例数 = 10 + 3 = 13
- 最小可用实例数 = 10 - 1 = 9
- 更新节奏:快速(允许大量 Surge)
case 2: maxSurge=1, maxUnavailable=1
- 最大总实例数 = 10 + 1 = 11
- 最小可用实例数 = 10 - 1 = 9
- 更新节奏:保守(每次只替换 1 个)
case 3: maxSurge=25%, maxUnavailable=25%
- 百分比基于 replicas 向上取整
- maxSurge = ceil(10 * 0.25) = 3
- maxUnavailable = ceil(10 * 0.25) = 3
- 更新节奏:均衡(默认配置)
case 4: maxSurge=0, maxUnavailable=1
- 先终止 1 个 V1 实例,再启动 1 个 V2 实例
- 最大总实例数 = 10(不额外占用资源)
- 更新节奏:最保守(适合资源受限环境)滚动更新中的探针作用
滚动更新依赖 readinessProbe(就绪探针) 来判断新 Pod 是否就绪。只有 readinessProbe 通过的 Pod 才会被加入 Service 的端点列表接收流量。
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10 # 容器启动后等待 10 秒再开始探测
periodSeconds: 5 # 每 5 秒探测一次
timeoutSeconds: 3 # 探测超时时间
successThreshold: 1 # 连续成功 1 次即为就绪
failureThreshold: 3 # 连续失败 3 次视为不就绪如果新版本的 readinessProbe 持续失败,Kubernetes 会停止滚动更新,保留现有实例继续服务。
更新暂停和恢复
暂停更新
在滚动更新过程中,可以暂停更新以检查新版本的状态:
# 暂停 Deployment 的滚动更新
kubectl rollout pause deployment/user-service
# 暂停后,新 Pod 继续运行,但不会继续替换旧 Pod
# 可以在这个阶段检查日志、指标等
kubectl logs -l app=user-service,version=v2
kubectl get pods -l app=user-service恢复更新
# 恢复暂停的滚动更新
kubectl rollout resume deployment/user-service
# 恢复后,Kubernetes 继续执行剩余的更新流程完整生命周期示例
# 1. 触发滚动更新(更新镜像)
kubectl set image deployment/user-service user-service=registry.example.com/user-service:v2
# 2. 查看更新状态
kubectl rollout status deployment/user-service
# 3. 暂停更新,观察新版本运行情况
kubectl rollout pause deployment/user-service
# 4. 检查新版本日志和指标
kubectl logs -l app=user-service,version=v2 --tail=100
# 5. 确认无误后恢复更新
kubectl rollout resume deployment/user-service
# 6. 等待更新完成
kubectl rollout status deployment/user-service回滚(Rollback)
回滚到上一个版本
# 回滚到上一个版本
kubectl rollout undo deployment/user-service
# 回滚后观察状态
kubectl rollout status deployment/user-service回滚到指定历史版本
# 查看发布历史记录
kubectl rollout history deployment/user-service
# 输出示例:
# deployment.apps/user-service
# REVISION CHANGE-CAUSE
# 1 <none>
# 2 <none>
# 3 kubectl set image deployment/user-service user-service=registry.example.com/user-service:v2
# 回滚到指定的 revision
kubectl rollout undo deployment/user-service --to-revision=1回滚原理
Kubernetes 通过 ReplicaSet 机制实现回滚。
Deployment
├── ReplicaSet v1 (revision=1) ── 旧版本 Pod 模板
│ └── Pod (V1)
│
├── ReplicaSet v2 (revision=2) ── 当前版本 Pod 模板 [回滚目标]
│ └── Pod (V2)
│
└── ReplicaSet v3 (revision=3) ── 新版本 Pod 模板 [当前回滚源]
└── Pod (V3)- 每次
kubectl apply或kubectl set image操作都会创建新的 ReplicaSet rollout undo的本质是将 Deployment 的 Pod 模板还原为指定 ReplicaSet 的模板revisionHistoryLimit控制保留的历史 ReplicaSet 数量,默认 10
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
revisionHistoryLimit: 5 # 只保留最近 5 个版本的 ReplicaSet
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0 # 零宕机更新:确保所有旧 Pod 都不被终止,直到新 Pod 就绪滚动更新过程图解
时间线 ────────────────────────────────────────────────────────>
初始状态 ┌──────┐
├─ Service 端点 │ V1 x5 │ 5 个 V1 Pod 全部运行
│ └──────┘
│
触发更新 ┌──────┐
├─ kubectl set image │ V1 x5 │ 创建 V2 Pod(启动中)
│ │ V2 x1 │
│ └──────┘
│
等待新 Pod 就绪 ┌──────┐
├─ readinessProbe 通过 │ V1 x5 │ V2 Pod 就绪,加入端点
│ │ V2 x1 │ V1 Pod 开始终止
│ └──────┘
│
逐步替换 ┌──────┐
├─ 循环执行 │ V1 x3 │ 每轮启动 1 个 V2
│ │ V2 x3 │ 终止 1 个 V1
│ └──────┘
│
更新完成 ┌──────┐
├─ 全部替换完毕 │ V2 x5 │ V1 Pod 全部终止
│ └──────┘蓝绿部署(Blue-Green Deployment)
概念
蓝绿部署的核心思想是同时维护两套完全独立的生产环境,分别称为"蓝环境"(当前稳定版本)和"绿环境"(新版本)。两套环境在部署过程中并存,通过流量切换实现版本升级。
┌─────────────────┐
│ Load Balancer │
│ (Ingress / SLB) │
└────────┬────────┘
│
┌───────────┴───────────┐
│ 切换 │
▼ ▼
┌────────────┐ ┌────────────┐
│ Blue Env │ │ Green Env │
│ (当前版本) │ │ (新版本) │
│ │ │ │
│ Service: │ │ Service: │
│ user-svc │ │ user-svc │
│ selector: │ │ selector: │
│ blue=true │ │ green=true │
└────────────┘ └────────────┘Service Selector 切换实现
蓝绿部署在 Kubernetes 中最简洁的实现方式是通过修改 Service 的 label selector,将流量从蓝环境切换到绿环境。
蓝环境 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-blue
namespace: production
spec:
replicas: 5
selector:
matchLabels:
app: user-service
release: blue
template:
metadata:
labels:
app: user-service
release: blue
spec:
containers:
- name: user-service
image: registry.example.com/user-service:v1
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5绿环境 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-green
namespace: production
spec:
replicas: 5
selector:
matchLabels:
app: user-service
release: green
template:
metadata:
labels:
app: user-service
release: green
spec:
containers:
- name: user-service
image: registry.example.com/user-service:v2
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
env:
- name: DB_HOST
value: "shared-database.cluster.local" # 共享数据库,Schema 必须向前兼容
- name: CACHE_CLUSTER
value: "redis-cluster:6379"Service 定义(切换前指向蓝环境)
apiVersion: v1
kind: Service
metadata:
name: user-service
namespace: production
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: user-service
release: blue # 当前指向蓝环境切换流量到绿环境
修改 Service 的 selector,将 release: blue 改为 release: green:
# 方式一:直接 edit Service
kubectl edit svc/user-service -n production
# 将 selector 中的 release: blue 改为 release: green
# 方式二:使用 patch 命令
kubectl patch svc/user-service -n production -p '{"spec":{"selector":{"app":"user-service","release":"green"}}}'
# 方式三:重新 apply 更新后的 YAML
kubectl apply -f service-green.yaml切换后的 Service 定义
apiVersion: v1
kind: Service
metadata:
name: user-service
namespace: production
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: user-service
release: green # 切换后指向绿环境Ingress 流量切换
除了修改 Service selector,还可以通过 Ingress 实现蓝绿流量切换,适合需要更细粒度控制的场景。
为蓝绿环境分别创建 Service
---
apiVersion: v1
kind: Service
metadata:
name: user-service-blue
namespace: production
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: user-service
release: blue
---
apiVersion: v1
kind: Service
metadata:
name: user-service-green
namespace: production
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: user-service
release: greenIngress 配置(切换前)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-service-ingress
namespace: production
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /api/user
pathType: Prefix
backend:
service:
name: user-service-blue # 指向蓝环境
port:
number: 80切换流量到绿环境
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-service-ingress
namespace: production
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /api/user
pathType: Prefix
backend:
service:
name: user-service-green # 切换为绿环境
port:
number: 80完整示例:蓝绿部署工作流程
# 1. 部署蓝环境(当前生产环境)
kubectl apply -f user-service-blue.yaml
# 2. 部署 Service 指向蓝环境
kubectl apply -f user-service-svc-blue.yaml
# 3. 部署绿环境(新版本),两套环境并行运行
kubectl apply -f user-service-green.yaml
# 4. 验证绿环境是否正常(内部访问测试)
kubectl run test-pod --image=curlimages/curl -it --rm --restart=Never -- \
curl http://user-service-green.production:80/actuator/health
# 5. 切换到绿环境(修改 Service selector 或 Ingress)
kubectl patch svc/user-service -n production -p \
'{"spec":{"selector":{"app":"user-service","release":"green"}}}'
# 6. 确认流量切换完成
kubectl get endpoints user-service -n production
# 输出应只包含绿色 Pod 的 IP 地址
# 7. 保留蓝环境一段时间作为回滚备用
# 蓝环境的 Deployment 无需删除,只需确保没有流量接入
# 8. 确认新版本稳定后,可清理蓝环境
kubectl delete deployment user-service-blue -n production优缺点
优点
- 回滚极快:切换和回滚都是秒级操作,只需修改 Service selector 或 Ingress 配置
- 环境完全隔离:新旧版本各自独立运行,互不干扰
- 部署过程对用户无感知:流量切换瞬间完成,无停机窗口
- 便于验证:新版本部署后可先在内部进行完整验证,再切换流量
缺点
- 资源成本翻倍:需要同时维护两套完整环境,硬件资源需求翻倍
- 数据库兼容性要求高:新旧版本共享数据库,Schema 必须向前兼容(旧字段不可删除,新增字段需有默认值)
- 有状态服务处理复杂:Session、缓存、WebSocket 连接等状态需要额外的迁移方案
- 环境数量有限:蓝绿两种颜色意味着只能同时维护两个版本,不适合需要同时运行多个版本的场景
金丝雀发布(Canary Release)
概念
金丝雀发布的核心思想是让一小部分用户先使用新版本,验证稳定性和业务指标后,逐步扩大流量比例,最终全量上线。
名称来源于煤矿工人用金丝雀检测矿井毒气的历史——金丝雀对一氧化碳非常敏感,如果金丝雀倒下,矿工就知道需要立即撤离。
┌──────────────────┐
│ Ingress / GW │
└────────┬─────────┘
│
┌──────────┴──────────┐
│ 流量分发规则 │
│ (权重 / Header) │
└──────────┬──────────┘
│
┌──────────────┴──────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ 稳定版本 V1 │ │ 金丝雀版本 V2 │
│ 90% 流量 │ │ 10% 流量 │
│ replicas: 9 │ │ replicas: 1 │
└──────────────────┘ └──────────────────┘Ingress 权重路由实现
基于 NGINX Ingress Controller 的权重路由
---
apiVersion: v1
kind: Service
metadata:
name: user-service-stable
namespace: production
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: user-service
version: stable
---
apiVersion: v1
kind: Service
metadata:
name: user-service-canary
namespace: production
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: user-service
version: canary
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-service-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10" # 10% 流量进入金丝雀版本
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /api/user
pathType: Prefix
backend:
service:
name: user-service-canary
port:
number: 80金丝雀发布——渐进式权重调整
# 阶段一:1% 流量(内部团队验证)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-service-ingress-canary
namespace: production
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "1"
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /api/user
pathType: Prefix
backend:
service:
name: user-service-canary
port:
number: 80# 阶段二:扩大到 10% 流量
kubectl annotate ingress user-service-ingress-canary \
nginx.ingress.kubernetes.io/canary-weight=10
# 阶段三:扩大到 50% 流量
kubectl annotate ingress user-service-ingress-canary \
nginx.ingress.kubernetes.io/canary-weight=50
# 阶段四:全量上线(移除 canary 注解,由主 Ingress 接管)
kubectl delete ingress user-service-ingress-canary
# 或者:将金丝雀副本数扩到生产规格,稳定版缩到 0
kubectl scale deployment user-service-canary --replicas=10
kubectl scale deployment user-service-stable --replicas=0
# 然后更新主 Ingress 指向 canary ServiceService 标签选择 + 副本比例控制
通过 Deployment 的副本数比例和 Service 的标签选择实现金丝雀发布,无需依赖 Ingress 的 canary 注解。
稳定版本 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-stable
namespace: production
spec:
replicas: 9 # 稳定版本 9 个副本
selector:
matchLabels:
app: user-service
track: stable
template:
metadata:
labels:
app: user-service
track: stable
spec:
containers:
- name: user-service
image: registry.example.com/user-service:v1
ports:
- containerPort: 8080金丝雀版本 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-canary
namespace: production
spec:
replicas: 1 # 金丝雀版本 1 个副本(约 10% 流量)
selector:
matchLabels:
app: user-service
track: canary
template:
metadata:
labels:
app: user-service
track: canary
spec:
containers:
- name: user-service
image: registry.example.com/user-service:v2
ports:
- containerPort: 8080统一 Service
apiVersion: v1
kind: Service
metadata:
name: user-service
namespace: production
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: user-service
# 注意:没有指定 track 标签,因此会匹配 stable 和 canary 两个版本的所有 Pod工作原理:Service 的 selector 只指定 app: user-service,不区分 track 标签。因此 user-service Service 会同时将流量分发到稳定版本和金丝雀版本的 Pod。副本比例为 9:1,流量大致按 90%:10% 分配。
渐进式流量放大
# 初始:9 个 stable + 1 个 canary => 10% 金丝雀流量
kubectl scale deployment user-service-canary --replicas=1
# 扩大:9 个 stable + 3 个 canary => 25% 金丝雀流量
kubectl scale deployment user-service-canary --replicas=3
# 均衡:5 个 stable + 5 个 canary => 50% 金丝雀流量
kubectl scale deployment user-service-stable --replicas=5
kubectl scale deployment user-service-canary --replicas=5
# 全量:0 个 stable + 10 个 canary => 100% 金丝雀流量(新版本全量上线)
kubectl scale deployment user-service-stable --replicas=0
kubectl scale deployment user-service-canary --replicas=10
# 升级 stable 的镜像更新为新版本,再扩容
kubectl set image deployment/user-service-stable user-service=registry.example.com/user-service:v2
kubectl scale deployment user-service-stable --replicas=10
# 清理 canary
kubectl scale deployment user-service-canary --replicas=0基于 Header 的金丝雀路由(NGINX Ingress)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-service-ingress-canary
namespace: production
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "X-Canary" # 匹配 Header 名称
nginx.ingress.kubernetes.io/canary-by-header-value: "canary-v2" # 匹配 Header 值
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /api/user
pathType: Prefix
backend:
service:
name: user-service-canary
port:
number: 80客户端请求示例:
# 普通请求 -> 路由到稳定版本
curl https://api.example.com/api/user/123
# 携带金丝雀 Header -> 路由到金丝雀版本
curl -H "X-Canary: canary-v2" https://api.example.com/api/user/123基于 Cookie 的金丝雀路由(NGINX Ingress)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-service-ingress-canary
namespace: production
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-cookie: "canary_user" # Cookie 名称
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /api/user
pathType: Prefix
backend:
service:
name: user-service-canary
port:
number: 80客户端请求示例:
# 普通请求 -> 路由到稳定版本
curl https://api.example.com/api/user/123
# 携带金丝雀 Cookie -> 路由到金丝雀版本
curl -b "canary_user=true" https://api.example.com/api/user/123Service Mesh(Istio)流量权重配置
使用 Istio 可以实现更精细的流量管理,无需修改 Service 的标签选择器。
定义子集
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: user-service
namespace: production
spec:
host: user-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 1024
maxRequestsPerConnection: 10
outlierDetection:
consecutive5xxErrors: 5
interval: 30s
baseEjectionTime: 60s
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2流量权重路由
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
namespace: production
spec:
hosts:
- user-service
http:
- route:
- destination:
host: user-service
subset: v1
weight: 90 # 90% 流量到 V1(稳定版本)
- destination:
host: user-service
subset: v2
weight: 10 # 10% 流量到 V2(金丝雀版本)
timeout: 30s
retries:
attempts: 3
perTryTimeout: 2s基于 Header 的精细化路由
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
namespace: production
spec:
hosts:
- user-service
http:
# 金丝雀用户(匹配 Header) -> 路由到 V2
- match:
- headers:
x-canary:
exact: "v2"
route:
- destination:
host: user-service
subset: v2
weight: 100
# 其他用户 -> 按权重分发
- route:
- destination:
host: user-service
subset: v1
weight: 90
- destination:
host: user-service
subset: v2
weight: 10渐进式发布策略
通过调整 VirtualService 中的 weight 值,实现渐进式发布。
# 阶段一:1% 金丝雀流量(内部验证)
http:
- route:
- destination:
host: user-service
subset: v1
weight: 99
- destination:
host: user-service
subset: v2
weight: 1
# 阶段二:10% 金丝雀流量(扩大验证)
http:
- route:
- destination:
host: user-service
subset: v1
weight: 90
- destination:
host: user-service
subset: v2
weight: 10
# 阶段三:50% 金丝雀流量(大规模验证)
http:
- route:
- destination:
host: user-service
subset: v1
weight: 50
- destination:
host: user-service
subset: v2
weight: 50
# 阶段四:100% 全量上线
http:
- route:
- destination:
host: user-service
subset: v2
weight: 100基于流量镜像的 Istio 配置
流量镜像(Traffic Mirroring)可以将请求复制一份发送到金丝雀版本,但不影响真实用户响应。
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
namespace: production
spec:
hosts:
- user-service
http:
- route:
- destination:
host: user-service
subset: v1
weight: 100
mirror:
host: user-service
subset: v2
mirrorPercentage:
value: 10.0 # 镜像 10% 的流量到 V2灰度发布(A/B Testing)
概念
A/B Testing 与金丝雀发布的区别主要在于目的不同:
| 维度 | 金丝雀发布 | A/B 测试 |
|---|---|---|
| 目的 | 验证稳定性和可靠性 | 验证业务效果和用户体验 |
| 评估指标 | 错误率、延迟、CPU/内存 | 转化率、点击率、留存率、收入 |
| 持续时间 | 短期(几小时到几天) | 长期(几天到几周) |
| 版本数量 | 通常 2 个 | 可多个变体(A/B/C/D) |
| 分流依据 | 权重或内部用户 | 用户分桶(地域/ID/设备) |
基于 HTTP Header 的路由
NGINX Ingress 实现
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-service-ingress-a
namespace: production
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "X-Ab-Version"
nginx.ingress.kubernetes.io/canary-by-header-value: "variant-a"
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /api/user
pathType: Prefix
backend:
service:
name: user-service-variant-a
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-service-ingress-b
namespace: production
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "X-Ab-Version"
nginx.ingress.kubernetes.io/canary-by-header-value: "variant-b"
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /api/user
pathType: Prefix
backend:
service:
name: user-service-variant-b
port:
number: 80客户端请求示例:
# 控制组(默认版本)
curl https://api.example.com/api/user/123
# 实验组 A
curl -H "X-Ab-Version: variant-a" https://api.example.com/api/user/123
# 实验组 B
curl -H "X-Ab-Version: variant-b" https://api.example.com/api/user/123基于 Cookie 的路由
NGINX Ingress 实现
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-service-canary-cookie
namespace: production
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-cookie: "ab_test_group" # Cookie 名称
# Cookie 值为 "variant-a" 时路由到金丝雀版本
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /api/user
pathType: Prefix
backend:
service:
name: user-service-variant-a
port:
number: 80Istio 实现 Header/Cookie 路由
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service-ab
namespace: production
spec:
hosts:
- user-service
http:
- match:
- headers:
x-ab-variant:
exact: "variant-a"
route:
- destination:
host: user-service
subset: variant-a
weight: 100
- match:
- headers:
x-ab-variant:
exact: "variant-b"
route:
- destination:
host: user-service
subset: variant-b
weight: 100
- match:
- headers:
cookie:
regex: "ab_test_group=(variant-a|variant-b)"
route:
- destination:
host: user-service
subset: variant-a
weight: 50
- destination:
host: user-service
subset: variant-b
weight: 50
- route:
- destination:
host: user-service
subset: control
weight: 100按比例分流 + 一致性哈希
在网关层对用户标识(如 user_id)做一致性哈希,确保同一用户始终进入同一实验组,保证用户体验的一致性。
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
namespace: production
spec:
hosts:
- user-service
http:
- match:
- sourceLabels:
app: api-gateway
# 使用用户 ID 的 hash 值进行分流
# Istio 不支持直接在 VirtualService 中做哈希,需要结合 EnvoyFilter
route:
- destination:
host: user-service
subset: control
weight: 80 # 80% 用户进入控制组
- destination:
host: user-service
subset: variant-a
weight: 10 # 10% 用户进入实验组 A
- destination:
host: user-service
subset: variant-b
weight: 10 # 10% 用户进入实验组 BIngress 结合权重 + Header 的完整示例
---
# 主 Ingress — 处理默认流量
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-service-main
namespace: production
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /api/user
pathType: Prefix
backend:
service:
name: user-service-default # 默认路由到控制组
port:
number: 80
---
# 金丝雀 Ingress — 匹配特定 Header 的实验组 A
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-service-canary-header
namespace: production
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "X-Ab-Version"
nginx.ingress.kubernetes.io/canary-by-header-value: "variant-a"
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /api/user
pathType: Prefix
backend:
service:
name: user-service-variant-a
port:
number: 80
---
# 金丝雀 Ingress — 匹配特定 Cookie 的实验组 B
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-service-canary-cookie
namespace: production
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-cookie: "ab_test_b"
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /api/user
pathType: Prefix
backend:
service:
name: user-service-variant-b
port:
number: 80对应 Deployment 和 Service 定义
---
# 控制组 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-control
namespace: production
spec:
replicas: 8
selector:
matchLabels:
app: user-service
variant: control
template:
metadata:
labels:
app: user-service
variant: control
spec:
containers:
- name: user-service
image: registry.example.com/user-service:v1
ports:
- containerPort: 8080
---
# 实验组 A Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-variant-a
namespace: production
spec:
replicas: 1
selector:
matchLabels:
app: user-service
variant: variant-a
template:
metadata:
labels:
app: user-service
variant: variant-a
spec:
containers:
- name: user-service
image: registry.example.com/user-service:v2-feature-x # 新功能
ports:
- containerPort: 8080
---
# 实验组 B Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-variant-b
namespace: production
spec:
replicas: 1
selector:
matchLabels:
app: user-service
variant: variant-b
template:
metadata:
labels:
app: user-service
variant: variant-b
spec:
containers:
- name: user-service
image: registry.example.com/user-service:v2-feature-y # 另一个新功能
ports:
- containerPort: 8080
---
# 各变体对应的 Service
apiVersion: v1
kind: Service
metadata:
name: user-service-default
namespace: production
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: user-service
variant: control
---
apiVersion: v1
kind: Service
metadata:
name: user-service-variant-a
namespace: production
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: user-service
variant: variant-a
---
apiVersion: v1
kind: Service
metadata:
name: user-service-variant-b
namespace: production
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: user-service
variant: variant-b对比总结表
| 维度 | 滚动更新 | 蓝绿部署 | 金丝雀发布 | 灰度发布(A/B 测试) |
|---|---|---|---|---|
| 适用场景 | 常规版本迭代、无状态微服务 | 关键基础设施、金融核心系统、数据库迁移 | 重大版本变更、高风险发布、新功能验证 | 功能体验验证、UI/UX 改版、业务策略测试 |
| 部署复杂度 | 低(K8s 原生支持,配置简单) | 中(需维护两套环境,自动化切换) | 中(需网关或服务网格配合) | 高(需实验平台、数据埋点、统计分析) |
| 回滚速度 | 分钟级(rollout undo,需重新拉取镜像) | 秒级(切换 Service selector 或 DNS) | 秒级(调整权重为 0 或删除 canary Ingress) | 无需回滚,关闭实验即可 |
| 资源成本 | 低(100%~200% 峰值的额外 Pod) | 高(需要 200% 环境资源) | 中(100% + 少量金丝雀实例) | 中(100% + 各变体实例) |
| 流量控制粒度 | 粗粒度(按实例比例) | 粗粒度(100% 切换) | 细粒度(权重/Header/Cookie) | 细粒度(用户分桶、一致性哈希) |
| 持续时间 | 分钟到小时 | 分钟到小时 | 小时到天 | 天到周 |
| 自动化程度 | 高(K8s Deployment 原生能力) | 中(需 CI/CD 配合切换脚本) | 高(可配合 Flagger/Argo Rollouts 自动化) | 高(需结合实验管理平台) |
| 可观测性要求 | 低(只需基础监控) | 中(需对比两套环境指标) | 高(需按版本维度区分监控指标) | 高(需完整埋点系统支持) |
| 数据库兼容性 | 需向前兼容 | 需双向兼容 | 需向前兼容 | 需向前兼容 |
| 典型工具 | K8s Deployment | K8s Service + Ingress, Jenkins | NGINX Ingress, Istio, Flagger | Istio, 自研网关, 实验平台 |
| 适用团队规模 | 所有团队 | 中大型团队(有运维能力) | 中小型团队起步 | 中型团队以上(有数据能力) |
选型决策流程
服务是否需要灰度验证?
├── 否 ──> 服务是否有状态?
│ ├── 是 ──> 蓝绿部署(需要处理 Session 亲和性)
│ └── 否 ──> 滚动更新(K8s 原生支持,最简单)
│
└── 是 ──> 验证目的是什么?
├── 稳定性验证(错误率、延迟)──> 金丝雀发布
├── 功能验证(指定用户)──────> 金丝雀发布(Header/Cookie 路由)
└── 业务效果验证(转化率)───> A/B 测试(需要埋点 + 统计分析)
金丝雀发布条件:
├── 有 Ingress Controller? ──> NGINX Ingress canary 注解
├── 有 Istio? ─────────────> VirtualService weight
└── 只有 K8s Service? ────> 副本比例控制(共用 Service selector)