背景信息
灰度及蓝绿发布是为新版本创建一个与老版本完全一致的生产环境,在不影响老版本的前提下,按照一定的规则把部分流量切换到新版本,当新版本试运行一段时间没有问题后,将用户的全量流量从老版本迁移至新版本。
其中蓝绿发布就是一种灰度发布方式,一部分用户继续使用老版本的服务,将一部分用户的流量切换到新版本,如果新版本运行稳定,则逐步将所有用户迁移到新版本。
容器服务ACK控制台调整了灰度发布功能的用法,分为两种。
canary-*注解方式:使用canary-*
Annotation配置蓝绿发布与灰度发布,canary-*
Annotation是社区官方实现的灰度发布方式。
service-*注解方式:使用service-*
Annotation配置蓝绿发布与灰度发布。service-*
Annotation是ACK Nginx Ingress Controller早期实现灰度发布的方式。service-match
与service-weight
功能已不再维护,并即将废弃。service-*
Annotation功能目前仍正常保留,不影响使用。
应用场景
基于客户端请求的流量切分场景
假设当前线上环境,您已经有一套服务Service A对外提供7层服务,此时上线了一些新的特性,需要发布上线一个新的版本Service A'。但又不想直接替换Service A服务,而是希望将请求头中包含foo=bar
或者Cookie中包含foo=bar
的客户端请求转发到Service A'服务中。待运行一段时间稳定后,可将所有的流量从Service A切换到Service A'服务中,再平滑地将Service A服务下线。
基于服务权重的流量切分场景
假设当前线上环境,您已经有一套服务Service B对外提供7层服务,此时修复了一些问题,需要发布上线一个新的版本Service B'。但又不想将所有客户端流量切换到新版本Service B'中,而是希望将20%的流量切换到新版本Service B'中。待运行一段时间稳定后,再将所有的流量从Service B切换到Service B'服务中,再平滑地将Service B服务下线。
针对以上多种不同的应用发布需求,阿里云容器服务Ingress Controller支持了多种流量切分方式:
基于Request Header的流量切分,适用于灰度发布以及AB测试场景。
基于Cookie的流量切分,适用于灰度发布以及AB测试场景。
基于Query Param的流量切分,适用于灰度发布以及AB测试场景。
基于服务权重的流量切分,适用于蓝绿发布场景。
canary-*注解方式
注解说明
Nginx Ingress Controller通过下列canary-*
Annotation来支持应用服务的灰度发布机制。
Annotation | 说明 | 适用的ACK Nginx Ingress Controller版本 |
Annotation | 说明 | 适用的ACK Nginx Ingress Controller版本 |
nginx.ingress.kubernetes.io/canary | | ≥v0.22.0 |
nginx.ingress.kubernetes.io/canary-by-header | | ≥v0.22.0 |
nginx.ingress.kubernetes.io/canary-by-header-value | | ≥v0.30.0 |
nginx.ingress.kubernetes.io/canary-by-header-pattern | | ≥v0.44.0 |
nginx.ingress.kubernetes.io/canary-by-cookie | | ≥v0.22.0 |
nginx.ingress.kubernetes.io/canary-weight | 表示基于权重进行灰度发布。 取值范围:0~权重总值。 若未设定总值,默认总值为100。
| ≥v0.22.0 |
nginx.ingress.kubernetes.io/canary-weight-total | 表示设定的权重总值。 若未设定总值,默认总值为100。
| ≥v1.1.2 |
不同灰度方式的优先级由高到低为:
canary-by-header
>canary-by-cookie
>canary-weight
说明
目前每个Ingress规则只支持同时指定一个Canary Ingress,大于一个的Canary Ingress将会被忽略。
步骤一:部署服务
部署Nginx服务并通过Nginx Ingress Controller对外提供7层域名访问。
创建Deployment和Service。
创建nginx.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: registry.cn-hangzhou.aliyuncs.com/acs-sample/old-nginx
imagePullPolicy: Always
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
sessionAffinity: None
type: NodePort
执行以下命令,创建Deployment和Service。
kubectl apply -f nginx.yaml
部署Ingress。
创建ingress.yaml。
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: gray-release
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
serviceName: old-nginx
servicePort: 80
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gray-release
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
service:
name: old-nginx
port:
number: 80
pathType: ImplementationSpecific
执行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
测试访问情况。
执行以下命令,获取外部IP。
执行以下命令,查看路由访问情况。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
预期输出:
步骤二:灰度发布新版本服务
发布一个新版本的Nginx服务并配置路由规则。
部署新版本的Deployment和Service。
创建nginx1.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: registry.cn-hangzhou.aliyuncs.com/acs-sample/new-nginx
imagePullPolicy: Always
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
sessionAffinity: None
type: NodePort
执行以下命令,部署新版本的Deployment和Service。
kubectl apply -f nginx1.yaml
设置访问新版本服务的路由规则。
ACK支持设置以下三种路由规则,您可以根据实际情况选择路由规则。
-
设置满足特定规则的客户端才能访问新版本服务。以下示例仅请求头中满足foo=bar
的客户端请求才能路由到新版本服务。
按照以上条件,在ingress1.yaml文件中创建新的Ingress资源gray-release-canary
。
apiVersion: networking.k8s.io/v1beta1
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:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
serviceName: new-nginx
servicePort: 80
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:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
service:
name: new-nginx
port:
number: 80
pathType: ImplementationSpecific
执行以下命令,部署Ingress1。
kubectl apply -f ingress1.yaml
执行以下命令,获取外部IP。
查看路由访问情况。
执行以下命令,访问服务。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
预期输出:
执行以下命令,请求头中满足foo=bar
的客户端请求访问服务。
curl -H "Host: www.example.com" -H "foo: bar" http://<EXTERNAL_IP>
预期输出:
重复执行以上命令,可以看到,仅请求头中满足foo=bar
的客户端请求才能路由到新版本服务。
-
在特定规则未被满足时,再按照一定比例将请求路由到新版本服务中。以下示例要求请求头中满足foo=bar
的客户端请求,若不包含该请求头,再将50%的流量路由到新版本服务中。
按照以下内容,修改步骤2中创建的Ingress。
apiVersion: networking.k8s.io/v1beta1
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"
nginx.ingress.kubernetes.io/canary-weight: "50"
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
serviceName: new-nginx
servicePort: 80
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"
nginx.ingress.kubernetes.io/canary-weight: "50"
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
service:
name: new-nginx
port:
number: 80
pathType: ImplementationSpecific
执行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
执行以下命令,获取外部IP。
查看路由访问情况。
执行以下命令,访问服务。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
预期输出:
执行以下命令,请求头中满足foo=bar
的客户端请求访问服务。
curl -H "Host: www.example.com" -H "foo: bar" http://<EXTERNAL_IP>
预期输出:
重复执行以上命令。可以看到,仅请求头中满足foo=bar
的客户端请求,且只有50%的流量才能路由到新版本服务。
-
设置一定比例的请求被路由到新版本服务中,以下示例中仅50%的流量被路由到新版本服务中。
按照以下内容,修改步骤2创建的Ingress。
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: gray-release-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "50"
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
serviceName: new-nginx
servicePort: 80
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:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
service:
name: new-nginx
port:
number: 80
pathType: ImplementationSpecific
执行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
执行以下命令,获取外部IP。
执行以下命令,查看路由访问情况。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
重复执行以上命令,可以看到仅50%的流量路由到新版本服务。
步骤三:删除老版本服务
系统运行一段时间后,当新版本服务已经稳定并且符合预期后,需要下线老版本的服务 ,仅保留新版本服务在线上运行。为了达到该目标,需要将旧版本的Service指向新版本服务的Deployment,并且删除旧版本的Deployment和新版本的Service。
修改旧版本Service文件nginx.yaml,使其指向新版本服务。
apiVersion: v1
kind: Service
metadata:
name: old-nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
run: new-nginx
sessionAffinity: None
type: NodePort
执行以下命令,部署旧版本服务。
kubectl apply -f nginx.yaml
执行以下命令,获取外部IP。
执行以下命令,查看路由访问情况。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
预期输出:
重复执行以上命令,可以看到请求全部被路由到了新版本的服务。
执行以下命令,删除Canary Ingress资源gray-release-canary
。
kubectl delete ingress gray-release-canary
删除旧版本的Deployment和新版本的Service。
执行以下命令,删除旧版本的Deployment。
kubectl delete deploy old-nginx
执行以下命令,删除新版本的Service。
kubectl delete svc new-nginx
service-*注解方式
注解说明
Nginx Ingress Controller通过下列Annotation来支持应用服务的灰度发布机制。
步骤一:部署服务
部署Nginx服务并通过Nginx Ingress Controller对外提供7层域名访问。
创建Deployment和Service。
创建nginx.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: registry.cn-hangzhou.aliyuncs.com/acs-sample/old-nginx
imagePullPolicy: Always
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
sessionAffinity: None
type: NodePort
执行以下命令,创建Deployment和Service。
kubectl apply -f nginx.yaml
部署Ingress。
创建ingress.yaml。
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: gray-release
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
serviceName: old-nginx
servicePort: 80
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gray-release
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
service:
name: old-nginx
port:
number: 80
pathType: ImplementationSpecific
执行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
测试访问情况。
执行以下命令,获取外部IP。
执行以下命令,查看路由访问情况。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
预期输出:
步骤二:灰度发布新版本服务
发布一个新版本的Nginx服务并配置路由规则。
部署新版本的Deployment和Service。
创建nginx1.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: registry.cn-hangzhou.aliyuncs.com/acs-sample/new-nginx
imagePullPolicy: Always
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
sessionAffinity: None
type: NodePort
部署新版本的Deployment和Service。
kubectl apply -f nginx1.yaml
设置访问新版本服务的路由规则。
ACK支持设置以下三种路由规则,您可以根据实际情况选择路由规则。
-
设置满足特定规则的客户端才能访问新版本服务。以下示例仅请求头中满足foo=bar
的客户端请求才能路由到新版本服务。
按照以下内容,修改步骤2创建的Ingress。
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: gray-release
annotations:
nginx.ingress.kubernetes.io/service-match: |
new-nginx: header("foo", /^bar$/)
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
serviceName: old-nginx
servicePort: 80
- path: /
backend:
serviceName: new-nginx
servicePort: 80
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gray-release
annotations:
nginx.ingress.kubernetes.io/service-match: |
new-nginx: header("foo", /^bar$/)
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
service:
name: old-nginx
port:
number: 80
pathType: ImplementationSpecific
- path: /
backend:
service:
name: new-nginx
port:
number: 80
pathType: ImplementationSpecific
执行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
执行以下命令,获取外部IP。
查看路由访问情况。
执行以下命令,访问服务。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
预期输出:
执行以下命令,请求头中满足foo=bar
的客户端请求访问服务。
curl -H "Host: www.example.com" -H "foo: bar" http://<EXTERNAL_IP>
预期输出:
重复执行以上命令,可以看到,仅请求头中满足foo=bar
的客户端请求才能路由到新版本服务。
-
在满足特定规则的基础上设置一定比例的请求被路由到新版本服务中。以下示例要求请求头中满足foo=bar
的客户端请求,且仅允许其中50%的流量被路由到新版本服务中。
按照以下内容,修改步骤2创建的Ingress。
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: gray-release
annotations:
nginx.ingress.kubernetes.io/service-match: |
new-nginx: header("foo", /^bar$/)
nginx.ingress.kubernetes.io/service-weight: |
new-nginx: 50, old-nginx: 50
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
serviceName: old-nginx
servicePort: 80
- path: /
backend:
serviceName: new-nginx
servicePort: 80
servicePort: 80
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gray-release
annotations:
nginx.ingress.kubernetes.io/service-match: |
new-nginx: header("foo", /^bar$/)
nginx.ingress.kubernetes.io/service-weight: |
new-nginx: 50, old-nginx: 50
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
service:
name: old-nginx
port:
number: 80
pathType: ImplementationSpecific
- path: /
backend:
service:
name: new-nginx
port:
number: 80
pathType: ImplementationSpecific
执行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
执行以下命令,获取外部IP。
查看路由访问情况。
执行以下命令,访问服务。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
预期输出:
执行以下命令,请求头中满足foo=bar
的客户端请求访问服务。
curl -H "Host: www.example.com" -H "foo: bar" http://<EXTERNAL_IP>
预期输出:
重复执行以上命令。可以看到,仅请求头中满足foo=bar
的客户端请求,且只有50%的流量才能路由到新版本服务。
-
设置一定比例的请求被路由到新版本服务中,以下示例中仅50%的流量被路由到新版本服务中。
按照以下内容,修改步骤2创建的Ingress。
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: gray-release
annotations:
nginx.ingress.kubernetes.io/service-weight: |
new-nginx: 50, old-nginx: 50
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
serviceName: old-nginx
servicePort: 80
- path: /
backend:
serviceName: new-nginx
servicePort: 80
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gray-release
annotations:
nginx.ingress.kubernetes.io/service-weight: |
new-nginx: 50, old-nginx: 50
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
service:
name: old-nginx
port:
number: 80
pathType: ImplementationSpecific
- path: /
backend:
service:
name: new-nginx
port:
number: 80
pathType: ImplementationSpecific
执行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
执行以下命令,获取外部IP。
执行以下命令,查看路由访问情况。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
重复执行以上命令,可以看到仅50%的流量路由到新版本服务。
步骤三:删除老版本服务
系统运行一段时间后,当新版本服务已经稳定并且符合预期后,需要下线老版本的服务 ,仅保留新版本服务在线上运行。
按照以下内容,修改步骤2创建的Ingress。
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: gray-release
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
serviceName: new-nginx
servicePort: 80
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gray-release
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
backend:
service:
name: new-nginx
port:
number: 80
pathType: ImplementationSpecific
执行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
执行以下命令,获取外部IP。
执行以下命令,查看路由访问情况。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
预期输出:
重复执行以上命令,可以看到请求全部被路由到了新版本的服务。
删除旧版本的Deployment和Service。
执行以下命令,删除旧版本的Deployment。
kubectl delete deploy <Deployment名称>
执行以下命令,删除旧版本的Service。
kubectl delete svc <Service名称>