基于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閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容