Ingress-nginx灰度发布功能详解

灰度发布使用背景

最近公司一直在推进DevOps,主要目标是减少对个人的依赖,降低团队之间的损耗,在保证质量的前提下,快速交付价值。在实际执行过程中表现出来的就是服务拆分粒度尽可能细,服务每次上线功能尽可能少,发布节奏尽可能快; 服务必须做到可灰度、可监控、可回滚。至于监控先暂且不聊,如何做到灰度发布升级以及回滚呢?整个PaaS平台是基于Kubernetes进行建设,Kubernetes资源对象Deployment可以做到滚动升级的功能,但并没有提供暂停点机制,即没有办法快捷方便的进行灰度功能的针对性测试。而灰度能力是业务快速发布过程中不可或缺的一种能力,如果出现问题,灰度能够保证其影响范围。

灰度发布概念介绍

灰度发布是指产品迭代过程中能够按照某种策略对新版本进行线上功能的测试,先推广给一部分人使用和测试,如果满足预期,平滑升级;如有问题,修改、回退....保证其影响范围及系统稳定性、降低生产环境引入新软件带来的风险。常见的有金丝雀发布、蓝绿部署和A/B测试等,可根据业务场景选择发布方式。

ingress-nginx灰度发布介绍

因为平台中暂未使用ServiceMesh,所以就Kubernetes入口控制器Ingress-Controller下手,寻找灰度发布的功能,

https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md

可以看出Ingress-nginx0.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-headerstring基于 Request Header 的流量切分,适用于灰度发布以及 A/B 测试。当 Request Header 设置为 always时,请求将会被一直发送到 Canary 版本;当 Request Header 设置为 never时,请求不会被发送到 Canary 入口;对于任何其他 Header 值,将忽略 Header,并通过优先级将请求与其他金丝雀规则进行优先级的比较。
nginx.ingress.kubernetes.io/canary-by-header-valuestring要匹配的 Request Header 的值,用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。当 Request Header 设置为此值时,它将被路由到 Canary 入口。该规则允许用户自定义 Request Header 的值,必须与上一个 annotation (即canary-by-header)一起使用。
nginx.ingress.kubernetes.io/canary-by-header-patternstring正则表达式匹配的 Request Header 的值,用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。当 Request Header 设置为此值时,它将被路由到 Canary 入口。该规则允许用户自定义 Request Header 的值,必须与上一个 annotation (即canary-by-header)一起使用,如果已经使用canary-by-header-value,那么此属性将被忽略。
nginx.ingress.kubernetes.io/canary-by-cookiestring基于 Cookie 的流量切分,适用于灰度发布与 A/B 测试。用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务的cookie。当 cookie 值设置为 always时,它将被路由到 Canary 入口;当 cookie 值设置为 never时,请求不会被发送到 Canary 入口;对于任何其他值,将忽略 cookie 并将请求与其他金丝雀规则进行优先级的比较。
nginx.ingress.kubernetes.io/canary-weightnumber基于服务权重的流量切分,适用于蓝绿部署,权重范围 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,其中这两个版本的服务需要通过labeldeployment名称区分开,当然也可以使用命名空间进行严格隔离。

如下分别进行展示灰度发布的具体使用,其中灰度发布注解属性中的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设置为neveralwayscurl -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 Headerannotation用法规则类似。例如在A/B测试场景 下,需要让地域为北京的用户访问Canary版本。那么当cookieannotation设置为nginx.ingress.kubernetes.io/canary-by-cookie: "user_Beijing",此时后台可对登录的用户请求进行检查,如果该用户访问源来自北京则设置cookie user_Beijing的值为always

总结

本文主要介绍了为什么使用灰度发布,如何使用Ingress-nginx进行灰度发布,最后通过示例详细介绍了Ingress-nginx annotation灰度发布配置和使用,如有问题,关注公众号,加我微信,拉你进群讨论。


推荐

  如何使用 Ingress-nginx 进行前后端分离?

  不想凌晨上线的你,不考虑徒手撸一个灰度发布系统?


   原创不易,随手关注或者”三连“,诚挚感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值