J.V.'s Blog

基于Nginx Ingress实现灰度发布

本文详细介绍如何使用Nginx Ingress Controller的Canary机制实现灰度发布和蓝绿发布,包括基于请求头和基于权重两种方式。

什么是灰度发布和蓝绿发布?

简单来说,就是不要一次性把所有流量都切到新版本,而是先让一小部分用户试用新版本,观察一段时间没问题了再逐步放量。

这样做的好处很明显:

Nginx Ingress的Canary机制

Nginx Ingress Controller提供了一套Canary(金丝雀)机制来实现灰度发布,主要通过几个annotation来控制:

这几种方式的优先级是:canary-by-header > canary-by-cookie > canary-weight

需要注意的是,一个Ingress规则只能对应一个Canary Ingress,配置多个的话只有第一个会生效。另外,Canary Ingress必须与普通Ingress具有相同的host和path规则才能生效。

灰度发布流程图

我画了一张图展示了基于Nginx Ingress实现灰度发布的完整流程:

灰度发布流程图

从图中可以看出,灰度发布主要包括以下几个关键步骤:

  1. 部署老版本服务并创建普通Ingress规则
  2. 部署新版本服务
  3. 创建Canary Ingress规则,配置灰度策略(基于请求头、cookie或权重)
  4. 逐步调整流量分配比例
  5. 完全切换到新版本并清理资源

实战

下面我们用一个实际例子来演示整个流程。假设我们有一个Nginx服务要从老版本升级到新版本。

第一步:部署老版本服务

首先把现有的老版本服务部署起来,这个比较简单。

创建 old-nginx-deployment-and-service.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: old-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      run: old-nginx
  template:
    metadata:
      labels:
        run: old-nginx
    spec:
      containers:
      - image: my-nginx:old  # 老版本镜像
        imagePullPolicy: IfNotPresent
        name: old-nginx
        ports:
        - containerPort: 80
          protocol: TCP
      restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: old-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    run: old-nginx # 指向老版本Deployment
  sessionAffinity: None
  type: NodePort

然后创建Ingress规则 ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: gray-release
spec:
  ingressClassName: nginx
  rules:
  - host: www.example.com
    http:
      paths:
      - path: /
        backend:
          service: 
            name: old-nginx # 指向老版本服务
            port:
              number: 80
        pathType: ImplementationSpecific

执行部署命令:

kubectl apply -f old-nginx-deployment-and-service.yaml
kubectl apply -f ingress.yaml

测试一下是否正常:

# 获取Ingress的外部IP
kubectl get ingress

# 访问服务
curl -H "Host: www.example.com" http://<EXTERNAL_IP>

如果能看到nginx的欢迎页面,说明老版本服务已经正常运行了。

第二步:部署新版本服务

现在我们要上线新版本了,先把新版本的服务部署起来。

创建 new-nginx-deployment-and-service.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: new-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      run: new-nginx
  template:
    metadata:
      labels:
        run: new-nginx
    spec:
      containers:
      - image: my-nginx:new  # 新版本镜像
        imagePullPolicy: IfNotPresent
        name: new-nginx
        ports:
        - containerPort: 80
          protocol: TCP
      restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: new-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    run: new-nginx # 指向新版本Deployment
  sessionAffinity: None
  type: NodePort

部署新版本:

kubectl apply -f new-nginx-deployment-and-service.yaml

这时候新老版本都在运行,但是流量还都在老版本上。

第三步:配置灰度规则

接下来是关键步骤,我们要配置灰度规则。这里有两种常用的方式:

方式一:基于请求头的灰度(适合灰度发布以及AB测试场景)

这种方式适合给特定用户开放新功能,比如内部测试人员或者VIP用户。

创建 ingress-canary-rule1.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: gray-release-canary
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-by-header: "foo"
    nginx.ingress.kubernetes.io/canary-by-header-value: "bar"
spec:
  ingressClassName: nginx
  rules:
  - host: www.example.com
    http:
      paths:
      - path: /
        backend:
          service: 
            name: new-nginx # 指向新版本服务
            port:
              number: 80
        pathType: ImplementationSpecific

应用配置:

kubectl apply -f ingress-canary-rule1.yaml

测试效果:

# 普通请求,还是访问老版本
curl -H "Host: www.example.com" http://<EXTERNAL_IP>

