前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >K8S基于ingress-nginx实现灰度发布

K8S基于ingress-nginx实现灰度发布

作者头像
匿名用户的日记
发布2021-12-14 10:59:11
3.1K0
发布2021-12-14 10:59:11
举报
文章被收录于专栏:匿名用户的日记

注解说明

通过给 Ingress 资源指定 Nginx Ingress 所支持的 annotation 可实现金丝雀发布。需给服务创建2个 Ingress,其中1个常规 Ingress,另1个为nginx.ingress.kubernetes.io/canary: "true"· 固定的 annotation 的 Ingress,称为 Canary Ingress。Canary Ingress 一般代表新版本的服务,结合另外针对流量切分策略的 annotation 一起配置即可实现多种场景的金丝雀发布。以下为相关 annotation 的详细介绍:

  • nginx.ingress.kubernetes.io/canary-by-header 表示如果请求头中包含指定的 header 名称,并且值为 always,就将该请求转发给该 Ingress 定义的对应后端服务。如果值为 never 则不转发,可以用于回滚到旧版。如果为其他值则忽略该 annotation。
  • nginx.ingress.kubernetes.io/canary-by-header-value 该 annotation 可以作为 canary-by-header 的补充,可指定请求头为自定义值,包含但不限于 always 或 never。当请求头的值命中指定的自定义值时,请求将会转发给该 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 与 canary-by-header 类似,该 annotation 用于 cookie,仅支持 always 和 never。
  • nginx.ingress.kubernetes.io/canary-weight 表示 Canary Ingress 所分配流量的比例的百分比,取值范围 [0-100]。例如,设置为10,则表示分配10%的流量给 Canary Ingress 对应的后端服务。

说明: 以上规则会按优先顺序进行评估,优先顺序为: canary-by-header -> canary-by-cookie -> canary-weight。 当 Ingress 被标记为 Canary Ingress 时,除了 nginx.ingress.kubernetes.io/load-balancenginx.ingress.kubernetes.io/upstream-hash-by 外,所有其他非 Canary 注释都将被忽略。

可以把以上的四个 annotation 分为三类:

  1. 基于Request Header的流量切分,适用于灰度发布以及AB测试场景
  2. 基于Cookie的流量切分,适用于灰度发布以及AB测试场景
  3. 基于服务权重的流量切分,适用于蓝绿发布场景

总体划分为以下两大类:

  1. 基于权重的 Canary 规则

2. 基于用户请求的 Canary 规则

注意: Ingress-Nginx 实在0.21.0 版本 中,引入的Canary 功能,因此要确保ingress版本OK

部署正式版本服务

首先创建一个 deployment 代表正式版本的服务,编写 yaml 内容如下:

代码语言:javascript
复制
---
apiVersion: v1
kind: Namespace
metadata:
  name: ns-myapp
  labels:
    name: ns-myapp

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: production
  namespace: ns-myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: production
  template:
    metadata:
      labels:
        app: production
    spec:
      containers:
      - name: production
        image: mirrorgooglecontainers/echoserver:1.10
        ports:
        - containerPort: 8080
        env:
          - name: NODE_NAME
            valueFrom:
              fieldRef:
                fieldPath: spec.nodeName
          - name: POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: POD_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
          - name: POD_IP
            valueFrom:
              fieldRef:
                fieldPath: status.podIP
---
apiVersion: v1
kind: Service
metadata:
  name: production
  namespace: ns-myapp
  labels:
    app: production
spec:
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
  selector:
    app: production
    

为这个服务创建 Ingress 路由规则,yaml 文件内容如下:

代码语言:javascript
复制
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: production
  namespace: ns-myapp
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
  - host: ingress.test.com
    http:
      paths:
      - backend:
          serviceName: production
          servicePort: 80
          

应用以上 yaml 文件,创建完成后在 k8s 中查看到如下信息:

代码语言:javascript
复制
[k8s-master ~]# kubectl get ingress -n ns-myapp
NAME         CLASS    HOSTS              ADDRESS        PORTS   AGE
production   <none>   ingress.test.com   10.16.13.201   80      4m25s

