基于Ingress-Nginx 實(shí)現(xiàn)灰度發(fā)布

本文來(lái)自于我的公眾號(hào)程序猿天璇基于Ingress-Nginx 實(shí)現(xiàn)灰度發(fā)布,轉(zhuǎn)載請(qǐng)保留鏈接 ;)

灰度發(fā)布,是指在黑與白之間,能夠平滑過(guò)渡的一種發(fā)布方式。通俗來(lái)說(shuō),即讓產(chǎn)品的迭代能夠按照不同的灰度策略對(duì)新版本進(jìn)行線上環(huán)境的測(cè)試,灰度發(fā)布可以保證整體系統(tǒng)的穩(wěn)定,在初始灰度的時(shí)候就可以對(duì)新版本進(jìn)行測(cè)試、發(fā)現(xiàn)和調(diào)整問(wèn)題,以保證其影響度。

而KubeSphere在實(shí)現(xiàn)灰度發(fā)布中提供兩種方式,一種是在 Bookinfo 微服務(wù)的灰度發(fā)布示例中,KubeSphere 基于 Istio 對(duì) Bookinfo 微服務(wù)示例應(yīng)用實(shí)現(xiàn)灰度發(fā)布。另一種則是使用應(yīng)用路由 (Ingress) 和項(xiàng)目網(wǎng)關(guān) (Ingress Controller) 實(shí)現(xiàn)灰度發(fā)布。

Ingress-Nginx (0.21.0 版本) 中,引入了一個(gè)新的 Canary 功能,可用于為網(wǎng)關(guān)入口配置多個(gè)后端服務(wù),還可以使用指定的 annotation 來(lái)控制多個(gè)后端服務(wù)之間的流量分配。 KubeSphere 在 2.0.2 的版本 中,升級(jí)了項(xiàng)目網(wǎng)關(guān) (Ingress Controller) 版本至 0.24.1,支持基于 Ingress-Nginx 的灰度發(fā)布。

說(shuō)明: 本文用到的示例 yaml 源文件及代碼已在KubeSphere企業(yè)空間中demo-workspace實(shí)現(xiàn),后期版本控制發(fā)布可以在其基礎(chǔ)上拓展實(shí)現(xiàn)。

Ingress-Nginx Annotation 簡(jiǎn)介

KubeSphere 基于 Nginx Ingress Controller 實(shí)現(xiàn)了項(xiàng)目的網(wǎng)關(guān),作為項(xiàng)目對(duì)外的流量入口和項(xiàng)目中各個(gè)服務(wù)的反向代理。而 Ingress-Nginx 支持配置 Ingress Annotations 來(lái)實(shí)現(xiàn)不同場(chǎng)景下的灰度發(fā)布和測(cè)試,可以滿足金絲雀發(fā)布、藍(lán)綠部署與 A/B 測(cè)試等業(yè)務(wù)場(chǎng)景。

Nginx Annotations 支持以下 4 種 Canary 規(guī)則:

  • nginx.ingress.kubernetes.io/canary-by-header:基于 Request Header 的流量切分,適用于灰度發(fā)布以及 A/B 測(cè)試。當(dāng) Request Header 設(shè)置為 always 時(shí),請(qǐng)求將會(huì)被一直發(fā)送到 Canary 版本;當(dāng) Request Header 設(shè)置為 never 時(shí),請(qǐng)求不會(huì)被發(fā)送到 Canary 入口;對(duì)于任何其他 Header 值,將忽略 Header,并通過(guò)優(yōu)先級(jí)將請(qǐng)求與其他規(guī)則進(jìn)行優(yōu)先級(jí)的比較。

  • nginx.ingress.kubernetes.io/canary-by-header-value:要匹配的 Request Header 的值,用于通知 Ingress 將請(qǐng)求路由到 Canary Ingress 中指定的服務(wù)。當(dāng) Request Header 設(shè)置為此值時(shí),它將被路由到 Canary 入口。該規(guī)則允許用戶自定義 Request Header 的值,必須與上一個(gè) annotation (即:canary-by-header) 一起使用。

  • nginx.ingress.kubernetes.io/canary-weight:基于服務(wù)權(quán)重的流量切分,適用于藍(lán)綠部署,權(quán)重范圍 0 - 100 按百分比將請(qǐng)求路由到 Canary Ingress 中指定的服務(wù)。權(quán)重為 0 意味著該規(guī)則不會(huì)向 Canary 入口的服務(wù)發(fā)送任何請(qǐng)求,權(quán)重為 100 意味著所有請(qǐng)求都將被發(fā)送到 Canary 入口。

  • nginx.ingress.kubernetes.io/canary-by-cookie:基于 cookie 的流量切分,適用于灰度發(fā)布與 A/B 測(cè)試。用于通知 Ingress 將請(qǐng)求路由到 Canary Ingress 中指定的服務(wù)的cookie。當(dāng) cookie 值設(shè)置為 always 時(shí),它將被路由到 Canary 入口;當(dāng) cookie 值設(shè)置為 never 時(shí),請(qǐng)求不會(huì)被發(fā)送到 Canary 入口;對(duì)于任何其他值,將忽略 cookie 并將請(qǐng)求與其他規(guī)則進(jìn)行優(yōu)先級(jí)的比較。

