Ingress-Nginx 灰度(金丝雀)发布
使用 Ingress-Nginx 进行灰度(金丝雀)发布
Ingress-Nginx Canary介绍
Nginx Ingress Controller 作为项目对外的流量入口和项目中各个服务的反向代理。
官方文档概述:Annotations - Ingress-Nginx Controller (kubernetes.github.io)
Nginx Annotations 的几种 Canary 规则:
Annotation 说明 nginx.ingress.kubernetes.io/canary 必须设置该Annotation值为true,否则其它规则将不会生效。取值:true:启用canary功能。false:不启用canary功能。 nginx.ingress.kubernetes.io/canary-by-header 表示基于请求头的名称进行灰度发布。请求头名称的特殊取值: always:无论什么情况下,流量均会进入灰度服务。 never:无论什么情况下,流量均不会进入灰度服务。 若没有指定请求头名称的值,则只要该头存在,都会进行流量转发。 nginx.ingress.kubernetes.io/canary-by-header-value 表示基于请求头的值进行灰度发布。 需要与canary-by-header头配合使用。 nginx.ingress.kubernetes.io/canary-by-header-pattern 表示基于请求头的值进行灰度发布,并对请求头的值进行正则匹配。 需要与canary-by-header头配合使用。 取值为用于匹配请求头的值的正则表达式。 nginx.ingress.kubernetes.io/canary-by-cookie 表示基于Cookie进行灰度发布。例如,nginx.ingress.kubernetes.io/canary-by-cookie: foo。 Cookie内容的取值: always:当foo=always,流量会进入灰度服务。 never:当foo=never,流量不会进入灰度服务。 只有当Cookie存在,且值为always时,才会进行流量转发。 nginx.ingress.kubernetes.io/canary-weight 表示基于权重进行灰度发布。 取值范围:0~权重总值。 若未设定总值,默认总值为100。 nginx.ingress.kubernetes.io/canary-weight-total 表示设定的权重总值。 若未设定总值,默认总值为100。
注意:不同灰度方式的优先级 由高到低 为:
canary-by-header --> canary-by-cookie --> canary-weight
二、ingress-nginx Canary实现
2.1、基于客户端请求的流量切分场景
假设当前线上环境,您已经有一套服务Service V1对外提供7层服务,此时上线了一些新的特性,需要发布上线一个新的版本Service V2。
希望将请求头中包含foo=bar或者Cookie中包含foo=bar的客户端请求转发到Service V2服务中。
待运行一段时间稳定后,可将所有的流量从Service V1切换到Service V2服务中,再平滑地将Service V1服务下线。
2.2、实现方式:
通过上面的annotation来实现灰度发布,其 思路如下:
在集群中部署两套系统,一套是stable版本(old-nginx),一套是canary版本(new-nginx),两个版本都有自己的service;
定义两个ingress配置,一个正常提供服务,一个增加canary的annotation;
待canary版本无误后,将其切换成stable版本,并且将旧的版本下线,流量全部接入新的stable版本
old-nginx 创建Deployment、Service、Ingress。
apiVersion: apps/v1 kind: Deployment metadata: name: old-nginx namespace: nginx-gray labels: run: nginx spec: replicas: 2 selector: matchLabels: run: old-nginx template: metadata: labels: run: old-nginx spec: containers: - name: old-nginx image: registry.cn-hangzhou.aliyuncs.com/acs-sample/old-nginx ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: old-nginx namespace: nginx-gray spec: selector: run: old-nginx type: ClusterIP ports: - port: 80 protocol: TCP targetPort: 80 --- apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: old-nginx namespace: nginx-gray spec: rules: - host: nginx.kubernets.cn http: paths: - path: / pathType: Prefix backend: serviceName: old-nginx servicePort: 80
测试验证:(预期输出:old)
# curl -H "Host: nginx.kubernets.cn" http://nginx.kubernets.cn
灰度发布新版本服务:
new-nginx 创建Deployment、Service。
设置访问新版本服务的路由规则。
设置满足特定规则的客户端才能访问新版本服务。以下示例仅请求头中满足foo=bar的客户端请求才能路由到新版本服务。
[root@master1 nginx-gray]# cat new-new-nginx.yaml apiVersion: apps/v1 kind: Deployment metadata: name: new-nginx namespace: nginx-gray labels: run: nginx spec: replicas: 2 selector: matchLabels: run: new-nginx template: metadata: labels: run: new-nginx spec: containers: - name: new-nginx image: registry.cn-hangzhou.aliyuncs.com/acs-sample/new-nginx imagePullPolicy: Always ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: new-nginx namespace: nginx-gray spec: selector: run: new-nginx type: ClusterIP ports: - port: 80 protocol: TCP targetPort: 80 --- apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: new-nginx-ingress namespace: nginx-gray annotations: # 开启Canary。 nginx.ingress.kubernetes.io/canary: "true" #请求头为foo。 nginx.ingress.kubernetes.io/canary-by-header: "foo" # 请求头foo的值为bar时,请求才会被路由到新版本服务new-nginx中。 nginx.ingress.kubernetes.io/canary-by-header-value: "bar" # 在满足上述匹配规则的基础上仅允许50%的流量会被路由到新版本服务new-nginx中。 # nginx.ingress.kubernetes.io/canary-weight: "50" spec: rules: - host: nginx.kubernets.cn http: paths: - path: / pathType: Prefix backend: serviceName: new-nginx servicePort: 80
按照header头信息转发流量:
# 正常访问: $ curl -H "Host: nginx.kubernets.cn" http://nginx.kubernets.cn old # 访问新的服务: $ curl -H "Host: nginx.kubernets.cn" -H "foo: bar" http://nginx.kubernets.cn new
可以看到,仅请求头中满足foo=bar
的客户端请求才能路由到新版本服务。
需求:请求头中满足foo=bar
的客户端请求,若不包含该请求头,再将50%的流量路由到新版本服务中。
测试验证:(几乎可以达到50%请求分布)
# curl -H "Host: nginx.kubernets.cn" http://nginx.kubernets.cn new # curl -H "Host: nginx.kubernets.cn" http://nginx.kubernets.cn old
系统运行一段时间后,当新版本服务已经稳定并且符合预期后,需要下线老版本的服务 ,仅保留新版本服务在线上运行。
为了达到该目标,需要将旧版本的Service指向新版本服务的Deployment,并且删除旧版本的Deployment和新版本的Service。
三、金丝雀发布的高级功能
如上只简单介绍了一些ingress开源默认支持的Annotation。
日常工作中基于开源ingress-nginx实线的高级功能:
通过修改nginx.ingress.kubernetes.io/configuration-snippet配置,并且配置正则实现:
当header头中有关键字(foo 或 new)字段的时候,自动将流量转发至new-nginx;
nginx.ingress.kubernetes.io/configuration-snippet (用于插入 location 块代码段);
nginx.ingress.kubernetes.io/server-snippet (用于插入 server 块中的代码段);
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: new-nginx-ingress namespace: nginx-gray annotations: nginx.ingress.kubernetes.io/configuration-snippet: | if ($http_name ~ "^.*foo$|^.*new$") { proxy_pass http://new-nginx.nginx-gray:80; break; } spec: rules: - host: nginx.kubernets.cn http: paths: - path: / pathType: Prefix backend: serviceName: old-nginx servicePort: 80 - path: / pathType: Prefix backend: serviceName: new-nginx servicePort: 80
测试验证:
# curl -H "name: new" http://nginx.kubernets.cn new # curl -H "name: foo" http://nginx.kubernets.cn new # curl -H "Host: nginx.kubernets.cn" http://nginx.kubernets.cn old
四、阿里开源ingress-nginx实现
Nginx Ingress Controller通过Annotation来支持应用服务的灰度发布机制。
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,支持正则匹配和完整匹配。 # - query:基于请求参数,支持正则匹配和完整匹配。 # # 2. 匹配方式 # - 正则匹配格式:/{regular expression}/,//表明采用正则方式匹配。 # - 完整匹配格式:"{exact expression}",""表明采用完整方式匹配。
路由匹配规则配置示例:
# 请求头中满足foo正则匹配^bar$的请求被转发到新版本服务new-nginx中。 new-nginx: header("foo", /^bar$/) # 请求头中满足foo完整匹配bar的请求被转发到新版本服务new-nginx中。 new-nginx: header("foo", "bar") # cookie中满足foo正则匹配^sticky-.+$的请求被转发到新版本服务new-nginx中。 new-nginx: cookie("foo", /^sticky-.+$/) # query param中满足foo完整匹配bar的请求被转发到新版本服务new-nginx中。 new-nginx: query("foo", "bar")
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
案例
设置满足特定规则的客户端才能访问新版本服务。以下示例仅请求头中满足foo=bar
的客户端请求才能路由到新版本服务。
-
修改如上步骤创建的Ingress。
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: gray-release namespace: nginx-gray annotations: # 请求头中满足正则匹配foo=bar的请求才会被路由到新版本服务new-nginx中。 nginx.ingress.kubernetes.io/service-match: | new-nginx: header("foo", /^bar$/) spec: rules: - host: "nginx.kubernets.cn" http: paths: - pathType: ImplementationSpecific path: / backend: service: name: old-nginx port: number: 80 - pathType: ImplementationSpecific path: / backend: service: name: new-nginx port: number: 80
测试验证:
执行以下命令,访问当前服务: #curl -H "Host: nginx.kubernets.cn" http://nginx.kubernets.cn old 执行以下命令,请求头中满足foo=bar的客户端请求访问服务: #curl -H "Host: nginx.kubernets.cn" -H "foo: bar" http://nginx.kubernets.cn new
来源:https://mp.weixin.qq.com/s/JUJmE87hIC-NKnkgleLrAg