基于Ingress-Nginx 实现灰度发布

本文来自于我的公众号程序猿天璇基于Ingress-Nginx 实现灰度发布,转载请保留链接 ;)

灰度发布,是指在黑与白之间,能够平滑过渡的一种发布方式。通俗来说,即让产品的迭代能够按照不同的灰度策略对新版本进行线上环境的测试,灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以对新版本进行测试、发现和调整问题,以保证其影响度。

而KubeSphere在实现灰度发布中提供两种方式,一种是在 Bookinfo 微服务的灰度发布示例中,KubeSphere 基于 Istio 对 Bookinfo 微服务示例应用实现灰度发布。另一种则是使用应用路由 (Ingress) 和项目网关 (Ingress Controller) 实现灰度发布。

Ingress-Nginx (0.21.0 版本) 中,引入了一个新的 Canary 功能,可用于为网关入口配置多个后端服务,还可以使用指定的 annotation 来控制多个后端服务之间的流量分配。 KubeSphere 在 2.0.2 的版本 中,升级了项目网关 (Ingress Controller) 版本至 0.24.1,支持基于 Ingress-Nginx 的灰度发布。

说明: 本文用到的示例 yaml 源文件及代码已在KubeSphere企业空间中demo-workspace实现,后期版本控制发布可以在其基础上拓展实现。

Ingress-Nginx Annotation 简介

KubeSphere 基于 Nginx Ingress Controller 实现了项目的网关,作为项目对外的流量入口和项目中各个服务的反向代理。而 Ingress-Nginx 支持配置 Ingress Annotations 来实现不同场景下的灰度发布和测试,可以满足金丝雀发布、蓝绿部署与 A/B 测试等业务场景。

Nginx Annotations 支持以下 4 种 Canary 规则:

  • nginx.ingress.kubernetes.io/canary-by-header:基于 Request Header 的流量切分,适用于灰度发布以及 A/B 测试。当 Request Header 设置为 always 时,请求将会被一直发送到 Canary 版本;当 Request Header 设置为 never 时,请求不会被发送到 Canary 入口;对于任何其他 Header 值,将忽略 Header,并通过优先级将请求与其他规则进行优先级的比较。

  • nginx.ingress.kubernetes.io/canary-by-header-value:要匹配的 Request Header 的值,用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。当 Request Header 设置为此值时,它将被路由到 Canary 入口。该规则允许用户自定义 Request Header 的值,必须与上一个 annotation (即:canary-by-header) 一起使用。

  • nginx.ingress.kubernetes.io/canary-weight:基于服务权重的流量切分,适用于蓝绿部署,权重范围 0 - 100 按百分比将请求路由到 Canary Ingress 中指定的服务。权重为 0 意味着该规则不会向 Canary 入口的服务发送任何请求,权重为 100 意味着所有请求都将被发送到 Canary 入口。

  • nginx.ingress.kubernetes.io/canary-by-cookie:基于 cookie 的流量切分,适用于灰度发布与 A/B 测试。用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务的cookie。当 cookie 值设置为 always 时,它将被路由到 Canary 入口;当 cookie 值设置为 never 时,请求不会被发送到 Canary 入口;对于任何其他值,将忽略 cookie 并将请求与其他规则进行优先级的比较。

注意:规则按优先顺序进行如下排序:

canary-by-header - > canary-by-cookie - > canary-weight

把以上的四个 annotation 规则可以总体划分为以下两类:

  • 基于权重的 Canary 规则
基于权重的 Canary 规则
  • 基于用户请求的 Canary 规则
基于用户请求的 Canary 规则

前提条件

  • 使用 project-admin 登陆创建 ingress-demo 项目
  • 使用 admin 用户登陆,在KubeSphere 右下角的工具箱打开 web kubectl

第一步:创建项目和 Production 版本的应用

1.1. 在 KubeSphere 中创建一个企业空间 (workspace) 和项目 (namespace) ,可参考 多租户管理快速入门。如下已创建了一个示例项目。

web kubectl

1.2. 本文为了快速创建应用,使用集群的 admin 账号登录,在项目中创建工作负载和服务时可通过 编辑 yaml 的方式,或使用 KubeSphere 右下角的工具箱打开 web kubectl 并使用以下命令和 yaml 文件创建一个 Production 版本的应用并暴露给集群外访问。如下创建 Production 版本的 deploymentservice

web kubectl
$ kubectl apply -f production.yaml -n ingress-demo
deployment.extensions/production created
service/production created

其中用到的 yaml 文件如下:

production.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: production
  labels:
    app: production
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
  labels:
    app: production
spec:
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
  selector:
    app: production

1.3. 创建 Production 版本的应用路由 (Ingress)。

$ kubectl apply -f production.ingress -n ingress-demo
ingress.extensions/production created

其中用到的 yaml 文件如下:

production.ingress

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: production
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
  - host: kubesphere.io
    http:
      paths:
      - backend:
          serviceName: production
          servicePort: 80

第二步:访问 Production 版本的应用

