Nginx Ingress可以通过业务流量切分的方式,实现应用的灰度/蓝绿发布。Nginx Ingress支持三种流量切分策略,分别是基于Header、基于Cookie和基于服务权重。这三种策略可以归纳为两种场景:
- 将匹配的业务流量切分到新版本
- 将指定比例的流量切分到新版本
接下来本文将按照Nginx Ingress配置说明 、前提条件、应用场景和操作步骤的顺序进行介绍。
Nginx Ingress配置说明
为实现灰度/蓝绿发布,应用前提:
- 配置两个Ingress资源:一个为常规Ingress,一个为Canary Ingress。这两个Ingress通过域名(host)和路径(path)关联。Canary Ingress需带有注解nginx.ingress.kubernetes.io/canary: "true"。
- 通过注解为Canary Ingress配置流量切分策略,将流量路由到Canary Ingress定义的Service上,即可实现灰度发布、蓝绿发布、A/B测试等各种业务场景。
Nginx Ingress支持的流量切分策略注解如下:
- nginx.ingress.kubernetes.io/canary-by-header
基于Header切分流量,用于灰度发布。如果请求头中包含指定的header key,且值为“always”,则将请求切分到Canary Ingress定义的Service上。如果值为“never”,则将请求转发到常规Ingress定义的Service上。如果为其他值,则忽略该注解规则,并通过优先级将请求流量分配到其他规则。
- nginx.ingress.kubernetes.io/canary-by-header-value
与 canary-by-header 一起使用,可自定义header value。如果请求头命中自定义值,则将请求切分到Canary Ingress定义的Service上。如果为其他值,则忽略该注解规则,并通过优先级将请求流量分配到其他规则。
- nginx.ingress.kubernetes.io/canary-by-header-pattern
与 canary-by-header-value 工作方式类似,支持通过正则表达式匹配header value。请注意,当设置了 canary-by-header-value 时,此注解规则将被忽略。
- nginx.ingress.kubernetes.io/canary-by-cookie
基于Cookie切分流量,用于灰度发布,与canary-by-header相似。Cookie value仅支持“always”和“never”。
- nginx.ingress.kubernetes.io/canary-weight
基于服务权重切分流量,用于灰度发布或蓝绿发布。权重取值范围为[0-100],表示Canary Ingress所切分到的流量百分比。
以上策略的优先级顺序为: canary-by-header > canary-by-cookie > canary-weight 。更多内容请参阅官方文档Annotations。
前提条件
1、在集群中安装Nginx Ingress插件,作为Ingress Controller,并通过Nginx对外暴露统一的流量入口。详细操作可参考 安装插件。
2、上传Nginx镜像至容器镜像服务,使用Nginx作为demo应用。
3、使用Nginx部署应用Service v1,为方便观测流量切分的效果,将欢迎页设置为“v1”。关键配置见下步。
应用场景和操作步骤
场景一:将匹配的业务流量切分到新版本
应用运行了一套对外提供7层服务的Service v1,现需发布新版本Service v2。应用希望将header包含“version=v2”或Cookie包含“v2=always”的流量灰度到Service v2,待稳定运行后,逐步全量切到Service v2,平滑下线Service v1。示意图如下:
步骤1:部署旧版本Service v1和常规Ingresss
- 创建配置项(ConfigMap)gray-v1
- 部署无状态应用(Deployment)gray-v1-deploy
配置数据卷:
配置镜像和挂载卷:
访问配置:
- 创建常规Ingress gray-v1-ing
apiVersion: "networking.k8s.io/v1"
kind: "Ingress"
metadata:
annotations:
kubernetes.io/ingress.class: "nginx-ingress-controller" # 使用Nginx型Ingress
name: "gray-v1-ing"
namespace: "gray-test"
spec:
rules:
- host: "test-gray.com" # 域名
http:
paths:
- backend:
service:
name: "gray-v1-deploy" # 指定后端服务为gray-v1-deploy
port:
number: 80
path: "/" # 路径
pathType: "Prefix"
步骤2:部署新版本Service v2
- 创建配置项(ConfigMap)gray-v2
- 部署无状态应用(Deployment)gray-v2-deploy
配置数据卷:
配置镜像和挂载卷:
访问配置:
步骤3:灰度发布新版本
- 基于Header创建Canary Ingress gray-v2-ing
apiVersion: "networking.k8s.io/v1"
kind: "Ingress"
metadata:
annotations:
kubernetes.io/ingress.class: "nginx-ingress-controller"
nginx.ingress.kubernetes.io/canary: "true" # 启用Canary
nginx.ingress.kubernetes.io/canary-by-header: "version"
nginx.ingress.kubernetes.io/canary-by-header-value: "v2" # Header中包含version且值为v2的请求转发到此Canary Ingress
labels:
ingressName: "gray-v1-ing"
ingressNamespace: "gray-test"
name: "gray-v2-ing"
namespace: "gray-test"
spec:
rules:
- host: "test-gray.com" # 域名
http:
paths:
- backend:
service:
name: "gray-v2-deploy" # 指定后端服务为gray-v2-deploy
port:
number: 80
path: "/" # 路径
pathType: "Prefix"
执行命令进行访问测试:
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com'
v1
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com' -H 'version: v2'
v2
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com'
v1
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com' -H 'version: v2'
v2
其中,<EXTERNAL_IP>为Nginx Ingress对外暴露的IP。可以看出,仅当Header中包含version且值为v2的流量已切分到新版本服务。
- 基于Cookie创建Canary Ingress gray-v2-ing
apiVersion: "networking.k8s.io/v1"
kind: "Ingress"
metadata:
annotations:
kubernetes.io/ingress.class: "nginx-ingress-controller"
nginx.ingress.kubernetes.io/canary: "true" # 启用Canary
nginx.ingress.kubernetes.io/canary-by-cookie: "version" # Cookie中包含version且值为alway的请求转发到此Canary Ingress
labels:
ingressName: "gray-v1-ing"
ingressNamespace: "gray-test"
name: "gray-v2-ing"
namespace: "gray-test"
spec:
rules:
- host: "test-gray.com" # 域名
http:
paths:
- backend:
service:
name: "gray-v2-deploy" # 指定后端服务为gray-v2-deploy
port:
number: 80
path: "/" # 路径
pathType: "Prefix"
执行命令进行访问测试:
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com'
v1
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com' --cookie 'version=always'
v2
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com'
v1
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com' --cookie 'version=always'
v2
其中,<EXTERNAL_IP>为Nginx Ingress对外暴露的IP。可以看出,仅当Cookie中包含version且值为always的流量已切分到新版本服务。
步骤4:下线旧版本Service v1
- 将常规Ingress gray-v1-ing 的服务名称改为gray-v2-deploy
- 删除Canary Ingress gray-v2-ing
- 删除旧无状态应用 gray-v1-deploy 和配置项 gray-v1
场景二:将一定比例的流量切分到新版本
应用运行了一套对外提供7层服务的Service v1,现需发布新版本Service v2。应用希望将20%的流量灰度到Service v2,待稳定运行后,逐步全量切到Service v2,平滑下线Service v1。示意图如下:
步骤1:部署旧版本Service v1和常规Ingresss
同 “场景一:将匹配的业务流量切分到新版本”
步骤2:部署新版本Service v2
同 “场景一:将匹配的业务流量切分到新版本”
步骤3:灰度发布新版本
基于服务权重创建Canary Ingress gray-v2-ing
apiVersion: "networking.k8s.io/v1"
kind: "Ingress"
metadata:
annotations:
kubernetes.io/ingress.class: "nginx-ingress-controller"
nginx.ingress.kubernetes.io/canary: "true" # 启用Canary
nginx.ingress.kubernetes.io/canary-weight: "50" # 50%的请求转发到此Canary Ingress
labels:
ingressName: "gray-v1-ing"
ingressNamespace: "gray-test"
name: "gray-v2-ing"
namespace: "gray-test"
spec:
rules:
- host: "test-gray.com" # 域名
http:
paths:
- backend:
service:
name: "gray-v2-deploy" # 指定后端服务为gray-v2-deploy
port:
number: 80
path: "/" # 路径
pathType: "Prefix"
执行命令进行访问测试:
# for i in {1..10}; do curl http://<EXTERNAL_IP> -H 'Host: test-gray.com'; done;
v2
v2
v2
v2
v1
v1
v1
v2
v1
v2
其中,<EXTERNAL_IP>为Nginx Ingress对外暴露的IP。可以看出,有近50%的流量切分到新版本服务,当请求的数量越多时比例会越接近50%。
步骤4:下线旧版本Service v1
- 将常规Ingress gray-v1-ing 的服务名称改为gray-v2-deploy
- 删除Canary Ingress gray-v2-ing
- 删除旧无状态应用 gray-v1-deploy 和配置项 gray-v1