# 带上特定请求头,访问新版本
curl -H "Host: www.example.com" -H "foo: bar" http://<EXTERNAL_IP>

这样就实现了精准的流量控制,只有带特定请求头的请求才会路由到新版本。实际使用中,可以在客户端给测试用户的请求加上这个header,或者用 X-Canary-Version 这种更语义化的header名。

除了基于请求头,还可以使用基于cookie的方式,只需将annotation改为:

nginx.ingress.kubernetes.io/canary-by-cookie: "canary"

这样当请求中包含名为canary的cookie且值为always时,请求会被路由到Canary版本。

方式二:基于权重的灰度(适合蓝绿发布)

这种方式更常用,按百分比逐步放量。

创建 ingress-canary-rule2.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: gray-release-canary
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "50"
spec:
  ingressClassName: nginx
  rules:
  - host: www.example.com
    http:
      paths:
      - path: /
        backend:
          service: 
            name: new-nginx # 指向新版本服务
            port:
              number: 80
        pathType: ImplementationSpecific

应用配置:

kubectl apply -f ingress-canary-rule2.yaml

测试效果:

# 多次执行这个命令
curl -H "Host: www.example.com" http://<EXTERNAL_IP>

你会发现大概一半的请求会路由到新版本,一半还在老版本,这就是50%的流量分配。

实际操作中,可以这样逐步放量:

  1. 先设置 canary-weight: "10" 给新版本10%流量,观察一段时间
  2. 没问题就改成 canary-weight: "30",继续观察
  3. 再改成 canary-weight: "50"
  4. 最后改成 canary-weight: "100",或者直接进入下一步

第四步:完全切换到新版本

经过一段时间的观察,新版本运行稳定,没有出现问题,现在可以完全切换到新版本了。

这里有个小技巧:不是直接删除老版本,而是让老版本的Service指向新版本的Deployment。这样做的好处是,如果后面发现问题,可以快速回滚。

修改 old-nginx-deployment-and-service.yaml 中的Service部分:

apiVersion: v1
kind: Service
metadata:
  name: old-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    run: new-nginx  # 改成指向新版本Deployment
  sessionAffinity: None
  type: NodePort

应用修改:

kubectl apply -f old-nginx-deployment-and-service.yaml

验证一下:

curl -H "Host: www.example.com" http://<EXTERNAL_IP>
# 现在应该全部路由到新版本了

确认没问题后,就可以清理资源了:

# 删除Canary Ingress
kubectl delete ingress gray-release-canary

# 删除老版本的Deployment
kubectl delete deploy old-nginx

# 删除新版本的Service(因为现在用的是old-nginx这个Service)
kubectl delete svc new-nginx

最佳实践提示:在生产环境中,建议保留老版本的Deployment一段时间(比如一周),并将副本数设置为0,这样如果新版本出现严重问题,可以快速恢复老版本,而不需要重新构建镜像。

至此,整个灰度发布流程就完成了。

实际使用中的一些注意事项

  1. 监控很重要:在灰度过程中,一定要密切关注监控指标,比如错误率、响应时间、CPU和内存使用情况等。

  2. 逐步放量:不要一上来就给50%流量,建议从5%或10%开始,每次翻倍,比如 5% -> 10% -> 25% -> 50% -> 100%。

  3. 设置观察期:每次调整权重后,至少观察15-30分钟,确保没有问题再继续。

  4. 准备回滚方案:虽然灰度发布已经很安全了,但还是要准备好快速回滚的方案。最简单的方法就是把Canary Ingress删掉,流量就全部回到老版本了。

  5. 日志和告警:配置好日志收集和告警规则,一旦出现异常能第一时间发现。

  6. Canary规则注意事项

    • Canary Ingress必须与普通Ingress具有相同的host和path规则
    • 一个普通Ingress只能对应一个Canary Ingress
    • Canary Ingress的创建时间必须晚于普通Ingress
    • 建议给Canary Ingress添加明确的标签,方便管理

总结

使用Nginx Ingress Controller实现灰度发布和蓝绿发布其实并不复杂,核心就是利用Canary机制:

通过合理配置Canary规则,我们可以实现平滑的应用升级,最大程度降低新版本上线风险,提升用户体验。在实际生产环境中,建议结合监控、日志和告警系统,形成完整的发布流程,确保应用的高可用性和稳定性。

#k8s #开发