以前公司的部署流程极其粗暴,每次发版都是直接把老 Pod 缩容,新 Pod 起来,美其名曰“全量发布”。直到有一次,新版本里由于硬编码引入了一个数据库连接池泄漏的问题,一上线核心业务接口直接大面积报错。更痛苦的是,因为镜像太大,回滚老版本需要足足五分钟。在这漫长的五分钟里,客服电话被打爆,老板在身后疯狂踱步,那场面简直是一场噩梦。
发版一旦出错无法瞬间回滚,对线上业务的伤害是致命的。为了规避这个大坑,团队下决心改造了 CI/CD 调用链,基于 Kubernetes Ingress Controller(Nginx)的 Canary 机制实现了灰度和蓝绿发布。
Nginx Ingress 提供了一组特别好用的 Annotation。通过配置 nginx.ingress.kubernetes.io/canary 以及相关的控制参数,我们不需要去写复杂的 Service Mesh(如 Istio)配置,就能极其轻量地实现按 Request Header、Cookie 或者权重比例进行流量切分。
下面是我们在生产环境落地的一个灰度发布 YAML 配置文件。它定义了生产的主 Service 以及金丝雀(Canary)版本的 Ingress 规则,将 10% 的流量平滑切分给新版本进行观测:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-production
namespace: default
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: api.ourproduct.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-service-stable
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-canary
namespace: default
annotations:
kubernetes.io/ingress.class: nginx
# 开启 Canary 灰度发布机制
nginx.ingress.kubernetes.io/canary: "true"
# 按百分比切分流量,这里设置 10% 的流量导入到 canary 版本
nginx.ingress.kubernetes.io/canary-by-header: "X-Canary"
nginx.ingress.kubernetes.io/canary-by-header-value: "always"
nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
rules:
- host: api.ourproduct.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-service-canary
port:
number: 80
在这套方案中,我们配置了两种灰度策略的混合使用:一是当请求头中携带 X-Canary: always 时,强行将流量路由到 Canary 服务(用于开发和测试人员在线上进行验证);二是如果请求头不带特殊标识,则由 Ingress 负载均衡器将大约 10% 的随机流量分流到 Canary 服务。
通过这套机制,发版时的心智负担骤减。我们在 CI/CD 流程中设置了自动化流水线:先部署 Canary 版本并切入 5% 流量,通过 Prometheus 监控其 5xx 状态码和延迟指标;十分钟内无异常则自动将权重提升至 30%、50%,最后把稳定版的镜像替换为新版本,彻底关闭 Canary。
但在实际折腾过程中也踩过大坑,例如 Canary 版本的 Ingress 规则如果和 Stable 版本的 Path、Host 不一致,Ingress-Controller 会直接报错忽略 Canary 配置。
大家在 Kubernetes 中做流量切分时,遇到过跨 Session 或 Cookie 保持的灰度需求吗?你们又是怎么解决 Ingress 级别流量倾斜导致的上游服务不均问题的?