[k8s-master ~]# kubectl get pod -n ns-myapp
NAME                          READY   STATUS    RESTARTS   AGE
production-5698c4565c-jmjn5   1/1     Running   0          7m11s

此时在命令行中访问 ingress.test.com 可以看到如下内容:

代码语言:javascript
复制
# curl ingress.test.com

Hostname: production-5698c4565c-jmjn5

Pod Information:
    node name:  dumlog013201
    pod name:   production-5698c4565c-jmjn5
    pod namespace:  ns-myapp
    pod IP: 10.42.0.74

Server values:
    server_version=nginx: 1.13.3 - lua: 10008

Request Information:
    client_address=10.16.13.201
    method=GET
    real path=/
    query=
    request_version=1.1
    request_scheme=http
    request_uri=http://ingress.test.com:8080/

Request Headers:
    accept=*/*
    host=ingress.test.com
    user-agent=curl/7.64.1
    x-forwarded-for=10.2.130.18
    x-forwarded-host=ingress.test.com
    x-forwarded-port=80
    x-forwarded-proto=http
    x-real-ip=10.2.130.18
    x-request-id=3019362be59228ee2284f5737fa39eb1
    x-scheme=http

Request Body:
    -no body in request-

部署 Canary 版本服务

接下来创建一个 Canary 版本的服务,用于作为灰度测试。

参考将上述 Production 版本的 production.yaml 文件,再创建一个 Canary 版本的应用,包括一个 Canary 版本的 deploymentservice (为方便快速演示,仅需将 production.yaml 的 deploymentservice 中的关键字 production 直接替换为 canary,实际场景中可能涉及业务代码变更)。

基于权重的 Canary 规则测试

基于权重的流量切分的典型应用场景就是蓝绿部署,可通过将权重设置为 0 或 100 来实现。例如,可将 Green 版本设置为主要部分,并将 Blue 版本的入口配置为 Canary。最初,将权重设置为 0,因此不会将流量代理到 Blue 版本。一旦新版本测试和验证都成功后,即可将 Blue 版本的权重设置为 100,即所有流量从 Green 版本转向 Blue。

使用以下 canary.ingress 的 yaml 文件再创建一个基于权重的 Canary 版本的应用路由 (Ingress)。

注意:要开启灰度发布机制,首先需设置 nginx.ingress.kubernetes.io/canary: "true" 启用 Canary,以下 Ingress 示例的 Canary 版本使用了基于权重进行流量切分的 annotation 规则,将分配 30% 的流量请求发送至 Canary 版本。

代码语言:javascript
复制
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: canary
  namespace: ns-myapp
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "30"
spec:
  rules:
  - host: ingress.test.com
    http:
      paths:
      - backend:
          serviceName: canary
          servicePort: 80

接下来在命令行中使用如下命令访问域名 ingress.test.com 100次,计算每个版本分配流量的占比:

代码语言:javascript
复制
c=0;p=0;for i in $(seq 100); do result=$(curl -s ingress.test.com | grep  Hostname | awk -F: '{print $2}'); [[ ${result} =~ ^[[:space:]]canary ]] && let c++ || let p++; done;echo "production:${p}; canary:${c};"

可以得到如下结果:

代码语言:javascript
复制
production:73; canary:28;

注意这里权重不是一个精确的百分比,使用过程当中,只是会看到一个近似分布。

基于用户请求的 Canary 规则测试

基于 Resquest Header

基于 Request Header 进行流量切分的典型应用场景即灰度发布或 A/B 测试场景

给 Canary 版本的 Ingress 新增一条 annotation :nginx.ingress.kubernetes.io/canary-by-header: canary(这里的 annotation 的 value 可以是任意值),使当前的 Ingress 实现基于 Request Header 进行流量切分。

将 Canary 版本 Ingress 的 yaml 文件修改为如下内容:

代码语言:javascript
复制
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: canary
  namespace: ns-myapp
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "30"
    nginx.ingress.kubernetes.io/canary-by-header: "canary"
spec:
  rules:
  - host: ingress.test.com
    http:
      paths:
      - backend:
          serviceName: canary
          servicePort: 80

说明:金丝雀规则按优先顺序 canary-by-header - > canary-by-cookie - > canary-weight 进行如下排序,因此上面的 ingress 将忽略原有 canary-weight 的规则。

由于上面的 ingress 规则中没有对 canary-by-header: canary 提供具体的值,也就是 nginx.ingress.kubernetes.io/canary-by-header-value 规则,所以在访问的时候,只可以为 canary 赋值 neveralways,当 header 信息为 canary:never 时,请求将不会发送到 canary 版本;当 header 信息为 canary:always 时,请求将会一直发送到 canary 版本。示例如下:

代码语言:javascript
复制
[k8s-master ~ ]# curl -s -H "canary:never" ingress.test.com | grep Hostname
Hostname: production-5698c4565c-jmjn5
代码语言:javascript
复制
[k8s-master ~ ]# curl -s -H "canary:always" ingress.test.com | grep Hostname
Hostname: canary-79c899d85-992nw

也可以在上一个 annotation (即 canary-by-header)的基础上添加一条 nginx.ingress.kubernetes.io/canary-by-header-value: user-value 。用于通知 Ingress 将匹配到的请求路由到 Canary Ingress 中指定的服务。

将 Canary 版本 Ingress 的 yaml 文件修改为如下内容:

代码语言:javascript
复制
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: canary
  namespace: ns-myapp
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "30"
    nginx.ingress.kubernetes.io/canary-by-header: "canary"
    nginx.ingress.kubernetes.io/canary-by-header-value: "true"
spec:
  rules:
  - host: ingress.test.com
    http:
      paths:
      - backend:
          serviceName: canary
          servicePort: 80

上面的 ingress 规则设置了 header 信息为 canary:true,也就是只有满足这个 header 值时才会路由到 canary 版本。示例如下:

代码语言:javascript
复制
[k8s-master ~ ]# curl -s ingress.test.com | grep Hostname
Hostname: production-5698c4565c-jmjn5


[k8s-master ~ ]# curl -s -H "canary:test" ingress.test.com | grep Hostname
Hostname: production-5698c4565c-jmjn5
代码语言:javascript
复制
[k8s-master ~ ]# curl -s -H "canary:true" ingress.test.com | grep Hostname
Hostname: canary-79c899d85-992nw
基于 Cookie 的 Canary 规则测试

与基于 Request Header 的 annotation 用法规则类似。例如在 A/B 测试场景 下,需要让地域为北京的用户访问 Canary 版本。那么当 cookie 的 annotation 设置为 nginx.ingress.kubernetes.io/canary-by-cookie: "users_from_Beijing",此时后台可对登录的用户请求进行检查,如果该用户访问源来自北京则设置 cookieusers_from_Beijing 的值为 always,这样就可以确保北京的用户仅访问 Canary 版本。

将 Canary 版本 Ingress 的 yaml 文件修改为如下内容:

代码语言:javascript
复制
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: canary
  namespace: ns-myapp
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-by-cookie: "user_from_beijing"
spec:
  rules:
  - host: ingress.test.com
    http:
      paths:
      - backend:
          serviceName: canary
          servicePort: 80

访问示例如下:

代码语言:javascript
复制
[k8s-master ~ ]# curl -s -b "user_from_beijing=always" ingress.test.com | grep Hostname
Hostname: canary-79c899d85-992nw
代码语言:javascript
复制
[k8s-master ~ ]# curl -s -b "user_from_beijing=no" ingress.test.com | grep Hostname
Hostname: production-5698c4565c-jmjn5

多实例Ingress controllers 参考 https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#canary https://cloud.tencent.com/document/product/457/48907

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-03-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 注解说明
  • 部署正式版本服务
  • 部署 Canary 版本服务
    • 基于权重的 Canary 规则测试
      • 基于用户请求的 Canary 规则测试
        • 基于 Resquest Header
        • 基于 Cookie 的 Canary 规则测试
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档