ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[TOC] ## 背景信息 灰度及蓝绿发布是为新版本创建一个与老版本完全一致的生产环境,在不影响老版本的前提下,按照一定的规则把部分流量切换到新版本,当新版本试运行一段时间没有问题后,将用户的全量流量从老版本迁移至新版本。 其中AB测试就是一种灰度发布方式,一部分用户继续使用老版本的服务,将一部分用户的流量切换到新版本,如果新版本运行稳定,则逐步将所有用户迁移到新版本。 ## 应用场景 使用Nginx Ingress实现灰度发布适用场景主要取决于业务流量切分的策略,目前Nginx Ingress支持基于Header、Cookie和服务权重三种流量切分的策略,基于这三种策略可实现以下两种发布场景 - 切分部分用户流量到新版本 假设线上已运行了一套对外提供七层服务的Service A,此时开发了一些新的特性,需要发布上线一个新的版本Service A',但又不想直接替换原有的Service A,而是期望将Header中包含foo=bar或者Cookie中包含foo=bar的用户请求转发到新版本Service A'中。待运行一段时间稳定后,再逐步全量上线新版本,平滑下线旧版本。 - 切分一定比例的流量到新版本 假设线上已运行了一套对外提供七层服务的Service B,此时修复了一些问题,需要发布上线一个新的版本Service B',但又不想直接替换原有的Service B,而是期望将20%的流量切换到新版本Service B'中。待运行一段时间稳定后,再将所有的流量从旧版本切换到新版本中,平滑下线旧版本。 ## ingress注解说明 Nginx Ingress支持通过配置注解(Annotations)来实现不同场景下的发布和测试,可以满足灰度发布、蓝绿发布、A/B测试等业务场景。具体实现过程如下:为服务创建两个Ingress,一个为常规Ingress,另一个为带nginx.ingress.kubernetes.io/canary: "true"注解的Ingress,称为Canary Ingress;为Canary Ingress配置流量切分策略Annotation,两个Ingress相互配合,即可实现多种场景的发布和测试。Nginx Ingress的Annotation支持以下几种规则: - nginx.ingress.kubernetes.io/canary-by-header 基于Header的流量切分,适用于灰度发布。如果请求头中包含指定的header名称,并且值为“always”,就将该请求转发给Canary Ingress定义的对应后端服务。如果值为“never”则不转发,可用于回滚到旧版本。如果为其他值则忽略该annotation,并通过优先级将请求流量分配到其他规则。 - nginx.ingress.kubernetes.io/canary-by-header-value 必须与canary-by-header一起使用,可自定义请求头的取值,包含但不限于“always”或“never”。当请求头的值命中指定的自定义值时,请求将会转发给Canary Ingress定义的对应后端服务,如果是其他值则忽略该annotation,并通过优先级将请求流量分配到其他规则。 - nginx.ingress.kubernetes.io/canary-by-header-pattern 与canary-by-header-value类似,唯一区别是该annotation用正则表达式匹配请求头的值,而不是某一个固定值。如果该annotation与canary-by-header-value同时存在,该annotation将被忽略。 - nginx.ingress.kubernetes.io/canary-by-cookie 基于Cookie的流量切分,适用于灰度发布。与canary-by-header类似,该annotation用于cookie,仅支持“always”和“never”,无法自定义取值。 - nginx.ingress.kubernetes.io/canary-weight 基于服务权重的流量切分,适用于蓝绿部署。表示Canary Ingress所分配流量的百分比,取值范围[0-100]。例如,设置为100,表示所有流量都将转发给Canary Ingress对应的后端服务。 ### 重点注意 > 1. 以上注解规则会按优先级进行评估,优先级为:canary-by-header -> canary-by-cookie -> canary-weight。 > 2. 当Ingress被标记为Canary Ingress时,除了nginx.ingress.kubernetes.io/load-balance和nginx.ingress.kubernetes.io/upstream-hash-by外,所有其他非Canary的注解都将被忽略。 > 3. 相同服务的Canary Ingress(Ingress的YAML文件中host/path相同)仅能够定义一个,从而使后端服务最多支持两个版本。 > 4. 即使流量完全切到了Canary Ingress上,旧版服务仍需存在,否则会出现报错。 > 5. 更多内容请参阅官方文档[Annotations](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#canary)。 ## 部署两个版本服务 在集群中部署两个版本的Nginx服务,并通过Nginx Ingress对外提供七层域名访问。 1. 创建第一个版本的Deployment和Service,本文以app-v1为例。YAML示例如下: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: app-v1 namespace: default spec: replicas: 3 selector: matchLabels: app: app-v1 template: metadata: labels: app: app-v1 spec: containers: - name: nginx image: nginx:1.21.4 imagePullPolicy: IfNotPresent lifecycle: postStart: exec: command: - /bin/sh - -c - "echo \\<b\\>version: v1\\</b\\>, \\<br\\>IP: $(hostname -I), \\<br\\>hostname: $(hostname) > /usr/share/nginx/html/index.html" --- apiVersion: v1 kind: Service metadata: name: app-v1 namespace: default spec: type: ClusterIP ports: - name: http port: 80 targetPort: 80 selector: app: app-v1 ``` 2. 创建第二个版本的Deployment和Service,本文以app-v2为例。YAML示例如下: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: app-v2 namespace: default spec: replicas: 3 selector: matchLabels: app: app-v2 template: metadata: labels: app: app-v2 spec: containers: - name: nginx image: nginx:1.21.4 imagePullPolicy: IfNotPresent lifecycle: postStart: exec: command: - /bin/sh - -c - "echo \\<b\\>version: v2\\</b\\>, \\<br\\>IP: $(hostname -I), \\<br\\>hostname: $(hostname) > /usr/share/nginx/html/index.html" --- apiVersion: v1 kind: Service metadata: name: app-v2 namespace: default spec: type: ClusterIP ports: - name: http port: 80 targetPort: 80 selector: app: app-v2 ``` 3. 查看两个服务的运行状况 ```shell $ kubectl get pod NAME READY STATUS RESTARTS AGE app-v1-68db595855-99j89 1/1 Running 2 25h app-v1-68db595855-l8q6h 1/1 Running 0 25h app-v1-68db595855-z8kwv 1/1 Running 1 25h app-v2-595cf6b7f-8kh74 1/1 Running 0 25h app-v2-595cf6b7f-jbp5q 1/1 Running 2 25h app-v2-595cf6b7f-kspjd 1/1 Running 0 25h ``` 4. 创建Ingress,对外暴露服务,指向app-v1版本的服务。YAML示例如下 ```yaml apiVersion: extensions/v1beta1 kind: Ingress metadata: name: app-v1 namespace: default annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: ingressClassName: nginx rules: - host: www.ecloud.com http: paths: - path: /nginx(/|)(.*) backend: serviceName: app-v1 servicePort: 80 ``` 5. 验证服务 - 命令行验证 ```shell $ for i in `seq 3`;do curl www.ecloud.com/nginx ;sleep 1 ;done <b>version: v1</b>, <br>IP: 20.0.122.151 , <br>hostname: app-v1-68db595855-l8q6h <b>version: v1</b>, <br>IP: 20.0.58.213 , <br>hostname: app-v1-68db595855-z8kwv <b>version: v1</b>, <br>IP: 20.0.32.154 , <br>hostname: app-v1-68db595855-99j89 ``` - 浏览器验证 ![images01](../images/Snipaste_2021-12-30_16-21-51.png) ## 灰度发布新版本 基于Header、Cookie和服务权重三种流量切分策略均可实现灰度发布;基于服务权重的流量切分策略,调整新服务权重为100%,即可实现蓝绿发布。您可以在下述示例中了解具体使用方法。 ### 基于客户端Header的流量切分场景 1.创建Canary Ingress,指向新版本的后端服务,并增加annotation。YAML示例如下 ```yaml apiVersion: extensions/v1beta1 kind: Ingress metadata: name: app-v2 namespace: default annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2 nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-by-header: canary nginx.ingress.kubernetes.io/canary-by-header-value: "true" spec: ingressClassName: nginx rules: - host: www.ecloud.com http: paths: - path: /nginx(/|)(.*) backend: serviceName: app-v2 servicePort: 80 ``` 2.验证服务 - 命令行验证 ```shell $ for i in `seq 3`;do curl -s -H "canary:true" www.ecloud.com/nginx ;sleep 1 ;done <b>version: v2</b>, <br>IP: 20.0.32.155 , <br>hostname: app-v2-595cf6b7f-jbp5q <b>version: v2</b>, <br>IP: 20.0.122.152 , <br>hostname: app-v2-595cf6b7f-8kh74 <b>version: v2</b>, <br>IP: 20.0.135.154 , <br>hostname: app-v2-595cf6b7f-kspjd ``` - 浏览器验证 ![images01](../images/Snipaste_2021-12-30_16-40-17.png) ![images01](../images/Snipaste_2021-12-30_16-41-51.png) > Google浏览器安装 `Requestly: Modify Headers, Mock API, Redirect` 插件,可以添加 `header` 键值对 ### 基于客户端Cookie的流量切分场景 1.创建Canary Ingress,指向新版本的后端服务,并增加annotation。YAML示例如下 ```yaml apiVersion: extensions/v1beta1 kind: Ingress metadata: name: app-v2 namespace: default annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2 nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-by-cookie: canary spec: ingressClassName: nginx rules: - host: www.ecloud.com http: paths: - path: /nginx(/|)(.*) backend: serviceName: app-v2 servicePort: 80 ``` 2.验证服务 - 命令行验证 ```shell $ for i in `seq 3`;do curl -s -H "Cookie: canary=always" -H "Host: www.ecloud.com" http://192.168.31.188/nginx ;sleep 1 ;done <b>version: v2</b>, <br>IP: 20.0.32.155 , <br>hostname: app-v2-595cf6b7f-jbp5q <b>version: v2</b>, <br>IP: 20.0.122.152 , <br>hostname: app-v2-595cf6b7f-8kh74 <b>version: v2</b>, <br>IP: 20.0.135.154 , <br>hostname: app-v2-595cf6b7f-kspjd ``` - 浏览器验证 ![images01](../images/Snipaste_2021-12-30_16-50-58.png) ![images01](../images/Snipaste_2021-12-30_16-52-20.png) > Google浏览器安装 `EditCookie` 插件,可以添加 `cookie` ### 基于服务权重的流量切分场景 1.创建Canary Ingress,指向新版本的后端服务,并增加annotation。YAML示例如下 ```yaml apiVersion: extensions/v1beta1 kind: Ingress metadata: name: app-v2 namespace: default annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2 nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-weight: "30" spec: ingressClassName: nginx rules: - host: www.ecloud.com http: paths: - path: /nginx(/|)(.*) backend: serviceName: app-v2 servicePort: 80 ``` 2.验证服务 ```shell $for i in `seq 10`;do curl -s -H "Cookie: canary=always" www.ecloud.com/nginx ;sleep 1 ;done <b>version: v1</b>, <br>IP: 20.0.32.154 , <br>hostname: app-v1-68db595855-99j89 <b>version: v1</b>, <br>IP: 20.0.32.154 , <br>hostname: app-v1-68db595855-99j89 <b>version: v1</b>, <br>IP: 20.0.32.154 , <br>hostname: app-v1-68db595855-99j89 <b>version: v1</b>, <br>IP: 20.0.58.213 , <br>hostname: app-v1-68db595855-z8kwv <b>version: v2</b>, <br>IP: 20.0.32.155 , <br>hostname: app-v2-595cf6b7f-jbp5q <b>version: v1</b>, <br>IP: 20.0.58.213 , <br>hostname: app-v1-68db595855-z8kwv <b>version: v1</b>, <br>IP: 20.0.122.151 , <br>hostname: app-v1-68db595855-l8q6h <b>version: v2</b>, <br>IP: 20.0.135.154 , <br>hostname: app-v2-595cf6b7f-kspjd <b>version: v1</b>, <br>IP: 20.0.58.213 , <br>hostname: app-v1-68db595855-z8kwv <b>version: v2</b>, <br>IP: 20.0.122.152 , <br>hostname: app-v2-595cf6b7f-8kh74 ``` > 注意: > 基于权重(30%)进行流量切分后,访问到新版本的概率接近30%,流量比例可能会有小范围的浮动,这属于正常现象。 将百分比拉到100,则成功实现了蓝绿发布。 ## 附件 Google浏览器插件 `Modify Headers`、`EditCookie` 上传到百度云盘上,有需要的的话,请到百度云上下载。 > 链接:https://pan.baidu.com/s/1fdQxfJeV2uFZ2KrKuu8m4Q 提取码:q9dx 安装方法: 打开 `Google浏览器` ,输入 `chrome://extensions/` 进入到扩展程序,然后把下载好的插件拖拽到浏览器即可。