背景信息
当对服务进行版本更新升级时,需要使用到滚动升级、分批暂停发布、蓝绿发布以及灰度发布等发布方式。本文将介绍在SCE集群中如何通过Nginx Ingress Controller来实现应用服务的灰度发布。
当对服务进行版本更新升级时,需要使用到滚动升级、蓝绿发布以及灰度发布等发布方式。
- 滚动更新:依次进行新旧替换,直到旧的全部被替换为止。
- 蓝绿发布:两套独立的系统,对外提供服务的称为绿系统,待上线的服务称为蓝系统,当蓝系统里面的应用测试完成后,用户流量接入蓝系统,蓝系统将称为绿系统,以前的绿系统就可以销毁。
- 灰度发布:在一套集群中存在稳定和灰度两个版本,灰度版本可以限制只针对部分人员可用,待灰度版本测试完成后,可以将灰度版本升级为稳定版本,旧的稳定版本就可以下线了,也称之为金丝雀发布。
前提条件
- 确保您已经创建SCE集群,具体操作请参阅创建SCE集群。
- 在集群中安装nginx-ingress-controller插件,作为Ingress Controller,并通过Nginx对外暴露统一的流量入口。详细操作可参考安装插件。
实现原理
nginx-ingress是Kubernetes官方推荐的ingress controller,它是基于nginx实现的,增加了一组用于实现额外功能的Lua插件。
为了实现灰度发布,ingress-nginx通过定义annotation来实现不同场景的灰度发布,其支持的规则如下:
- nginx.ingress.kubernetes.io/canary-by-header:基于 Request Header 的流量切分,适用于灰度发布以及 A/B 测试。当 Request Header 设置为 always时,则将请求切分到Canary Ingress定义的Service上;当 Request Header 设置为 never时,请求不会被发送到 Canary 入口,会将请求转发到常规Ingress定义的Service上;对于任何其他 Header 值,将忽略 Header,并通过优先级将请求与其他金丝雀规则进行优先级的比较。
- nginx.ingress.kubernetes.io/canary-by-header-value:要匹配的 Request Header 的值,用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。当 Request Header 设置为此值时,它将被路由到 Canary 入口,将请求切分到Canary Ingress定义的Service上。该规则允许用户自定义 Request Header 的值,必须与上一个 annotation (即:canary-by-header)一起使用。
- nginx.ingress.kubernetes.io/canary-by-cookie:基于 Cookie 的流量切分,适用于灰度发布与 A/B 测试。用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务的cookie。Cookie 值仅支持“always”和“never”。当 cookie 值设置为 always时,它将被路由到 Canary 入口;当 cookie 值设置为 never时,请求不会被发送到 Canary 入口;对于任何其他值,将忽略 cookie 并将请求与其他金丝雀规则进行优先级的比较。
- nginx.ingress.kubernetes.io/canary-weight:基于服务权重的流量切分,适用于蓝绿部署,权重取值范围为[0-100],表示Canary Ingress所切分到的流量百分比。权重为 0 意味着该金丝雀规则不会向 Canary 入口的服务发送任何请求。权重为 100 意味着所有请求都将被发送到 Canary 入口。
以上策略的优先级顺序为: canary-by-header > canary-by-cookie > canary-weight 。
基于以上annotation的发布思路如下:
- 在集群中部署新旧两套应用版本,一套是stable版本,一套是canary版本,两个版本都有自己的service。
- 定义两个Ingress配置,一个正常提供服务,一个增加canary的annotation。
- 待canary版本无误后,将其切换成stable版本,并且将旧的版本下线,流量全部接入新的stable版本。
应用场景
场景一:基于用户请求将匹配的业务流量切分到新版本
假设在当前线上环境中,您已经有一套服务Service v1对外提供7层服务,此时开发了一些新的功能,现需发布新版本Service v2服务。但又不想直接替换Service v1服务,而是希望将请求头包含 “foo=bar” 或者Cookie包含 “foo=bar” 的客户端请求转发到Service v2服务中,验证一下新版本功能是否正常,待稳定运行后,再逐步全量切到Service v2服务,平滑下线Service v1服务。示意图如下:
场景二:基于服务权重将业务流量切分到新版本
假设当前线上环境,您已经有一套服务Service v1对外提供7层服务,此时修复了一些问题,需要发布上线一个新的版本Service v2。但又不想将所有客户端流量切换到新版本Service v2中,而是希望将20%的流量灰度到Service v2,待稳定运行后,逐步全量切到Service v2,平滑下线Service v1。示意图如下:
操作步骤
场景一:基于用户请求将匹配的业务流量切分到新版本
步骤一:部署旧版本Service v1和常规Ingress
这里使用Ingress作为service v1应用服务,并且为方便观测流量切分的效果,将nginx欢迎页设置为“v1”。
- 创建配置configmap,key为index.html,value为v1。
- 创建nginx工作负载,配置数据卷为刚才创建的configmap;配置镜像和挂载卷,挂载容器路径为:/usr/share/nginx/html;配置访问设置,选择虚拟集群IP类型,容器端口80,服务端口30080。
- 创建旧版本service v1的常规ingress。灰度ingress一栏选择否,在域名路径规则一栏填写域名,指定服务名称以及端口等。
- 检查通过Ingress域名能正常访问旧版本service v1服务。
步骤二:部署新版本Service v2
这里同样使用Ingress作为service v2应用服务,并且为方便观测流量切分的效果,将nginx欢迎页设置为“v2”。
- 创建配置configmap,key为index.html,value为v2。
- 创建Ingress工作负载,配置数据卷为刚才创建的configmap;配置镜像和挂载卷,挂载容器路径为:/usr/share/nginx/html;配置访问设置,选择虚拟集群IP类型,容器端口80,服务端口30081。
步骤三:创建灰度ingress,在灰度发布新版本
- 基于Header创建新版本service v2的Ingress。
- 在灰度Ingress一栏选择是;在生产ingress一栏选择旧版本service v1的常规Ingress;在流量切换方式一栏选择灰度,基于Header的区分方式,填写Header key为foo,Header value为bar,精确匹配;在域名路径规则一栏填写域名,指定服务名称和端口等。
执行命令进行访问测试,<EXTERNAL_IP>为Nginx Ingress对外暴露的IP:
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com'
v1
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com' -H 'foo: bar'
v2
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com'
v1
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com' -H 'foo: bar'
v2
可以看出,仅当Header中包含foo且值为bar的流量才会切分到新版本service v2服务。
- 基于Cookie创建新版本service v2的Ingress。
在灰度Ingress一栏选择是;在生产Ingress一栏选择旧版本service v1的常规Ingress;在流量切换方式一栏选择灰度,基于Cookie的区分方式,填写Cookie key为foo,Cookie value为always,精确匹配;在域名路径规则一栏填写域名,指定服务名称和端口等。
执行命令进行访问测试,<EXTERNAL_IP>为Nginx Ingress对外暴露的IP:
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com'
v1
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com' --cookie 'foo=bar'
v2
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com'
v1
# curl http://<EXTERNAL_IP> -H 'Host: test-gray.com' --cookie 'foo=bar'
v2
可以看出,仅当Cookie中包含foo且值为bar的流量才会切分到新版本service v2服务。
步骤四:下线旧版本Service v1服务
- 将service v1的常规Ingress的服务名称改为service v2服务。
- 删除service v2的Ingress。
- 删除旧版本service v1的无状态工作负载和配置项。
平滑下线旧版本后,通过原来的常规Ingress请求的流量都会切分到新版本Service v2服务了。
场景二:基于服务权重将业务流量切分到新版本
步骤一:部署旧版本Service v1和常规Ingress
同“场景一:基于用户请求将匹配的业务流量切分到新版本”。
步骤二:部署新版本Service v2
同“场景一:基于用户请求将匹配的业务流量切分到新版本”。
步骤三:创建灰度Ingress,在灰度发布新版本
- 基于服务权重新版本service v2的Ingress。
- 在灰度Ingress一栏选择是;在生产ingress一栏选择旧版本service v1的常规Ingress;在流量切换方式一栏选择蓝绿,配置全部切到灰度的权重百分比;在域名路径规则一栏填写域名,指定服务名称和端口等。
执行命令进行访问测试,<EXTERNAL_IP>为Nginx Ingress对外暴露的IP:
$ 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
可以看出,有近50%的流量切分到新版本service v2服务,当请求的数量越多时比例会越接近50%。
步骤四:下线旧版本Service v1服务
- 将service v1的常规Ingress的服务名称改为service v2服务。
- 删除service v2的Ingress。
- 删除旧版本service v1的无状态工作负载和配置项。
平滑下线旧版本后,通过原来的常规Ingress请求的流量都会切分到新版本Service v2服务了。