注意:規(guī)則按優(yōu)先順序進(jìn)行如下排序:

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

把以上的四個(gè) annotation 規(guī)則可以總體劃分為以下兩類(lèi):

  • 基于權(quán)重的 Canary 規(guī)則
基于權(quán)重的 Canary 規(guī)則
  • 基于用戶請(qǐng)求的 Canary 規(guī)則
基于用戶請(qǐng)求的 Canary 規(guī)則

前提條件

  • 使用 project-admin 登陸創(chuàng)建 ingress-demo 項(xiàng)目
  • 使用 admin 用戶登陸,在KubeSphere 右下角的工具箱打開(kāi) web kubectl

第一步:創(chuàng)建項(xiàng)目和 Production 版本的應(yīng)用

1.1. 在 KubeSphere 中創(chuàng)建一個(gè)企業(yè)空間 (workspace) 和項(xiàng)目 (namespace) ,可參考 多租戶管理快速入門(mén)。如下已創(chuàng)建了一個(gè)示例項(xiàng)目。

web kubectl

1.2. 本文為了快速創(chuàng)建應(yīng)用,使用集群的 admin 賬號(hào)登錄,在項(xiàng)目中創(chuàng)建工作負(fù)載和服務(wù)時(shí)可通過(guò) 編輯 yaml 的方式,或使用 KubeSphere 右下角的工具箱打開(kāi) web kubectl 并使用以下命令和 yaml 文件創(chuàng)建一個(gè) Production 版本的應(yīng)用并暴露給集群外訪問(wèn)。如下創(chuàng)建 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. 創(chuàng)建 Production 版本的應(yīng)用路由 (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

第二步:訪問(wèn) Production 版本的應(yīng)用

2.1. 此時(shí),在 KubeSphere UI 的企業(yè)空間 demo-workspace 下,可以看到 ingress-demo 項(xiàng)目下的所有資源。
ji
Deployment

工作負(fù)載

Service

服務(wù)

Route (Ingress)

應(yīng)用路由

2.2. 訪問(wèn) Production 版本的應(yīng)用需確保當(dāng)前項(xiàng)目已開(kāi)啟了網(wǎng)關(guān),在外網(wǎng)訪問(wèn)下打開(kāi)網(wǎng)關(guān),類(lèi)型為 NodePort。

外網(wǎng)訪問(wèn)

2.3. 如下訪問(wèn) Production 版本的應(yīng)用,注意以下命令需要在 SSH 客戶端中執(zhí)行。

$ curl --resolve kubesphere.io:31740:192.168.100.210 kubesphere.io:31740
# 注意,加上 --resolve 參數(shù)則無(wú)需在本地配置 /etc/hosts 中的 IP 與域名映射,否則需要預(yù)先在本地配置域名映射,其中 192.168.100.210 是項(xiàng)目?jī)?nèi)的網(wǎng)關(guān)地址。

  
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-  

第三步:創(chuàng)建 Canary 版本

參考將上述 Production 版本的 production.yaml 文件,再創(chuàng)建一個(gè) Canary 版本的應(yīng)用,包括一個(gè) Canary 版本的 deploymentservice (為方便快速演示,僅需將 production.yaml 的 deployment 和 service 中的關(guān)鍵字 production 直接替換為 canary,實(shí)際場(chǎng)景中可能涉及業(yè)務(wù)代碼變更)。

第四步:Ingress-Nginx Annotation 規(guī)則

基于權(quán)重 (Weight)

基于權(quán)重的流量切分的典型應(yīng)用場(chǎng)景就是藍(lán)綠部署,可通過(guò)將權(quán)重設(shè)置為 0 或 100 來(lái)實(shí)現(xiàn)。例如,可將 Green 版本設(shè)置為主要部分,并將 Blue 版本的入口配置為 Canary。最初,將權(quán)重設(shè)置為 0,因此不會(huì)將流量代理到 Blue 版本。一旦新版本測(cè)試和驗(yàn)證都成功后,即可將 Blue 版本的權(quán)重設(shè)置為 100,即所有流量從 Green 版本轉(zhuǎn)向 Blue。

4.1. 使用以下 canary.ingress 的 yaml 文件再創(chuàng)建一個(gè)基于權(quán)重的 Canary 版本的應(yīng)用路由 (Ingress)。

注意:要開(kāi)啟灰度發(fā)布機(jī)制,首先需設(shè)置 nginx.ingress.kubernetes.io/canary: "true" 啟用 Canary,以下 Ingress 示例的 Canary 版本使用了基于權(quán)重進(jìn)行流量切分的 annotation 規(guī)則,將分配 30% 的流量請(qǐng)求發(fā)送至 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
應(yīng)用路由

4.2. 訪問(wèn)應(yīng)用的域名(在 SSH 中執(zhí)行以下命令)。

說(shuō)明:應(yīng)用的 Canary 版本基于權(quán)重 (30%) 進(jìn)行流量切分后,訪問(wèn)到 Canary 版本的概率接近 30%,流量比例可能會(huì)有小范圍的浮動(dòng)。

$ 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 進(jìn)行流量切分的典型應(yīng)用場(chǎng)景即灰度發(fā)布或 A/B 測(cè)試場(chǎng)景。參考以下截圖,在 KubeSphere 給 Canary 版本的應(yīng)用路由 (Ingress) 新增一條 annotation nginx.ingress.kubernetes.io/canary-by-header: canary (這里的 annotation 的 value 可以是任意值),使當(dāng)前的 Ingress 實(shí)現(xiàn)基于 Request Header 進(jìn)行流量切分。

說(shuō)明:規(guī)則按優(yōu)先順序 canary-by-header - > canary-by-cookie - > canary-weight 進(jìn)行如下排序,因此以下情況將忽略原有 canary-weight 的規(guī)則。

Request Header

4.4. 在請(qǐng)求中加入不同的 Header 值,再次訪問(wèn)應(yīng)用的域名。

說(shuō)明:
舉兩個(gè)例子,如開(kāi)篇提到的當(dāng) Request Header 設(shè)置為 neveralways 時(shí),請(qǐng)求將不會(huì)一直被發(fā)送到 Canary 版本;

對(duì)于任何其他 Header 值,將忽略 Header,并通過(guò)優(yōu)先級(jí)將請(qǐng)求與其他 Canary 規(guī)則進(jìn)行優(yōu)先級(jí)的比較(如下第二次請(qǐng)求已將基于 30% 權(quán)重作為第一優(yōu)先級(jí))。

$ 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. 此時(shí)可以在上一個(gè) annotation (即 canary-by-header)的基礎(chǔ)上添加一條 nginx.ingress.kubernetes.io/canary-by-header-value: user-value 。用于通知 Ingress 將請(qǐng)求路由到 Canary Ingress 中指定的服務(wù)。

canary-by-header

4.6. 如下訪問(wèn)應(yīng)用的域名,當(dāng) Request Header 滿足此值時(shí),所有請(qǐng)求被路由到 Canary 版本(該規(guī)則允許用戶自定義 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值,所以會(huì)匹配到第三優(yōu)先級(jí)Ingress規(guī)則(weight),即權(quán)重規(guī)則。

$ 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 用法規(guī)則類(lèi)似。例如在 A/B 測(cè)試場(chǎng)景 下,需要讓地域?yàn)楸本┑挠脩粼L問(wèn) Canary 版本。那么當(dāng) cookie 的 annotation 設(shè)置為 nginx.ingress.kubernetes.io/canary-by-cookie: "users_from_Beijing",此時(shí)后臺(tái)可對(duì)登錄的用戶請(qǐng)求進(jìn)行檢查,如果該用戶訪問(wèn)源來(lái)自北京則設(shè)置 cookie users_from_Beijing 的值為 always,這樣就可以確保北京的用戶僅訪問(wèn) Canary 版本。

總結(jié)

灰度發(fā)布可以保證整體系統(tǒng)的穩(wěn)定,在初始灰度的時(shí)候就可以對(duì)新版本進(jìn)行測(cè)試、發(fā)現(xiàn)和調(diào)整問(wèn)題,以保證其影響度。本文通過(guò)多個(gè)示例演示和說(shuō)明了基于 KubeSphere 使用應(yīng)用路由 (Ingress) 和項(xiàng)目網(wǎng)關(guān) (Ingress Controller) 實(shí)現(xiàn)灰度發(fā)布,并詳細(xì)介紹了 Ingress-Nginx 的四種 Annotation,在未使用 Istio 的情況下,也能借助 Ingress-Nginx 輕松實(shí)現(xiàn)灰度發(fā)布。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容