前言
上篇文章我们介绍了如何通过service来原生实现简单的灰度/蓝绿发布,这篇文章就来介绍一下如何通过nginx-ingress实现灰度/蓝绿发布。
实现方式
nginx-ingress controller通过增加annotation来实现不同场景的灰度/蓝绿发布。有两种annotation的方式:
- canary-*:是社区官方实现的灰度发布方式。
- service-*:早期的实现方式,现在不怎么用了。
canary-*
一共有以下几种annotation,包括header,cookie,weight。
- nginx.ingress.kubernetes.io/canary
必须设置该Annotation值为true,否则其它规则将不会生效 - nginx.ingress.kubernetes.io/canary-by-header
- 适用于灰度发布以及 A/B 测试。
- 表示基于请求头的名称进行灰度发布。
- 值为always:无论什么情况下,流量均会进入灰度服务。
- 值为never:无论什么情况下,流量均不会进入灰度服务。
- 若没有指定请求头名称的值,则只要该头存在,都会进行流量转发。
- nginx.ingress.kubernetes.io/canary-by-header-value
- 用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。
- 表示基于请求头的值进行灰度发布。
- 需要与canary-by-header头配合使用。
- nginx.ingress.kubernetes.io/canary-by-header-pattern
- 表示基于请求头的值进行灰度发布,并对请求头的值进行正则匹配。
- 需要与canary-by-header头配合使用。
- nginx.ingress.kubernetes.io/canary-by-cookie
- 适用于灰度发布与 A/B 测试。
- 表示基于Cookie进行灰度发布。
- 值为always:无论什么情况下,流量均会进入灰度服务。
- 值为never:无论什么情况下,流量均不会进入灰度服务。
- 只要存在该Cookie名称,都会进行流量转发。
- nginx.ingress.kubernetes.io/canary-weight
- 适用于蓝绿部署。
- 表示基于权重进行灰度发布。
- 取值范围:0~权重总值。
- 若未设定总值,默认总值为100。
- nginx.ingress.kubernetes.io/canary-weight-total
- 表示设定的权重总值。
- 若未设定总值,默认总值为100。
优先级
canary-by-header>canary-by-cookie>canary-weight。
目前每个Ingress规则只支持同时指定一个Canary Ingress,大于一个的Canary Ingress将会被忽略。
应用场景
基于header
- 假设当前环境已经有一套服务Service A对外提供7层服务,此时上线了一些新的特性,需要发布上线一个新的版本Service A'。
- 但又不想直接替换Service A服务,而是希望将请求头中包含foo=bar或者Cookie中包含foo=bar的客户端请求转发到Service A'服务中。
- 待运行一段时间稳定后,可将所有的流量从Service A切换到Service A'服务中,再平滑地将Service A服务下线。
基于weight
- 假设当前环境已经有一套服务Service B对外提供7层服务,此时上线了一些新的特性,需要发布上线一个新的版本Service B'。
- 但又不想将所有客户端流量切换到新版本Service B'中,而是希望将20%的流量切换到新版本Service B'中。
- 待运行一段时间稳定后,再将所有的流量从Service B切换到Service B'服务中,再平滑地将Service B服务下线。
实例
1.部署服务
参考这里的部署两个版本的nignx章节
cat oldsvc.yaml
apiVersion: v1
kind: Service
metadata:
name: old-nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
version: v1
type: ClusterIP
---
cat newsvc.yaml
apiVersion: v1
kind: Service
metadata:
name: new-nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
version: v2
type: ClusterIP
2.创建old-ingress
cat old-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: old-ingress
spec:
ingressClassName: nginx
rules:
- host: www.nginx.com
http:
paths:
- path: /
backend:
service:
name: old-nginx
port:
number: 80
pathType: ImplementationSpecific
2.1测试访问
我的ingress是没有loadbalancer的,采用的NodePort方式,所以ingress的地址就是本机网卡地址。
k get svc -n ingress
ip a | grep eth0
curl -H "Host: www.nginx.com" http://172.17.88.228:30080
3.1创建基于header的new-ingress
当header中包含foo=bar的请求才能路由到新版本服务。
cat new-ingress-header.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: new-ingress-header
annotations:
# 开启Canary。
nginx.ingress.kubernetes.io/canary: "true"
# 请求头为foo。
nginx.ingress.kubernetes.io/canary-by-header: "foo"
# 请求头foo的值为bar时,请求才会被路由到新版本服务new-nginx中。
nginx.ingress.kubernetes.io/canary-by-header-value: "bar"
spec:
ingressClassName: nginx
rules:
- host: www.nginx.com
http:
paths:
- path: /
backend:
service:
name: new-nginx
port:
number: 80
pathType: ImplementationSpecific
3.2测试访问
curl -H "Host: www.nginx.com" http://172.17.88.228:30080
curl -H "Host: www.nginx.com" -H "foo: bar" http://172.17.88.228:30080
4.1创建基于weight的new-ingress
header中包含foo=bar的请求,只允许50%的流量被路由到新版本服务中。
cat new-ingress-weight.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: new-ingress-weight
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "foo"
nginx.ingress.kubernetes.io/canary-by-header-value: "bar"
# 在满足foo=bar的基础上仅允许50%的流量会被路由到新版本服务new-nginx中。
nginx.ingress.kubernetes.io/canary-weight: "50"
spec:
ingressClassName: nginx
rules:
- host: www.nginx.com
http:
paths:
- path: /
backend:
service:
name: new-nginx
port:
number: 80
pathType: ImplementationSpecific
4.2测试访问
curl -H "Host: www.nginx.com" http://172.17.88.228:30080
curl -H "Host: www.nginx.com" -H "foo: bar" http://172.17.88.228:30080
5.1创建仅weight的ingress
仅50%的流量被路由到新版本服务中。
cat weight-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: weight-ingress
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "50"
spec:
ingressClassName: nginx
rules:
- host: www.nginx.com
http:
paths:
- path: /
backend:
service:
name: new-nginx
port:
number: 80
pathType: ImplementationSpecific
5.2测试访问
k delete -f new-ingress-header.yaml
k delete -f new-ingress-weight.yaml
curl -H "Host: www.nginx.com" http://172.17.88.228:30080
6.删除老版本服务
系统运行一段时间后,当新版本服务已经稳定并且符合预期后,需要下线老版本的服务 ,仅保留新版本服务在线上运行。为了达到该目标,需要将旧版本的Service指向新版本服务的Deployment,并且删除旧版本的Deployment和新版本的Service。
6.1替换old-svc指向new-nginx
k patch svc old-nginx -p '{"spec":{"selector":{"version":"v2"}}}'
6.2测试访问
可以看到请求全部被路由到了新版本的服务。
curl -H "Host: www.nginx.com" http://172.17.88.228:30080
6.3删除old-ingress,old-deploy,new-svc
k delete -f weight-ingress.yaml
k delete -f old-nginx.yaml
k delete -f newsvc.yaml
k get po,svc | grep nginx
k get ing