本文將介紹如何使用 Nginx Ingress 實現金絲雀發佈,從使用場景分析,到用法詳解,再到上手實踐。nginx
集羣中須要部署 Nginx Ingress 做爲 Ingress Controller,而且對外暴露了統一的流量入口,參考 在 TKE 上部署 Nginx Ingress。git
使用 Nginx Ingress 來實現金絲雀發佈,能夠用在哪些場景呢?這個主要看使用什麼策略進行流量切分,目前 Nginx Ingress 支持基於 Header、Cookie 和服務權重這 3 種流量切分的策略,基於它們能夠實現如下兩種發佈場景。github
假設線上運行了一套對外提供 7 層服務的 Service A 服務,後來開發了個新版本 Service A' 想要上線,但又不想直接替換掉原來的 Service A,但願先灰度一小部分用戶,等運行一段時間足夠穩定了再逐漸全量上線新版本,最後平滑下線舊版本。這個時候就能夠利用 Nginx Ingress 基於 Header 或 Cookie 進行流量切分的策略來發布,業務使用 Header 或 Cookie 來標識不一樣類型的用戶,咱們經過配置 Ingress 來實現讓帶有指定 Header 或 Cookie 的請求被轉發到新版本,其它的仍然轉發到舊版本,從而實現將新版本灰度給部分用戶:正則表達式
假設線上運行了一套對外提供 7 層服務的 Service B 服務,後來修復了一些問題,須要灰度上線一個新版本 Service B',但又不想直接替換掉原來的 Service B,而是讓先切 10% 的流量到新版本,等觀察一段時間穩定後再逐漸加大新版本的流量比例直至徹底替換舊版本,最後再滑下線舊版本,從而實現切必定比例的流量給新版本:後端
咱們經過給 Ingress 資源指定 Nginx Ingress 所支持的一些 annotation 能夠實現金絲雀發佈,須要給服務建立兩個 Ingress,一個正常的 Ingress,另外一個是帶 nginx.ingress.kubernetes.io/canary: "true"
這個固定的 annotation 的 Ingress,咱們姑且稱它爲 Canary Ingress,通常表明新版本的服務,結合另外針對流量切分策略的 annotation 一塊兒配置便可實現多種場景的金絲雀發佈,如下對這些 annotation 詳細介紹下:centos
nginx.ingress.kubernetes.io/canary-by-header
: 表示若是請求頭中包含這裏指定的 header 名稱,而且值爲 always
的話,就將該請求轉發給該 Ingress 定義的對應後端服務;若是值爲 never
就不轉發,能夠用於回滾到舊版;若是是其它值則忽略該 annotation。nginx.ingress.kubernetes.io/canary-by-header-value
: 這個能夠做爲 canary-by-header
的補充,容許指定請求頭的值能夠自定義成其它值,再也不只能是 always
或 never
;當請求頭的值命中這裏的自定義值時,請求將會轉發給該 Ingress 定義的對應後端服務,若是是其它值則將會忽略該 annotation。nginx.ingress.kubernetes.io/canary-by-header-pattern
: 這個與上面的 canary-by-header-value
相似,惟一的區別是它是用正則表達式對來匹配請求頭的值,而不是隻固定某一個值;須要注意的是,若是它與 canary-by-header-value
同時存在,這個 annotation 將會被忽略。nginx.ingress.kubernetes.io/canary-by-cookie
: 這個與 canary-by-header
相似,只是這個用於 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
api
注意: 當 Ingress 被標記爲 Canary Ingress 時,除了nginx.ingress.kubernetes.io/load-balance
和 nginx.ingress.kubernetes.io/upstream-hash-by
以外,全部其餘非 Canary 註釋都將被忽略。cookie
下面咱們給出一些例子,讓你快速上手 Nginx Ingress 的金絲雀發佈,環境爲 TKE 集羣。app
本文的示例將使用 yaml 的方式部署工做負載和建立 Service,有兩種操做方式。curl
方式一:在 TKE 或 EKS 控制檯右上角點擊 YAML 建立資源
,而後將本文示例的 yaml 粘貼進去:
方式二:將示例的 yaml 保存成文件,而後使用 kubectl 指定 yaml 文件來建立,如: kubectl apply -f xx.yaml
。
這裏以簡單的 nginx 爲例,先部署一個 v1 版本:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-v1 spec: replicas: 1 selector: matchLabels: app: nginx version: v1 template: metadata: labels: app: nginx version: v1 spec: containers: - name: nginx image: "openresty/openresty:centos" ports: - name: http protocol: TCP containerPort: 80 volumeMounts: - mountPath: /usr/local/openresty/nginx/conf/nginx.conf name: config subPath: nginx.conf volumes: - name: config configMap: name: nginx-v1 --- apiVersion: v1 kind: ConfigMap metadata: labels: app: nginx version: v1 name: nginx-v1 data: nginx.conf: |- worker_processes 1; events { accept_mutex on; multi_accept on; use epoll; worker_connections 1024; } http { ignore_invalid_headers off; server { listen 80; location / { access_by_lua ' local header_str = ngx.say("nginx-v1") '; } } } --- apiVersion: v1 kind: Service metadata: name: nginx-v1 spec: type: ClusterIP ports: - port: 80 protocol: TCP name: http selector: app: nginx version: v1
再部署一個 v2 版本:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-v2 spec: replicas: 1 selector: matchLabels: app: nginx version: v2 template: metadata: labels: app: nginx version: v2 spec: containers: - name: nginx image: "openresty/openresty:centos" ports: - name: http protocol: TCP containerPort: 80 volumeMounts: - mountPath: /usr/local/openresty/nginx/conf/nginx.conf name: config subPath: nginx.conf volumes: - name: config configMap: name: nginx-v2 --- apiVersion: v1 kind: ConfigMap metadata: labels: app: nginx version: v2 name: nginx-v2 data: nginx.conf: |- worker_processes 1; events { accept_mutex on; multi_accept on; use epoll; worker_connections 1024; } http { ignore_invalid_headers off; server { listen 80; location / { access_by_lua ' local header_str = ngx.say("nginx-v2") '; } } } --- apiVersion: v1 kind: Service metadata: name: nginx-v2 spec: type: ClusterIP ports: - port: 80 protocol: TCP name: http selector: app: nginx version: v2
能夠在控制檯看到部署的狀況:
再建立一個 Ingress,對外暴露服務,指向 v1 版本的服務:
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: nginx annotations: kubernetes.io/ingress.class: nginx spec: rules: - host: canary.example.com http: paths: - backend: serviceName: nginx-v1 servicePort: 80 path: /
訪問驗證一下:
$ curl -H "Host: canary.example.com" http://EXTERNAL-IP # EXTERNAL-IP 替換爲 Nginx Ingress 自身對外暴露的 IP nginx-v1
建立 Canary Ingress,指定 v2 版本的後端服務,且加上一些 annotation,實現僅將帶有名爲 Region 且值爲 cd 或 sz 的請求頭的請求轉發給當前 Canary Ingress,模擬灰度新版本給成都和深圳地域的用戶:
apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-by-header: "Region" nginx.ingress.kubernetes.io/canary-by-header-pattern: "cd|sz" name: nginx-canary spec: rules: - host: canary.example.com http: paths: - backend: serviceName: nginx-v2 servicePort: 80 path: /
測試訪問:
$ curl -H "Host: canary.example.com" -H "Region: cd" http://EXTERNAL-IP # EXTERNAL-IP 替換爲 Nginx Ingress 自身對外暴露的 IP nginx-v2 $ curl -H "Host: canary.example.com" -H "Region: bj" http://EXTERNAL-IP nginx-v1 $ curl -H "Host: canary.example.com" -H "Region: cd" http://EXTERNAL-IP nginx-v2 $ curl -H "Host: canary.example.com" http://EXTERNAL-IP nginx-v1
能夠看到,只有 header Region
爲 cd 或 sz 的請求才由 v2 版本服務響應。
與前面 Header 相似,不過使用 Cookie 就沒法自定義 value 了,這裏以模擬灰度成都地域用戶爲例,僅將帶有名爲 user_from_cd
的 cookie 的請求轉發給當前 Canary Ingress 。先刪除前面基於 Header 的流量切分的 Canary Ingress,而後建立下面新的 Canary Ingress:
apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-by-cookie: "user_from_cd" name: nginx-canary spec: rules: - host: canary.example.com http: paths: - backend: serviceName: nginx-v2 servicePort: 80 path: /
測試訪問:
$ curl -s -H "Host: canary.example.com" --cookie "user_from_cd=always" http://EXTERNAL-IP # EXTERNAL-IP 替換爲 Nginx Ingress 自身對外暴露的 IP nginx-v2 $ curl -s -H "Host: canary.example.com" --cookie "user_from_bj=always" http://EXTERNAL-IP nginx-v1 $ curl -s -H "Host: canary.example.com" http://EXTERNAL-IP nginx-v1
能夠看到,只有 cookie user_from_cd
爲 always
的請求才由 v2 版本的服務響應。
基於服務權重的 Canary Ingress 就簡單了,直接定義須要導入的流量比例,這裏以導入 10% 流量到 v2 版本爲例 (若是有,先刪除以前的 Canary Ingress):
apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-weight: "10" name: nginx-canary spec: rules: - host: canary.example.com http: paths: - backend: serviceName: nginx-v2 servicePort: 80 path: /
測試訪問:
$ for i in {1..10}; do curl -H "Host: canary.example.com" http://EXTERNAL-IP; done; nginx-v1 nginx-v1 nginx-v1 nginx-v1 nginx-v1 nginx-v1 nginx-v2 nginx-v1 nginx-v1 nginx-v1
能夠看到,大概只有十分之一的概率由 v2 版本的服務響應,符合 10% 服務權重的設置。
雖然咱們使用 Nginx Ingress 實現了幾種不一樣姿式的金絲雀發佈,但還存在一些缺陷:
本文全方位總結了 Nginx Ingress 的金絲雀發佈用法,雖然 Nginx Ingress 在金絲雀發佈這方面的能力有限,而且還存在一些缺陷,但基本也能覆蓋一些常見的場景,若是集羣中使用了 Nginx Ingress,而且發佈的需求也不復雜,能夠考慮使用這種方案。
【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公衆號,及時獲取更多幹貨!!