2.1. 此时,在 KubeSphere UI 的企业空间 demo-workspace 下,可以看到 ingress-demo 项目下的所有资源。
ji
Deployment

工作负载

Service

服务

Route (Ingress)

应用路由

2.2. 访问 Production 版本的应用需确保当前项目已开启了网关,在外网访问下打开网关,类型为 NodePort。

外网访问

2.3. 如下访问 Production 版本的应用,注意以下命令需要在 SSH 客户端中执行。

$ curl --resolve kubesphere.io:31740:192.168.100.210 kubesphere.io:31740
# 注意,加上 --resolve 参数则无需在本地配置 /etc/hosts 中的 IP 与域名映射,否则需要预先在本地配置域名映射,其中 192.168.100.210 是项目内的网关地址。

  
Hostname: production-7946d7969c-pf8gq

Pod Information:
        node name:      k8snode1
        pod name:       production-7946d7969c-pf8gq
        pod namespace:  ingress-demo
        pod IP: 10.101.249.5

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

Request Information:
        client_address=10.101.249.7
        method=GET
        real path=/
        query=
        request_version=1.1
        request_scheme=http
        request_uri=http://kubesphere.io:8080/

Request Headers:
        accept=*/*
        host=kubesphere.io:31740
        user-agent=curl/7.66.0
        x-forwarded-for=10.101.16.128
        x-forwarded-host=kubesphere.io:31740
        x-forwarded-port=80
        x-forwarded-proto=http
        x-original-uri=/
        x-real-ip=10.101.16.128
        x-request-id=2da8fdbddfa5cfeda25e1ae1c5189b35
        x-scheme=http

Request Body:
        -no body in request-  

第三步:创建 Canary 版本

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

第四步:Ingress-Nginx Annotation 规则

基于权重 (Weight)

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

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

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

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: canary
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "30"
spec:
  rules:
  - host: kubesphere.io
    http:
      paths:
      - backend:
          serviceName: canary
          servicePort: 80
应用路由

4.2. 访问应用的域名(在 SSH 中执行以下命令)。

说明:应用的 Canary 版本基于权重 (30%) 进行流量切分后,访问到 Canary 版本的概率接近 30%,流量比例可能会有小范围的浮动。

$ for i in $(seq 1 10); do curl -s --resolve kubesphere.io:31740:192.168.100.210 kubesphere.io:31740 | grep "Hostname"; done
Weight

基于 Request Header

4.3. 基于 Request Header 进行流量切分的典型应用场景即灰度发布或 A/B 测试场景。参考以下截图,在 KubeSphere 给 Canary 版本的应用路由 (Ingress) 新增一条 annotation nginx.ingress.kubernetes.io/canary-by-header: canary (这里的 annotation 的 value 可以是任意值),使当前的 Ingress 实现基于 Request Header 进行流量切分。

说明:规则按优先顺序 canary-by-header - > canary-by-cookie - > canary-weight 进行如下排序,因此以下情况将忽略原有 canary-weight 的规则。

Request Header

4.4. 在请求中加入不同的 Header 值,再次访问应用的域名。

说明:
举两个例子,如开篇提到的当 Request Header 设置为 neveralways 时,请求将不会一直被发送到 Canary 版本;

对于任何其他 Header 值,将忽略 Header,并通过优先级将请求与其他 Canary 规则进行优先级的比较(如下第二次请求已将基于 30% 权重作为第一优先级)。

$ for i in $(seq 1 10); do curl -s -H "canary: never" --resolve kubesphere.io:31740:192.168.100.210 kubesphere.io:31740 | grep "Hostname"; done
never
$ for i in $(seq 1 10); do curl -s -H "canary: always" --resolve kubesphere.io:31740:192.168.100.210 kubesphere.io:31740 | grep "Hostname"; done
always
$ for i in $(seq 1 10); do curl -s -H "canary: other-value" --resolve kubesphere.io:31740:192.168.100.210  kubesphere.io:31740 | grep "Hostname"; done
other-value

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

canary-by-header

4.6. 如下访问应用的域名,当 Request Header 满足此值时,所有请求被路由到 Canary 版本(该规则允许用户自定义 Request Header 的值)。

$ for i in $(seq 1 10); do curl -s -H "canary: user-value" --resolve kubesphere.io:31740:192.168.100.210 kubesphere.io:31740 | grep "Hostname"; done
user-value

4.7. 如果改成other-value,则匹配的原则不是user-value值,所以会匹配到第三优先级Ingress规则(weight),即权重规则。

$ for i in $(seq 1 10); do curl -s -H "canary: other-value" --resolve kubesphere.io:31740:192.168.100.210  kubesphere.io:31740 | grep "Hostname"; done
other-value

基于 Cookie

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

总结

灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以对新版本进行测试、发现和调整问题,以保证其影响度。本文通过多个示例演示和说明了基于 KubeSphere 使用应用路由 (Ingress) 和项目网关 (Ingress Controller) 实现灰度发布,并详细介绍了 Ingress-Nginx 的四种 Annotation,在未使用 Istio 的情况下,也能借助 Ingress-Nginx 轻松实现灰度发布。

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容