灰度发布使用背景
最近公司一直在推进DevOps
,主要目标是减少对个人的依赖,降低团队之间的损耗,在保证质量的前提下,快速交付价值。在实际执行过程中表现出来的就是服务拆分粒度尽可能细,服务每次上线功能尽可能少,发布节奏尽可能快; 服务必须做到可灰度、可监控、可回滚。至于监控先暂且不聊,如何做到灰度发布升级以及回滚呢?整个PaaS
平台是基于Kubernetes
进行建设,Kubernetes
资源对象Deployment
可以做到滚动升级的功能,但并没有提供暂停点机制,即没有办法快捷方便的进行灰度功能的针对性测试。而灰度能力是业务快速发布过程中不可或缺的一种能力,如果出现问题,灰度能够保证其影响范围。
灰度发布概念介绍
灰度发布是指产品迭代过程中能够按照某种策略对新版本进行线上功能的测试,先推广给一部分人使用和测试,如果满足预期,平滑升级;如有问题,修改、回退....保证其影响范围及系统稳定性、降低生产环境引入新软件带来的风险。常见的有金丝雀发布、蓝绿部署和A/B测试等,可根据业务场景选择发布方式。
ingress-nginx灰度发布介绍
因为平台中暂未使用ServiceMesh
,所以就Kubernetes
入口控制器Ingress-Controller
下手,寻找灰度发布的功能,
https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md
可以看出
Ingress-nginx
从0.21.0
版本开始支持金丝雀发布功能,具体如下图:
注解划分
官网介绍
https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#canary
具体支持的发布功能如下所示:
名称 | 类型 | 解释 |
---|---|---|
nginx.ingress.kubernetes.io/canary | "true","false" | 开启灰度发布功能,如果没有开启此属性,则如下属性不生效。 |
nginx.ingress.kubernetes.io/canary-by-header | string | 基于 Request Header 的流量切分,适用于灰度发布以及 A/B 测试。当 Request Header 设置为 always时,请求将会被一直发送到 Canary 版本;当 Request Header 设置为 never时,请求不会被发送到 Canary 入口;对于任何其他 Header 值,将忽略 Header,并通过优先级将请求与其他金丝雀规则进行优先级的比较。 |
nginx.ingress.kubernetes.io/canary-by-header-value | string | 要匹配的 Request Header 的值,用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。当 Request Header 设置为此值时,它将被路由到 Canary 入口。该规则允许用户自定义 Request Header 的值,必须与上一个 annotation (即canary-by-header)一起使用。 |
nginx.ingress.kubernetes.io/canary-by-header-pattern | string | 正则表达式匹配的 Request Header 的值,用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。当 Request Header 设置为此值时,它将被路由到 Canary 入口。该规则允许用户自定义 Request Header 的值,必须与上一个 annotation (即canary-by-header)一起使用,如果已经使用canary-by-header-value,那么此属性将被忽略。 |
nginx.ingress.kubernetes.io/canary-by-cookie | string | 基于 Cookie 的流量切分,适用于灰度发布与 A/B 测试。用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务的cookie。当 cookie 值设置为 always时,它将被路由到 Canary 入口;当 cookie 值设置为 never时,请求不会被发送到 Canary 入口;对于任何其他值,将忽略 cookie 并将请求与其他金丝雀规则进行优先级的比较。 |
nginx.ingress.kubernetes.io/canary-weight | number | 基于服务权重的流量切分,适用于蓝绿部署,权重范围 0 - 100 按百分比将请求路由到 Canary Ingress 中指定的服务。权重为 0 意味着该金丝雀规则不会向 Canary 入口的服务发送任何请求。权重为 100 意味着所有请求都将被发送到 Canary 入口。 |
注意:金丝雀规则按照如下优先级进行匹配:canary-by-header -> canary-by-cookie -> canary-weight
类型划分
如果按照类型可以划分为权重(一定百分比的流量路由到金丝雀版本)和用户自定义(根据用户自定义header/cookie
路由到指定版本)两种金丝雀发布方式,具体如下图所示:
Ingress-nginx灰度功能测试
测试环境说明
ingress
版本:nginx-ingress-controller:0.32.0
所有部署方式见:https://kubernetes.github.io/ingress-nginx/deploy/
,部署方式非常简单。Kubernetes
版本:1.15
服务使用
spring-boot
,其中这两个版本的服务需要通过label
和deployment
名称区分开,当然也可以使用命名空间进行严格隔离。
如下分别进行展示灰度发布的具体使用,其中灰度发布注解属性中的key
可以为任意合法值,如下举例为canary
,另外每次对资源文件修改后,需要kubectl apply
重新加载资源。
金丝雀版本
服务代码:
@GetMapping({ "/vets" })
public @ResponseBody Vets showResourcesVetList() {
// Here we are returning an object of type 'Vets' rather than a collection of Vet
// objects so it is simpler for JSon/Object mapping
Vets vets = new Vets();
Vet vet = new Vet();
vet.setId(1);
vet.setFirstName("gray");
vet.setLastName("canary");
vets.getVetList().add(vet);
return vets;
}
svc编排文件
apiVersion: v1
kind: Service
metadata:
name: spring-boot-service-gray
labels:
app: spring-boot-gray
spec:
ports:
- name: http
port: 80
targetPort: 8080
selector:
app: spring-boot-gray
ingress编排文件
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: spring-boot-gray
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/ssl-redirect: "false"
nginx.ingress.kubernetes.io/use-forwarded-headers: "true"
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "canary"
nginx.ingress.kubernetes.io/canary-by-header-value: "abcde"
spec:
tls:
- hosts:
- spring.boot.sc.com
secretName: boot
rules:
- host: spring.boot.sc.com
http:
paths:
- path: /
backend:
serviceName: spring-boot-service-gray
servicePort: 80
production版本
服务代码
@GetMapping({ "/vets" })
public @ResponseBody Vets showResourcesVetList() {
Vets vets = new Vets();
vets.getVetList().addAll(this.vets.findAll());
return vets;
}
svc
编排文件
apiVersion: v1
kind: Service
metadata:
name: spring-boot-service
labels:
app: spring-boot
spec:
ports:
- name: http
port: 80
targetPort: 8080
selector:
app: spring-boot
ingress编排文件
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: spring-boot
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/ssl-redirect: "false"
nginx.ingress.kubernetes.io/use-forwarded-headers: "true"
spec:
tls:
- hosts:
- spring.boot.sc.com
secretName: boot
rules:
- host: spring.boot.sc.com
http:
paths:
- path: /
backend:
serviceName: spring-boot-service
servicePort: 80
按照请求header执行测试
正如上面表格中解释Request Header
设置为never
或always
时curl -H'canary: always' http://spring.boot.sc.com/vets
,请求将不会
或一直
被发送到Canary
版本。
curl http://spring.boot.sc.com/vets
返回production环境数据:
{"vetList":[{"id":1,"firstName":"James","lastName":"Carter","specialties":[],"nrOfSpecialties":0,"new":false},{"id":2,"firstName":"Helen","lastName":"Leary","specialties":[{"id":1,"name":"radiology","new":false}],"nrOfSpecialties":1,"new":false},{"id":3,"firstName":"Linda","lastName":"Douglas","specialties":[{"id":3,"name":"dentistry","new":false},{"id":2,"name":"surgery","new":false}],"nrOfSpecialties":2,"new":false},{"id":4,"firstName":"Rafael","lastName":"Ortega","specialties":[{"id":2,"name":"surgery","new":false}],"nrOfSpecialties":1,"new":false},{"id":5,"firstName":"Henry","lastName":"Stevens","specialties":[{"id":1,"name":"radiology","new":false}],"nrOfSpecialties":1,"new":false},{"id":6,"firstName":"Sharon","lastName":"Jenkins","specialties":[],"nrOfSpecialties":0,"new":false}]}
curl -H'canary: abcde' http://spring.boot.sc.com/vets
返回金丝雀版本数据:{"vetList":[{"id":1,"firstName":"gray","lastName":"canary","specialties":[],"nrOfSpecialties":0,"new":false}]}
按照请求权重执行测试(按照流量进行灰度,这种方式尤为有用)
金丝雀Ingress
编排文件annotations
添加如下注解:
nginx.ingress.kubernetes.io/canary-weight: "50"
再次执行会发现按照半对半比例分别分配到金丝雀环境和production
环境。
按照模糊匹配执行测试(A/B发布场景下,这种方式尤为有用)
金丝雀Ingress
编排文件annotations
添加如下注解,其中正则表达式使用的是PCRE
库:
nginx.ingress.kubernetes.io/canary-by-header-pattern: "(?=13713876543|13713876544|13713876545)"
同时删除:
nginx.ingress.kubernetes.io/canary-by-header: "canary"
再次执行curl -H'canary: 13713876543' http://spring.boot.sc.com/vets
,会按照正则表达式匹配内容路由到相应服务。
按照cookie匹配执行测试
与基于Request Header
的annotation
用法规则类似。例如在A/B
测试场景 下,需要让地域为北京的用户访问Canary
版本。那么当cookie
的annotation
设置为nginx.ingress.kubernetes.io/canary-by-cookie: "user_Beijing"
,此时后台可对登录的用户请求进行检查,如果该用户访问源来自北京则设置cookie user_Beijing
的值为always
。
总结
本文主要介绍了为什么使用灰度发布,如何使用Ingress-nginx
进行灰度发布,最后通过示例详细介绍了Ingress-nginx annotation
灰度发布配置和使用,如有问题,关注公众号,加我微信,拉你进群讨论。
推荐
原创不易,随手关注或者”三连“,诚挚感谢!