當對服務進行版本更新升級時,需要使用到滾動升級、分批暫停發布、藍綠發布以及灰度發布等發布方式。本文將介紹在ACK集群中如何通過Nginx Ingress Controller來實現應用服務的灰度發布。
背景信息
灰度及藍綠發布是為新版本創建一個與老版本完全一致的生產環境,在不影響老版本的前提下,按照一定的規則把部分流量切換到新版本,當新版本試運行一段時間沒有問題后,將用戶的全量流量從老版本遷移至新版本。
其中藍綠發布就是一種灰度發布方式,一部分用戶繼續使用老版本的服務,將一部分用戶的流量切換到新版本,如果新版本運行穩定,則逐步將所有用戶遷移到新版本。
容器服務ACK控制臺調整了灰度發布功能的用法,分為兩種。
canary-*注解方式:使用
canary-*
Annotation配置藍綠發布與灰度發布,canary-*
Annotation是社區官方實現的灰度發布方式。service-*注解方式:使用
service-*
Annotation配置藍綠發布與灰度發布。service-*
Annotation是ACK Nginx Ingress Controller早期實現灰度發布的方式。service-match
與service-weight
功能已不再維護,并即將廢棄。service-*
Annotation功能目前仍正常保留,不影響使用。
應用場景
基于客戶端請求的流量切分場景
假設當前線上環境,您已經有一套服務Service A對外提供7層服務,此時上線了一些新的特性,需要發布上線一個新的版本Service A'。但又不想直接替換Service A服務,而是希望將請求頭中包含foo=bar
或者Cookie中包含foo=bar
的客戶端請求轉發到Service A'服務中。待運行一段時間穩定后,可將所有的流量從Service A切換到Service A'服務中,再平滑地將Service A服務下線。
基于服務權重的流量切分場景
假設當前線上環境,您已經有一套服務Service B對外提供7層服務,此時修復了一些問題,需要發布上線一個新的版本Service B'。但又不想將所有客戶端流量切換到新版本Service B'中,而是希望將20%的流量切換到新版本Service B'中。待運行一段時間穩定后,再將所有的流量從Service B切換到Service B'服務中,再平滑地將Service B服務下線。
針對以上多種不同的應用發布需求,阿里云容器服務Ingress Controller支持了多種流量切分方式:
基于Request Header的流量切分,適用于灰度發布以及AB測試場景。
基于Cookie的流量切分,適用于灰度發布以及AB測試場景。
基于Query Param的流量切分,適用于灰度發布以及AB測試場景。
基于服務權重的流量切分,適用于藍綠發布場景。
canary-*注解方式
注解說明
Nginx Ingress Controller通過下列canary-*
Annotation來支持應用服務的灰度發布機制。
Annotation | 說明 | 適用的ACK Nginx Ingress Controller版本 |
nginx.ingress.kubernetes.io/canary |
| ≥v0.22.0 |
nginx.ingress.kubernetes.io/canary-by-header |
| ≥v0.22.0 |
nginx.ingress.kubernetes.io/canary-by-header-value |
| ≥v0.30.0 |
nginx.ingress.kubernetes.io/canary-by-header-pattern |
| ≥v0.44.0 |
nginx.ingress.kubernetes.io/canary-by-cookie |
| ≥v0.22.0 |
nginx.ingress.kubernetes.io/canary-weight |
| ≥v0.22.0 |
nginx.ingress.kubernetes.io/canary-weight-total |
| ≥v1.1.2 |
不同灰度方式的優先級由高到低為:
canary-by-header
>canary-by-cookie
>canary-weight
目前每個Ingress規則只支持同時指定一個Canary Ingress,大于一個的Canary Ingress將會被忽略。
步驟一:部署服務
部署Nginx服務并通過Nginx Ingress Controller對外提供7層域名訪問。
創建Deployment和Service。
創建nginx.yaml。
apiVersion: apps/v1 kind: Deployment metadata: name: old-nginx spec: replicas: 2 selector: matchLabels: run: old-nginx template: metadata: labels: run: old-nginx spec: containers: - image: registry.cn-hangzhou.aliyuncs.com/acs-sample/old-nginx imagePullPolicy: Always name: old-nginx ports: - containerPort: 80 protocol: TCP restartPolicy: Always --- apiVersion: v1 kind: Service metadata: name: old-nginx spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: run: old-nginx sessionAffinity: None type: NodePort
執行以下命令,創建Deployment和Service。
kubectl apply -f nginx.yaml
部署Ingress。
創建ingress.yaml。
1.19版本之前集群
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: gray-release spec: rules: - host: www.example.com http: paths: # 老版本服務。 - path: / backend: serviceName: old-nginx servicePort: 80
1.19及之后版本集群
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: gray-release spec: rules: - host: www.example.com http: paths: # 老版本服務。 - path: / backend: service: name: old-nginx port: number: 80 pathType: ImplementationSpecific
執行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
測試訪問情況。
執行以下命令,獲取外部IP。
kubectl get ingress
執行以下命令,查看路由訪問情況。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
預期輸出:
old
步驟二:灰度發布新版本服務
發布一個新版本的Nginx服務并配置路由規則。
部署新版本的Deployment和Service。
創建nginx1.yaml。
apiVersion: apps/v1 kind: Deployment metadata: name: new-nginx spec: replicas: 1 selector: matchLabels: run: new-nginx template: metadata: labels: run: new-nginx spec: containers: - image: registry.cn-hangzhou.aliyuncs.com/acs-sample/new-nginx imagePullPolicy: Always name: new-nginx ports: - containerPort: 80 protocol: TCP restartPolicy: Always --- apiVersion: v1 kind: Service metadata: name: new-nginx spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: run: new-nginx sessionAffinity: None type: NodePort
執行以下命令,部署新版本的Deployment和Service。
kubectl apply -f nginx1.yaml
設置訪問新版本服務的路由規則。
ACK支持設置以下三種路由規則,您可以根據實際情況選擇路由規則。
按照以上條件,在ingress1.yaml文件中創建新的Ingress資源
gray-release-canary
。1.19版本之前集群
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: gray-release-canary annotations: # 開啟Canary。 nginx.ingress.kubernetes.io/canary: "true" # 請求頭為foo。 nginx.ingress.kubernetes.io/canary-by-header: "foo" # 請求頭foo的值為bar時,請求才會被路由到新版本服務new-nginx中。 nginx.ingress.kubernetes.io/canary-by-header-value: "bar" spec: rules: - host: www.example.com http: paths: # 新版本服務。 - path: / backend: serviceName: new-nginx servicePort: 80
1.19及之后版本集群
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: gray-release-canary annotations: # 開啟Canary。 nginx.ingress.kubernetes.io/canary: "true" # 請求頭為foo。 nginx.ingress.kubernetes.io/canary-by-header: "foo" # 請求頭foo的值為bar時,請求才會被路由到新版本服務new-nginx中。 nginx.ingress.kubernetes.io/canary-by-header-value: "bar" spec: rules: - host: www.example.com http: paths: # 新版本服務。 - path: / backend: service: name: new-nginx port: number: 80 pathType: ImplementationSpecific
執行以下命令,部署Ingress1。
kubectl apply -f ingress1.yaml
執行以下命令,獲取外部IP。
kubectl get ingress
查看路由訪問情況。
執行以下命令,訪問服務。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
預期輸出:
old
執行以下命令,請求頭中滿足
foo=bar
的客戶端請求訪問服務。curl -H "Host: www.example.com" -H "foo: bar" http://<EXTERNAL_IP>
預期輸出:
new
重復執行以上命令,可以看到,僅請求頭中滿足
foo=bar
的客戶端請求才能路由到新版本服務。
按照以下內容,修改步驟2中創建的Ingress。
1.19版本之前集群
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: gray-release-canary annotations: # 開啟Canary。 nginx.ingress.kubernetes.io/canary: "true" # 請求頭為foo。 nginx.ingress.kubernetes.io/canary-by-header: "foo" # 請求頭foo的值為bar時,請求才會被路由到新版本服務new-nginx中。 nginx.ingress.kubernetes.io/canary-by-header-value: "bar" # 在未滿足上述匹配規則的基礎上僅允許50%的流量會被路由到新版本服務new-nginx中。 nginx.ingress.kubernetes.io/canary-weight: "50" spec: rules: - host: www.example.com http: paths: # 新版本服務。 - path: / backend: serviceName: new-nginx servicePort: 80
1.19及之后版本集群
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: gray-release-canary annotations: # 開啟Canary。 nginx.ingress.kubernetes.io/canary: "true" # 請求頭為foo。 nginx.ingress.kubernetes.io/canary-by-header: "foo" # 請求頭foo的值為bar時,請求才會被路由到新版本服務new-nginx中。 nginx.ingress.kubernetes.io/canary-by-header-value: "bar" # 在未滿足上述匹配規則的基礎上僅允許50%的流量會被路由到新版本服務new-nginx中。 nginx.ingress.kubernetes.io/canary-weight: "50" spec: rules: - host: www.example.com http: paths: # 新版本服務。 - path: / backend: service: name: new-nginx port: number: 80 pathType: ImplementationSpecific
執行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
執行以下命令,獲取外部IP。
kubectl get ingress
查看路由訪問情況。
執行以下命令,訪問服務。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
預期輸出:
old
執行以下命令,請求頭中滿足
foo=bar
的客戶端請求訪問服務。curl -H "Host: www.example.com" -H "foo: bar" http://<EXTERNAL_IP>
預期輸出:
new
有foo=bar請求頭:
請求流量100%會路由到新版本
new-nginx
服務中,由canary-by-header
和canary-by-header-value
控制。沒有foo=bar請求頭:
請求流量50%會路由到新版本
new-nginx
服務中,由canary-weight
控制。
按照以下內容,修改步驟2創建的Ingress。
1.19版本之前集群
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: gray-release-canary annotations: # 開啟Canary。 nginx.ingress.kubernetes.io/canary: "true" # 僅允許50%的流量會被路由到新版本服務new-nginx中。 # 默認總值為100。 nginx.ingress.kubernetes.io/canary-weight: "50" spec: rules: - host: www.example.com http: paths: # 新版本服務。 - path: / backend: serviceName: new-nginx servicePort: 80
1.19及之后版本集群
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: gray-release-canary annotations: # 開啟Canary。 nginx.ingress.kubernetes.io/canary: "true" # 僅允許50%的流量會被路由到新版本服務new-nginx中。 # 默認總值為100。 nginx.ingress.kubernetes.io/canary-weight: "50" spec: rules: - host: www.example.com http: paths: # 新版本服務。 - path: / backend: service: name: new-nginx port: number: 80 pathType: ImplementationSpecific
執行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
執行以下命令,獲取外部IP。
kubectl get ingress
執行以下命令,查看路由訪問情況。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
重復執行以上命令,可以看到僅50%的流量路由到新版本服務。
步驟三:刪除老版本服務
系統運行一段時間后,當新版本服務已經穩定并且符合預期后,需要下線老版本的服務 ,僅保留新版本服務在線上運行。為了達到該目標,需要將舊版本的Service指向新版本服務的Deployment,并且刪除舊版本的Deployment和新版本的Service。
修改舊版本Service文件nginx.yaml,使其指向新版本服務。
apiVersion: v1 kind: Service metadata: name: old-nginx spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: # 指向新版本服務。 run: new-nginx sessionAffinity: None type: NodePort
執行以下命令,部署舊版本服務。
kubectl apply -f nginx.yaml
執行以下命令,獲取外部IP。
kubectl get ingress
執行以下命令,查看路由訪問情況。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
預期輸出:
new
重復執行以上命令,可以看到請求全部被路由到了新版本的服務。
執行以下命令,刪除Canary Ingress資源
gray-release-canary
。kubectl delete ingress gray-release-canary
刪除舊版本的Deployment和新版本的Service。
執行以下命令,刪除舊版本的Deployment。
kubectl delete deploy old-nginx
執行以下命令,刪除新版本的Service。
kubectl delete svc new-nginx
service-*注解方式
注解說明
Nginx Ingress Controller通過下列Annotation來支持應用服務的灰度發布機制。
nginx.ingress.kubernetes.io/service-match
該注解用來配置新版本服務的路由匹配規則。
nginx.ingress.kubernetes.io/service-match: | <service-name>: <match-rule> # 參數說明: # service-name:服務名稱,滿足match-rule的請求會被路由到該服務中。 # match-rule:路由匹配規則 # # 路由匹配規則: # 1. 支持的匹配類型 # - header:基于請求頭,支持正則匹配和完整匹配。 # - cookie:基于cookie,支持正則匹配和完整匹配。 # - query:基于請求參數,支持正則匹配和完整匹配。 # # 2. 匹配方式 # - 正則匹配格式:/{regular expression}/,使用反斜杠(//)表明采用正則方式匹配。 # - 完整匹配格式:"{exact expression}",使用引號("")表明采用完整方式匹配。
路由匹配規則配置示例:
# 請求頭中滿足foo正則匹配^bar$的請求被轉發到新版本服務new-nginx中。 new-nginx: header("foo", /^bar$/) # 請求頭中滿足foo完整匹配bar的請求被轉發到新版本服務new-nginx中。 new-nginx: header("foo", "bar") # cookie中滿足foo正則匹配^sticky-.+$的請求被轉發到新版本服務new-nginx中。 new-nginx: cookie("foo", /^sticky-.+$/) # query param中滿足foo完整匹配bar的請求被轉發到新版本服務new-nginx中。 new-nginx: query("foo", "bar")
nginx.ingress.kubernetes.io/service-weight
該注解用來配置新舊版本服務的流量權重。
nginx.ingress.kubernetes.io/service-weight: | <new-svc-name>:<new-svc-weight>, <old-svc-name>:<old-svc-weight> 參數說明: new-svc-name:新版本服務名稱 new-svc-weight:新版本服務權重 old-svc-name:舊版本服務名稱 old-svc-weight:舊版本服務權重
服務權重配置示例:
nginx.ingress.kubernetes.io/service-weight: | new-nginx: 20, old-nginx: 60
步驟一:部署服務
部署Nginx服務并通過Nginx Ingress Controller對外提供7層域名訪問。
創建Deployment和Service。
創建nginx.yaml。
apiVersion: apps/v1 kind: Deployment metadata: name: old-nginx spec: replicas: 2 selector: matchLabels: run: old-nginx template: metadata: labels: run: old-nginx spec: containers: - image: registry.cn-hangzhou.aliyuncs.com/acs-sample/old-nginx imagePullPolicy: Always name: old-nginx ports: - containerPort: 80 protocol: TCP restartPolicy: Always --- apiVersion: v1 kind: Service metadata: name: old-nginx spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: run: old-nginx sessionAffinity: None type: NodePort
執行以下命令,創建Deployment和Service。
kubectl apply -f nginx.yaml
部署Ingress。
創建ingress.yaml。
1.19版本之前集群
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: gray-release spec: rules: - host: www.example.com http: paths: # 老版本服務。 - path: / backend: serviceName: old-nginx servicePort: 80
1.19及之后版本集群
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: gray-release spec: rules: - host: www.example.com http: paths: # 老版本服務。 - path: / backend: service: name: old-nginx port: number: 80 pathType: ImplementationSpecific
執行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
測試訪問情況。
執行以下命令,獲取外部IP。
kubectl get ingress
執行以下命令,查看路由訪問情況。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
預期輸出:
old
步驟二:灰度發布新版本服務
發布一個新版本的Nginx服務并配置路由規則。
部署新版本的Deployment和Service。
創建nginx1.yaml。
apiVersion: apps/v1 kind: Deployment metadata: name: new-nginx spec: replicas: 1 selector: matchLabels: run: new-nginx template: metadata: labels: run: new-nginx spec: containers: - image: registry.cn-hangzhou.aliyuncs.com/acs-sample/new-nginx imagePullPolicy: Always name: new-nginx ports: - containerPort: 80 protocol: TCP restartPolicy: Always --- apiVersion: v1 kind: Service metadata: name: new-nginx spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: run: new-nginx sessionAffinity: None type: NodePort
部署新版本的Deployment和Service。
kubectl apply -f nginx1.yaml
設置訪問新版本服務的路由規則。
ACK支持設置以下三種路由規則,您可以根據實際情況選擇路由規則。
按照以下內容,修改步驟2創建的Ingress。
1.19版本之前集群
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: gray-release annotations: # 請求頭中滿足正則匹配foo=bar的請求才會被路由到新版本服務new-nginx中。 nginx.ingress.kubernetes.io/service-match: | new-nginx: header("foo", /^bar$/) spec: rules: - host: www.example.com http: paths: # 老版本服務。 - path: / backend: serviceName: old-nginx servicePort: 80 # 新版本服務。 - path: / backend: serviceName: new-nginx servicePort: 80
1.19及之后版本集群
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: gray-release annotations: # 請求頭中滿足正則匹配foo=bar的請求才會被路由到新版本服務new-nginx中。 nginx.ingress.kubernetes.io/service-match: | new-nginx: header("foo", /^bar$/) spec: rules: - host: www.example.com http: paths: # 老版本服務。 - path: / backend: service: name: old-nginx port: number: 80 pathType: ImplementationSpecific # 新版本服務。 - path: / backend: service: name: new-nginx port: number: 80 pathType: ImplementationSpecific
執行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
執行以下命令,獲取外部IP。
kubectl get ingress
查看路由訪問情況。
執行以下命令,訪問服務。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
預期輸出:
old
執行以下命令,請求頭中滿足
foo=bar
的客戶端請求訪問服務。curl -H "Host: www.example.com" -H "foo: bar" http://<EXTERNAL_IP>
預期輸出:
new
重復執行以上命令,可以看到,僅請求頭中滿足
foo=bar
的客戶端請求才能路由到新版本服務。按照以下內容,修改步驟2創建的Ingress。
1.19版本之前集群
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: gray-release annotations: # 請求頭中滿足正則匹配foo=bar的請求才會被路由到新版本服務new-nginx中。 nginx.ingress.kubernetes.io/service-match: | new-nginx: header("foo", /^bar$/) # 在滿足上述匹配規則的基礎上僅允許50%的流量會被路由到新版本服務new-nginx中。 nginx.ingress.kubernetes.io/service-weight: | new-nginx: 50, old-nginx: 50 spec: rules: - host: www.example.com http: paths: # 老版本服務。 - path: / backend: serviceName: old-nginx servicePort: 80 # 新版本服務。 - path: / backend: serviceName: new-nginx servicePort: 80 servicePort: 80
1.19及之后版本集群
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: gray-release annotations: # 請求頭中滿足正則匹配foo=bar的請求才會被路由到新版本服務new-nginx中。 nginx.ingress.kubernetes.io/service-match: | new-nginx: header("foo", /^bar$/) # 在滿足上述匹配規則的基礎上僅允許50%的流量會被路由到新版本服務new-nginx中。 nginx.ingress.kubernetes.io/service-weight: | new-nginx: 50, old-nginx: 50 spec: rules: - host: www.example.com http: paths: # 老版本服務。 - path: / backend: service: name: old-nginx port: number: 80 pathType: ImplementationSpecific # 新版本服務。 - path: / backend: service: name: new-nginx port: number: 80 pathType: ImplementationSpecific
執行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
執行以下命令,獲取外部IP。
kubectl get ingress
查看路由訪問情況。
執行以下命令,訪問服務。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
預期輸出:
old
執行以下命令,請求頭中滿足
foo=bar
的客戶端請求訪問服務。curl -H "Host: www.example.com" -H "foo: bar" http://<EXTERNAL_IP>
預期輸出:
new
重復執行以上命令。可以看到,僅請求頭中滿足
foo=bar
的客戶端請求,且只有50%的流量才能路由到新版本服務。
按照以下內容,修改步驟2創建的Ingress。
1.19版本之前集群
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: gray-release annotations: # 允許50%的流量被路由到新版本服務new-nginx中。 nginx.ingress.kubernetes.io/service-weight: | new-nginx: 50, old-nginx: 50 spec: rules: - host: www.example.com http: paths: # 老版本服務。 - path: / backend: serviceName: old-nginx servicePort: 80 # 新版本服務。 - path: / backend: serviceName: new-nginx servicePort: 80
1.19及之后版本集群
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: gray-release annotations: # 允許50%的流量被路由到新版本服務new-nginx中。 nginx.ingress.kubernetes.io/service-weight: | new-nginx: 50, old-nginx: 50 spec: rules: - host: www.example.com http: paths: # 老版本服務。 - path: / backend: service: name: old-nginx port: number: 80 pathType: ImplementationSpecific # 新版本服務。 - path: / backend: service: name: new-nginx port: number: 80 pathType: ImplementationSpecific
執行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
執行以下命令,獲取外部IP。
kubectl get ingress
執行以下命令,查看路由訪問情況。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
重復執行以上命令,可以看到僅50%的流量路由到新版本服務。
步驟三:刪除老版本服務
系統運行一段時間后,當新版本服務已經穩定并且符合預期后,需要下線老版本的服務 ,僅保留新版本服務在線上運行。
按照以下內容,修改步驟2創建的Ingress。
1.19版本之前集群
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: gray-release spec: rules: - host: www.example.com http: paths: # 新版本服務。 - path: / backend: serviceName: new-nginx servicePort: 80
1.19及之后版本集群
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: gray-release spec: rules: - host: www.example.com http: paths: # 新版本服務。 - path: / backend: service: name: new-nginx port: number: 80 pathType: ImplementationSpecific
執行以下命令,部署Ingress。
kubectl apply -f ingress.yaml
執行以下命令,獲取外部IP。
kubectl get ingress
執行以下命令,查看路由訪問情況。
curl -H "Host: www.example.com" http://<EXTERNAL_IP>
預期輸出:
new
重復執行以上命令,可以看到請求全部被路由到了新版本的服務。
刪除舊版本的Deployment和Service。
執行以下命令,刪除舊版本的Deployment。
kubectl delete deploy <Deployment名稱>
執行以下命令,刪除舊版本的Service。
kubectl delete svc <Service名稱>