软件 | 版本 |
---|---|
rancher | 2.5.11-ent |
k8s | 1.20.12 |
概述
使用Nginx Ingress实现灰度发布适用场景主要取决于业务流量切分的策略,目前Nginx Ingress支持基于Header、Cookie和服务权重三种流量切分的策略,基于这三种策略可实现以下两种发布场景:
• 场景一:切分部分用户流量到新版本
原应用:Service A
升级后的应用:Service A’(Header中包含foo=bar或者Cookie中包含foo=bar)
假设线上已运行了一套对外提供七层服务的Service A,此时需要发布上线一个新特性的版本Service A’,但又不想直接替换原有的Service A,而是期望将Header中包含foo=bar,或者Cookie中包含foo=bar的用户请求,转发到新版本Service A’中。待运行一段时间业务稳定后,再逐步升级到新版本,平滑下线旧版本。示意图如下:
• 场景二:切分一定比例的流量到新版本
假设线上已运行了一套对外提供七层服务的Service B,此时修复了一些问题,需要发布上线一个新的版本Service B’,但又不想直接替换原有的Service B,而是期望将20%的流量切换到新版本Service B’中。待运行一段时间业务稳定后,再将所有的流量从旧版本切换到新版本中,平滑下线旧版本。
注解说明
Nginx Ingress支持通过配置注解(Annotations)来实现不同场景下的发布和测试,可以满足灰度发布、蓝绿发布、A/B测试等业务场景。
具体实现过程如下:为服务创建两个Ingress,一个为常规Ingress,另一个为带nginx.ingress.kubernetes.io/canary: "true"注解的Ingress,称为Canary Ingress;
对Canary Ingress配置流量切分策略的Annotation,两个Ingress相互配合,即可实现多种场景的发布和测试。Nginx Ingress的Annotation支持以下几种规则:
• nginx.ingress.kubernetes.io/canary-by-header
基于Header的流量切分,适用于灰度发布。
如果请求头中包含指定的header名称,并且值为“always”,就将该请求转发给Canary Ingress定义的对应后端服务。如果值为“never”则不转发,可用于回滚到旧版本。
如果为其他值则忽略该annotation,并通过优先级将请求流量分配到其他规则。
• nginx.ingress.kubernetes.io/canary-by-header-value
必须与canary-by-header一起使用,可自定义请求头的取值,包含但不限于“always”或“never”。当请求头的值命中指定的自定义值时,请求将会转发给Canary Ingress定义的对应后端服务,如果是其他值则忽略该annotation,并通过优先级将请求流量分配到其他规则。
• nginx.ingress.kubernetes.io/canary-by-header-pattern
与canary-by-header-value类似,唯一区别是该annotation用正则表达式匹配请求头的值,而不是某一个固定值。如果该annotation与canary-by-header-value同时存在,该annotation将被忽略。
• nginx.ingress.kubernetes.io/canary-by-cookie
基于Cookie的流量切分,适用于灰度发布。
与canary-by-header类似,该annotation用于cookie,仅支持“always”和“never”,无法自定义取值。
• nginx.ingress.kubernetes.io/canary-weight
基于服务权重的流量切分,适用于蓝绿部署。
表示Canary Ingress所分配流量的百分比,取值范围[0-100]。例如,设置为100,表示所有流量都将转发给Canary Ingress对应的后端服务。
说明:
以上注解规则会按优先级进行评估,优先级为:canary-by-header -> canary-by-cookie -> canary-weight。
当Ingress被标记为Canary Ingress时,除了nginx.ingress.kubernetes.io/load-balance和nginx.ingress.kubernetes.io/upstream-hash-by外,所有其他非Canary的注解都将被忽略。
前提条件
使用Nginx Ingress实现灰度发布的集群,需安装nginx-ingress插件作为Ingress Controller,并且对外暴露统一的流量入口。Rancher企业版已集成。
资源创建方式
部署两个版本的服务
old-nginx为老版本
new-nginx为新版本,用于对old-nginx做升级测试
创建第一个版本的nginx:old-nginx
新建配置映射configmap,用于nginx的主页显示
选择 资源 菜单中的 配置映射 菜单项
指定配置映射的名称与命名空间
指定配置映射的键值,键为文件名,值为显示的内容,此处为old-nginx
Configmap对应的yaml
apiVersion: v1
data:
index.html: old-nginx
kind: ConfigMap
metadata:
annotations:
field.cattle.io/creatorId: user-k2shc
labels:
cattle.io/creator: norman
name: old-nginx
新建工作负载部署
工作负载的详细信息:
名称:old-nginx
镜像:nginx:latest
端口:80
副本数:1
发布方式:Nodeport
指定命名空间
并且指定上一步中创建的配置映射,挂载到/usr/share/nginx/html/
指定挂载模式644
选择指定的键
键的名称为:index.html
路径为:index.html
容器路径:/usr/share/nginx/html/
对工作负载添加标签(重要)
用于service与pod的关联筛选
标签为app=old-nginx
注意:生产环境时注意标签不可以与其他工作负载重复
增加cpu与内存的配额限制
部署对应的yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
field.cattle.io/creatorId: user-k2shc
labels:
cattle.io/creator: norman
workload.user.cattle.io/workloadselector: deployment-test-old-nginx
name: old-nginx
spec:
replicas: 1
selector:
matchLabels:
workload.user.cattle.io/workloadselector: deployment-test-old-nginx
template:
metadata:
labels:
app: old-nginx
workload.user.cattle.io/workloadselector: deployment-test-old-nginx
spec:
containers:
- image: nginx:latest
imagePullPolicy: Always
name: old-nginx
ports:
- containerPort: 80
name: 80tcp01
protocol: TCP
resources:
limits:
cpu: 100m
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
stdin: true
tty: true
volumeMounts:
- mountPath: /usr/share/nginx/html/
name: vol2
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
volumes:
- configMap:
defaultMode: 420
items:
- key: index.html
path: index.html
name: old-nginx
optional: false
name: vol2
新建svc
用于外部ingress访问的对应service
选择 资源 菜单中的工作负载菜单项,切换到服务发现
添加service,指定名称、命名空间
解析到选择pod
指定标签 app=old-nginx
注意:标签必须在工作负载中指定好并且不能与其他的工作负载重复
类型为:clusterIP或headless
端口映射指定为80
Svc对应的yaml
apiVersion: v1
kind: Service
metadata:
annotations:
field.cattle.io/creatorId: user-k2shc
field.cattle.io/ipAddresses: "null"
field.cattle.io/targetDnsRecordIds: "null"
field.cattle.io/targetWorkloadIds: "null"
labels:
cattle.io/creator: norman
name: old-nginx-svc
spec:
clusterIP: 10.43.219.43
clusterIPs:
- 10.43.219.43
ports:
- name: 80tcp2
port: 80
protocol: TCP
targetPort: 80
selector:
app: old-nginx
sessionAffinity: None
type: ClusterIP
创建第二个版本的nginx:new-nginx
新建配置映射configmap,用于nginx的主页显示
选择 资源 菜单中的 配置映射 菜单项
指定配置映射的名称与命名空间
指定配置映射的键值,键为文件名,值为显示的内容,此处为new-nginx
Configmap对应的yaml
apiVersion: v1
data:
index.html: new-nginx
kind: ConfigMap
metadata:
annotations:
field.cattle.io/creatorId: user-k2shc
labels:
cattle.io/creator: norman
name: new-nginx
新建工作负载部署
工作负载的详细信息:
名称:new-nginx
镜像:nginx:latest
端口:80
副本数:1
发布方式:Nodeport
指定命名空间
并且指定上一步中创建的配置映射,挂载到/usr/share/nginx/html/
指定挂载模式644
选择指定的键
键的名称为:index.html
路径为:index.html
容器路径:/usr/share/nginx/html/
对工作负载添加标签(重要)
用于service与pod的关联筛选
标签为app=new-nginx
注意:生产环境时注意标签不可以与其他工作负载重复
增加cpu与内存的配额限制
部署对应的yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
field.cattle.io/creatorId: user-k2shc
labels:
cattle.io/creator: norman
workload.user.cattle.io/workloadselector: deployment-test-new-nginx
name: new-nginx
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
workload.user.cattle.io/workloadselector: deployment-test-new-nginx
template:
metadata:
labels:
app: new-nginx
workload.user.cattle.io/workloadselector: deployment-test-new-nginx
spec:
containers:
- image: nginx:latest
imagePullPolicy: Always
name: new-nginx
ports:
- containerPort: 80
name: 80tcp01
protocol: TCP
resources:
limits:
cpu: 100m
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
stdin: true
tty: true
volumeMounts:
- mountPath: /usr/share/nginx/html/
name: vol1
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
volumes:
- configMap:
defaultMode: 420
items:
- key: index.html
path: index.html
name: new-nginx
optional: false
name: vol1
新建svc
用于外部ingress访问的对应service
选择 资源 菜单中的工作负载菜单项,切换到服务发现
添加service,指定名称、命名空间
解析到选择pod
指定标签 app=new-nginx
注意:标签必须在工作负载中指定好并且不能与其他的工作负载重复
类型为:clusterIP或headless
端口映射指定为80
Svc对应的yaml
apiVersion: v1
kind: Service
metadata:
annotations:
field.cattle.io/creatorId: user-k2shc
field.cattle.io/ipAddresses: "null"
field.cattle.io/targetDnsRecordIds: "null"
field.cattle.io/targetWorkloadIds: "null"
labels:
cattle.io/creator: norman
name: new-nginx-svc
spec:
clusterIP: 10.43.55.151
clusterIPs:
- 10.43.55.151
ports:
- name: 80tcp2
port: 80
protocol: TCP
targetPort: 80
selector:
app: new-nginx
sessionAffinity: None
type: ClusterIP
使用nodeport方式访问测试
切换到工作负载查看nodeport端口
打开浏览器输入edge节点的IP与old-nginx nodeport端口验证业务的可用
打开浏览器输入edge节点的IP与new-nginx nodeport端口验证业务的可用
灰度发布新版本服务
创建Ingress,对外暴露服务,指向old版本的服务
新建ingress
用于外部用户的http访问
切换到资源菜单,选择工作负载,切换到负载均衡选项
添加规则
名称:old-nginx
选中自定义域名,并指定访问域名,域名为dns能解析的A记录
选择服务,访问路径为/, 服务名称指定为old-nginx-svc,端口为80
注释为:
kubernetes.io/ingress.class=nginx
kubernetes.io/elb.port='80'
Ingress对应的yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
field.cattle.io/creatorId: user-k2shc
kubernetes.io/elb.port: "80"
kubernetes.io/ingress.class: nginx
labels:
cattle.io/creator: norman
name: old-nginx
spec:
rules:
- host: www.demo-test.com
http:
paths:
- backend:
serviceName: old-nginx-svc
servicePort: 80
path: /
pathType: ImplementationSpecific
命令行验证old 版本的ingress可用性
命令:curl -H “Host: <ingress对应的域名>” http://< Ingress对外暴露的IP>
基于Header的流量切分 (注意: Canary Ingress的优先级)
以下示例仅模拟灰度新版本给北京和广州地域的用户,Header中包含Region且值为bj或gz的请求才能转发到新版本服务。
新建ingress
用于外部用户的http访问
切换到资源菜单,选择工作负载,切换到负载均衡选项
添加规则
名称:new-nginx-header
选中自定义域名,并指定访问域名,域名为dns能解析的A记录
选择服务,访问路径为/, 服务名称指定为new-nginx-svc,端口为80
注释为:
kubernetes.io/ingress.class=nginx
kubernetes.io/elb.port='80'
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "Region"
nginx.ingress.kubernetes.io/canary-by-header-pattern: "bj|gz"
Ingress对应的yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
field.cattle.io/creatorId: user-k2shc
kubernetes.io/elb.port: "80"
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: Region
nginx.ingress.kubernetes.io/canary-by-header-pattern: bj|gz
labels:
cattle.io/creator: norman
name: new-nginx-header
spec:
rules:
- host: www.demo-test.com
http:
paths:
- backend:
serviceName: new-nginx-svc
servicePort: 80
path: /
pathType: ImplementationSpecific
验证访问测试
命令:curl -H “Host: <ingress对应的域名>” -H “Region: bj/gz” http:// < Ingress对外暴露的IP>
基于Cookie的流量切分 (注意:Canary Ingress的优先级)
以下示例仅Cookie中包含user_from_bj的请求才能转发到新版本服务,模拟灰度新版本给北京地域的用户。
删除原有的ingress
新建ingress
用于外部用户的http访问
切换到资源菜单,选择工作负载,切换到负载均衡选项
添加规则
名称:new-nginx-cookie
选中自定义域名,并指定访问域名,域名为dns能解析的A记录
选择服务,访问路径为/, 服务名称指定为new-nginx-svc,端口为80
注释为:
kubernetes.io/ingress.class=nginx
kubernetes.io/elb.port='80'
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-cookie: user_from_bj
Ingress对应的yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
field.cattle.io/creatorId: user-k2shc
kubernetes.io/elb.port: "80"
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-cookie: user_from_bj
labels:
cattle.io/creator: norman
name: new-nginx-cookie
spec:
rules:
- host: www.demo-test.com
http:
paths:
- backend:
serviceName: new-nginx-svc
servicePort: 80
path: /
pathType: ImplementationSpecific
验证访问测试
命令:curl -H “Host: <ingress对应的域名>” --cookie “user_from_bj=always” http:// < Ingress对外暴露的IP>
基于服务权重的流量切分 (注意:Canary Ingress的优先级)
仅允许20%的流量被转发到新版本服务中,实现灰度发布。
删除原有的ingress
新建ingress
用于外部用户的http访问
切换到资源菜单,选择工作负载,切换到负载均衡选项
添加规则
名称:new-nginx-weight20
选中自定义域名,并指定访问域名,域名为dns能解析的A记录
选择服务,访问路径为/, 服务名称指定为new-nginx-svc,端口为80
注释为:
kubernetes.io/ingress.class=nginx
kubernetes.io/elb.port='80'
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "20"
Ingress对应的yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
field.cattle.io/creatorId: user-k2shc
kubernetes.io/elb.port: "80"
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "20"
labels:
cattle.io/creator: norman
name: new-nginx-weight20
spec:
rules:
- host: www.demo-test.com
http:
paths:
- backend:
serviceName: new-nginx-svc
servicePort: 80
path: /
pathType: ImplementationSpecific
验证访问测试
命令:for i in {1…20}; do curl -H “Host: <ingress对应的域名>” http:// < Ingress对外暴露的IP>; echo; done;
允许所有的流量被转发到新版本服务中,实现蓝绿发布
删除原有的ingress
新建ingress
用于外部用户的http访问
切换到资源菜单,选择工作负载,切换到负载均衡选项
添加规则
名称:new-nginx-weight100
选中自定义域名,并指定访问域名,域名为dns能解析的A记录
选择服务,访问路径为/, 服务名称指定为new-nginx-svc,端口为80
注释为:
kubernetes.io/ingress.class=nginx
kubernetes.io/elb.port='80'
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "100"
Ingress对应的yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
field.cattle.io/creatorId: user-k2shc
kubernetes.io/elb.port: "80"
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "100"
labels:
cattle.io/creator: norman
name: new-nginx-weight100
spec:
rules:
- host: www.demo-test.com
http:
paths:
- backend:
serviceName: new-nginx-svc
servicePort: 80
path: /
pathType: ImplementationSpecific
验证访问测试
命令:for i in {1…20}; do curl -H “Host: <ingress对应的域名>” http:// < Ingress对外暴露的IP>;echo ; done;