简介
日常工作中我们经常需要对服务进行版本更新升级, 为此我们经常使用到的发布方式有滚动升级, 分批暂停发布, 蓝绿发布以及灰度发布, 今天主要跟大家分享下在阿里云容器服务 Kubernetes 集群中如何通过 Ingress Controller 来实现应用服务的灰度发布及 AB 测试.
流量切分
为满足用户多种不同的应用发布需求, 阿里云容器服务 K8S Ingress Controller 支持多种流量切分方式:
基于 Request Header 的流量切分, 适用于灰度发布以及 AB 测试场景
基于 Cookie 的流量切分, 适用于灰度发布以及 AB 测试场景
基于服务权重的流量切分, 适用于蓝绿发布场景
注解说明
阿里云容器服务 K8S Ingress Controller 通过下列 Annotation 来支持应用服务的灰度发布机制:
1. nginx.ingress.kubernetes.io/service-match
该注解用来配置新版本服务的路由匹配规则, 格式如下:
- nginx.ingress.kubernetes.io/service-match: |
- <service-name>: <match-rule>
- # 参数说明:
- # service-name: 服务名称, 满足 match-rule 的请求会被路由到该服务中
- # match-rule: 路由匹配规则
- #
- # 路由匹配规则:
- # 1. 支持的匹配类型
- # - header: 基于请求头, 支持正则匹配
- # - cookie: 基于 cookie, 支持正则匹配
路由匹配规则配置示例:
- # 请求头中满足 foo 正则匹配 ^bar$ 的请求被转发到新版本服务 new-nginx 中
- new-nginx: header("foo", /^bar$/)
- # cookie 中满足 foo 正则匹配 ^sticky-.+$ 的请求被转发到新版本服务 new-nginx 中
- new-nginx: cookie("foo", /^sticky-.+$/)
- 2. nginx.ingress.kubernetes.io/service-weight
该注解用来配置新旧版本服务的流量权重, 格式如下:
- nginx.ingress.kubernetes.io/service-weight: |
- <new-svc-name>:<new-svc-weight>, <old-svc-name>:<old-svc-weight>
参数说明:
new-svc-name: 新版本服务名称
new-svc-weight: 新版本服务权重
old-svc-name: 旧版本服务名称
old-svc-weight: 旧版本服务权重
服务权重配置示例:
- nginx.ingress.kubernetes.io/service-weight: |
- new-nginx: 20, old-nginx: 60
注意:
服务权重采用相对值计算方式. 如示例中的服务权重设置, new-nginx 服务的权重百分比为 25%,old-nginx 服务的权重百分比为 75%.
一个服务组 (同一个 ingress yaml 中具有相同 Host 和 Path 的服务) 中未明确设置权重的服务默认权重值为 100.
版本更新
目前阿里云容器服务 K8S Ingress Controller 需要 0.12.0-4 及其以上版本才支持流量切分特性, 可以通过下面命令来查看 Ingress Controller 的当前版本号:
采用 Deployment 部署的情况
kubectl -n kube-system get deploy nginx-ingress-controller -o yaml | grep -v 'apiVersion' | grep 'aliyun-ingress-controller'
采用 DaemonSet 部署的情况
kubectl -n kube-system get ds nginx-ingress-controller -o yaml | grep -v 'apiVersion' | grep 'aliyun-ingress-controller'
若您的集群内 Ingress Controller 当前版本号小于 0.12.0-4, 那么您可以通过下面方式来升级 Ingress Controller 到新版本:
采用 Deployment 部署的情况
kubectl -n kube-system set image deploy/nginx-ingress-controller nginx-ingress-controller=registry.cn-hangzhou.aliyuncs.com/acs/aliyun-ingress-controller:0.12.0-4
采用 DaemonSet 部署的情况
kubectl -n kube-system set image ds/nginx-ingress-controller nginx-ingress-controller=registry.cn-hangzhou.aliyuncs.com/acs/aliyun-ingress-controller:0.12.0-4
至此, 您集群内的 Ingress Controller 已经支持灰度发布功能.
部署服务
在应用灰度发布前, 我们必须事先已经存在一套现有的应用对外提供服务. 这里我们部署一套 nginx 服务并通过 Ingress Controller 对外提供 7 层域名访问:
- apiVersion: extensions/v1beta1
- 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/xianlu/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
部署 ingress 资源:
- apiVersion: extensions/v1beta1
- kind: Ingress
- metadata:
- name: gray-release
- spec:
- rules:
- - host: www.example.com
- http:
- paths:
- # 老版本服务
- - path: /
- backend:
- serviceName: old-nginx
- servicePort: 80
部署并测试访问情况:
kubectl get ing
NAME HOSTS ADDRESS PORTS AGE
- gray-release www.example.com 47.107.20.35 80 1m
- curl -H "Host: www.example.com" http://47.107.20.35
- old
- curl -H "Host: www.example.com" http://47.107.20.35
- old
- curl -H "Host: www.example.com" http://47.107.20.35
- old
灰度发布新版本服务
此时我们需要发布一个新版本的 nginx 服务并配置仅允许部分客户端才能访问到这个新版本服务:
- apiVersion: extensions/v1beta1
- 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/xianlu/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
场景一: 设置满足特定规则的客户端才能访问新版本服务
假若我们希望请求头中满足 foo=bar 的客户端请求才能路由到新版本服务中, 那么我们可以如下修改配置 ingress 规则:
- apiVersion: extensions/v1beta1
- kind: Ingress
- metadata:
- name: gray-release
- annotations:
- # 请求头中满足正则匹配 foo=bar 的请求才会被路由到新版本服务 new-nginx 中
- 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
测试客户端请求:
- curl -H "Host: www.example.com" http://47.107.20.35
- old
- curl -H "Host: www.example.com" http://47.107.20.35
- old
- curl -H "Host: www.example.com" http://47.107.20.35
- old
- curl -H "Host: www.example.com" -H "foo: bar" http://47.107.20.35
- new
- curl -H "Host: www.example.com" -H "foo: bar" http://47.107.20.35
- new
- curl -H "Host: www.example.com" -H "foo: bar" http://47.107.20.35
- new
场景二: 在满足特定规则的基础上设置一定比例的请求被路由到新版本服务中
假若我们希望请求头中满足 foo=bar 的客户端请求仅允许 50% 的流量被路由到新版本服务中, 那么我们可以如下修改配置 ingress 规则:
- apiVersion: extensions/v1beta1
- kind: Ingress
- metadata:
- name: gray-release
- annotations:
- # 请求头中满足正则匹配 foo=bar 的请求才会被路由到新版本服务 new-nginx 中
- nginx.ingress.kubernetes.io/service-match: |
- new-nginx: header("foo", /^bar$/)
- # 在满足上述匹配规则的基础上仅允许 50% 的流量会被路由到新版本服务 new-nginx 中
- 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
测试客户端请求:
- curl -H "Host: www.example.com" http://47.107.20.35
- old
- curl -H "Host: www.example.com" http://47.107.20.35
- old
- curl -H "Host: www.example.com" http://47.107.20.35
- old
- curl -H "Host: www.example.com" -H "foo: bar" http://47.107.20.35
- new
- curl -H "Host: www.example.com" -H "foo: bar" http://47.107.20.35
- old
- curl -H "Host: www.example.com" -H "foo: bar" http://47.107.20.35
- old
- curl -H "Host: www.example.com" -H "foo: bar" http://47.107.20.35
- new
场景三: 仅设置一定比例的请求被路由到新版本服务中
假若我们仅仅希望 50% 的流量被路由到新版本服务中, 那么我们可以如下修改配置 ingress 规则:
- apiVersion: extensions/v1beta1
- kind: Ingress
- metadata:
- name: gray-release
- annotations:
- # 允许 50% 的流量被路由到新版本服务 new-nginx 中
- 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
测试客户端访问:
- curl -H "Host: www.example.com" http://47.107.20.35
- new
- curl -H "Host: www.example.com" http://47.107.20.35
- new
- curl -H "Host: www.example.com" http://47.107.20.35
- new
- curl -H "Host: www.example.com" http://47.107.20.35
- old
- curl -H "Host: www.example.com" http://47.107.20.35
- old
- curl -H "Host: www.example.com" http://47.107.20.35
- new
- curl -H "Host: www.example.com" http://47.107.20.35
- old
特别说明: 当我们既没有设置 service-match 也没有设置 service-weight 注解时, Ingress Controller 在转发请求时默认会将请求均衡地随机转发到新老版本服务中.
灰度发布完成
系统运行一段时间后, 当新版本服务已经稳定并且符合预期后, 我们需要将老版本的服务下线掉, 仅仅保留新版本服务在线上运行, 此时我们需要修改 ingress 规则删除相关的 annotation 以及老版本服务, 最终规则如下:
- apiVersion: extensions/v1beta1
- kind: Ingress
- metadata:
- name: gray-release
- spec:
- rules:
- - host: www.example.com
- http:
- paths:
- # 新版本服务
- - path: /
- backend:
- serviceName: new-nginx
- servicePort: 80
测试客户端访问:
- curl -H "Host: www.example.com" http://47.107.20.35
- new
- curl -H "Host: www.example.com" http://47.107.20.35
- new
- curl -H "Host: www.example.com" http://47.107.20.35
- new
可以看到, 现在的请求是全部被路由到了新版本的服务中, 这就完成了灰度发布的整个周期. 最后, 您可以删除老版本的 service 和 deployment.
来源: https://yq.aliyun.com/articles/594019