如何在Service Mesh微服務(wù)架構(gòu)中實(shí)現(xiàn)金絲雀發(fā)布?
本文轉(zhuǎn)載自微信公眾號(hào)「無(wú)敵碼農(nóng)」,作者無(wú)敵碼農(nóng)。轉(zhuǎn)載本文請(qǐng)聯(lián)系無(wú)敵碼農(nóng)公眾號(hào)。
今天的文章繼續(xù)聊聊有關(guān)Service Mesh微服務(wù)架構(gòu)的話題,如果對(duì)之前的聊過(guò)的話題還不了解,可以參考文末的推薦閱讀。今天要聊的話題是:如何在Service Mesh微服務(wù)架構(gòu)中實(shí)現(xiàn)“金絲雀發(fā)布”?
什么是金絲雀發(fā)布
既然要聊具體的實(shí)現(xiàn),那么在開(kāi)始之前,先科普下什么是“金絲雀發(fā)布”。金絲雀發(fā)布也叫“灰度發(fā)布”,具體來(lái)說(shuō)就是在發(fā)布線上版本時(shí),先將少量的生產(chǎn)流量打到服務(wù)的新版本,以驗(yàn)證新版本的準(zhǔn)確性和可靠性,待發(fā)布的新版本得到線上流量的全面驗(yàn)證后,在逐步將所有流量放入新版本,以實(shí)現(xiàn)生產(chǎn)服務(wù)版本的穩(wěn)定更新。
為什么叫金絲雀發(fā)布呢,是因?yàn)榻鸾z雀對(duì)礦場(chǎng)中的毒氣比較敏感,所以在礦場(chǎng)開(kāi)工前工人們會(huì)放一只金絲雀進(jìn)去,以驗(yàn)證礦場(chǎng)是否存在毒氣,這便是金絲雀發(fā)布名稱的由來(lái)。
在不同技術(shù)棧場(chǎng)景中,金絲雀發(fā)布的實(shí)現(xiàn)方式也不盡相同:有通過(guò)nginx實(shí)現(xiàn)的、也有借助A/B測(cè)試實(shí)現(xiàn)的。而隨著以Kubernetes為代表的云原生基礎(chǔ)設(shè)施的普及,金絲雀發(fā)布作為一項(xiàng)基本的服務(wù)發(fā)布功能,其實(shí)現(xiàn)方式也有了一些新的趨勢(shì)——那就是逐步與云原生基礎(chǔ)設(shè)施融為一體,成為基礎(chǔ)設(shè)施服務(wù)的一部分。
Kubernetes中的金絲雀(灰度)發(fā)布
接下來(lái),先看看在Kubernetes中是如何實(shí)現(xiàn)版本更新的。以下內(nèi)容假設(shè)你已經(jīng)有了一套可用的Kubernetes環(huán)境,如果沒(méi)有可以查看文末推薦閱讀的文章鏈接,參考相關(guān)分享自行部署。
1.滾動(dòng)更新
在介紹Kubernetes中的金絲雀(灰度)發(fā)布之前,先來(lái)了解下Kubernetes中最重要的應(yīng)用部署方式——“滾動(dòng)升級(jí)”。
所謂“滾動(dòng)升級(jí)”:是指當(dāng)更新了Kubernetes中Deployment編排資源的Pod模版(例如更新鏡像版本號(hào))之后,Deployment就需要遵循一種叫做“滾動(dòng)更新(rolling update)”的方式,來(lái)升級(jí)現(xiàn)有的容器,從而實(shí)現(xiàn)應(yīng)用對(duì)外服務(wù)的“不中斷更新部署”。Kubernetes實(shí)現(xiàn)“滾動(dòng)升級(jí)”的示意圖如下:
如上圖所示,滾動(dòng)升級(jí)的過(guò)程為:
1)當(dāng)容器開(kāi)始升級(jí)時(shí),集群中會(huì)先啟動(dòng)一個(gè)新版本的Pod,并終止一個(gè)舊版本的Pod。
2)如果此時(shí),新版本的Pod有問(wèn)題啟動(dòng)不了,那么“滾動(dòng)升級(jí)”就會(huì)停止,并允許開(kāi)發(fā)和運(yùn)維人員介入。而在這個(gè)過(guò)程中,由于應(yīng)用本身還有兩個(gè)舊版本的Pod在線,所以服務(wù)并不會(huì)受到太大的影響。
3)而如果新版本的Pod啟動(dòng)成功,且服務(wù)訪問(wèn)正常,則繼續(xù)滾動(dòng)升級(jí),直至按照Deployment編排器設(shè)置的副本數(shù)量,完成后續(xù)舊版本Pod的升級(jí)。
在Kubernetes中Deployment還可以通過(guò)相應(yīng)地“滾動(dòng)升級(jí)”策略,來(lái)控制Pod的滾動(dòng)升級(jí)行為,以進(jìn)一步保證服務(wù)的連續(xù)性。例如:“在任何時(shí)間窗口內(nèi),只有指定比例的Pod處于離線狀態(tài);在任何時(shí)間窗口內(nèi),只有指定比例的新Pod被創(chuàng)建出來(lái)"??梢酝ㄟ^(guò)相應(yīng)地控制參數(shù)進(jìn)行設(shè)置,如下:
- ...
- spec:
- selector:
- matchLabels:
- app: micro-api
- replicas: 3
- #設(shè)置滾動(dòng)升級(jí)策略
- #Kubernetes在等待設(shè)置的時(shí)間后才開(kāi)始進(jìn)行升級(jí),例如5秒
- minReadySeconds: 5
- strategy:
- type: RollingUpdate
- rollingUpdate:
- #升級(jí)過(guò)程中最多可以比原先設(shè)置多出的Pod數(shù)量
- maxSurge: 1
- #升級(jí)過(guò)程中Deployment控制器最多可以刪除多少個(gè)舊Pod,主要用于提供緩沖時(shí)間
- maxUnavailable: 1
- ...
在上面RollingUpdate Strategy(滾動(dòng)升級(jí)策略)的配置中:
- maxSurge:指定的是,除了設(shè)定的Pod副本數(shù)量之外,在一次“滾動(dòng)”中,Deployment控制器還可以創(chuàng)建多少個(gè)新的Pod。
- maxUnavailable:指的是,在一次“滾動(dòng)”中,Deployment控制器可以刪除多少個(gè)舊Pod。
通過(guò)這種精確的“滾動(dòng)升級(jí)”策略,可以使得Kubernetes服務(wù)版本發(fā)布的過(guò)程更加平滑。此外,這兩個(gè)配置還可以通過(guò)百分比的方式來(lái)表示,比如“maxUnavailable=50%”,指的是Deployment控制器最多可以一次刪除“50%*設(shè)定Pod副本數(shù)”個(gè)Pod。
接下來(lái)具體演示下在Kubernetes中進(jìn)行服務(wù)滾動(dòng)升級(jí)的詳細(xì)過(guò)程。
使用的示例代碼說(shuō)明:
本文及本公眾號(hào)之前或之后與Service Mesh(服務(wù)網(wǎng)格、Istio)技術(shù)相關(guān)的分享,均使用《干貨|如何步入Service Mesh微服務(wù)架構(gòu)時(shí)代》、《實(shí)戰(zhàn)|Service Mesh微服務(wù)架構(gòu)實(shí)現(xiàn)服務(wù)間gRPC通信》這兩篇文章所展示的項(xiàng)目。
該項(xiàng)目以Spring Boot編寫(xiě)的Java服務(wù)為主,在體驗(yàn)上更接近真實(shí)的項(xiàng)目開(kāi)發(fā)場(chǎng)景。項(xiàng)目的結(jié)構(gòu)如下:
該項(xiàng)目所在的GitHub地址為:
https://github.com/manongwudi/istio-micro-service-demo
“滾動(dòng)升級(jí)”演示:
這里先借助示例項(xiàng)目中的“micro-api”服務(wù)來(lái)演示其在Kubernetes中進(jìn)行“滾動(dòng)升級(jí)”的過(guò)程,步驟如下:
(1)首先準(zhǔn)備“micro-api”服務(wù)的k8s發(fā)布文件(如:micro-api.yaml)。代碼如下:
- apiVersion: v1
- kind: Service
- metadata:
- name: micro-api
- spec:
- type: ClusterIP
- ports:
- - name: http
- port: 19090
- targetPort: 9090
- selector:
- app: micro-api
- ---
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: micro-api
- spec:
- selector:
- matchLabels:
- app: micro-api
- replicas: 3
- #設(shè)置滾動(dòng)升級(jí)策略
- #Kubernetes在等待設(shè)置的時(shí)間后才開(kāi)始進(jìn)行升級(jí),例如5秒
- minReadySeconds: 5
- strategy:
- type: RollingUpdate
- rollingUpdate:
- #升級(jí)過(guò)程中最多可以比原先設(shè)置多出的Pod數(shù)量
- maxSurge: 1
- #升級(jí)過(guò)程中Deployment控制器最多可以刪除多少個(gè)舊Pod
- maxUnavailable: 1
- template:
- metadata:
- labels:
- app: micro-api
- spec:
- #設(shè)置的阿里云私有鏡像倉(cāng)庫(kù)登陸信息的secret(對(duì)應(yīng)2.1.2的設(shè)置)
- imagePullSecrets:
- - name: regcred
- containers:
- - name: micro-api
- image: registry.cn-hangzhou.aliyuncs.com/wudimanong/micro-api:1.0-SNAPSHOT
- imagePullPolicy: Always
- tty: true
- ports:
- - name: http
- protocol: TCP
- containerPort: 19090
上述部署文件設(shè)置了“micro-api”服務(wù)的Pod副本個(gè)數(shù)為“3”,并且設(shè)置了相應(yīng)地滾動(dòng)升級(jí)策略。
(2)接下來(lái)執(zhí)行k8s部署命令如下:
- $ kubectl apply -f micro-api.yaml
成功后,查看Deployment創(chuàng)建后的狀態(tài)信息,命令效果如下:
- $ kubectl get deployments
- NAME READY UP-TO-DATE AVAILABLE AGE
- micro-api 3/3 3 3 190d
從上述命令的返回結(jié)果中,可以看到三個(gè)狀態(tài)字段,它們的含義如下所示:
- READY:表示用戶期望的Pod副本個(gè)數(shù),以及當(dāng)前處于Running狀態(tài)的Pod個(gè)數(shù)。
- UP-TO-DATE:當(dāng)前處于最新版本的Pod個(gè)數(shù)。所謂最新版本,指的是Pod的Spec部分與Deployment中Pod模版里定義的完全一致。
- AVAILABLE:當(dāng)前已經(jīng)可用的Pod的個(gè)數(shù)——既是Running狀態(tài),又是最新版本,并且已經(jīng)處于Ready(監(jiān)控檢查正確)狀態(tài)的Pod個(gè)數(shù)。
(3)模擬服務(wù)版本升級(jí),觸發(fā)滾動(dòng)升級(jí)。
接下來(lái)重新構(gòu)建“micro-api”服務(wù)的版本,并將其上傳至私有鏡像倉(cāng)庫(kù)。之后,通過(guò)命令修改“micro-api”的Deployment所使用的鏡像,并觸發(fā)滾動(dòng)升級(jí)。
修改Deployment所使用的鏡像的命令如下:
- $ kubectl set image deployment/micro-api micro-api=registry.cn-hangzhou.aliyuncs.com/wudimanong/micro-api:1.1-SNAPSHOT
- deployment.apps/micro-api image updated
這里使用了“kubectl set image”指令,主要是為了方便操作,也可以直接在k8s部署文件中進(jìn)行鏡像版本的修改。
修改完Deployment的鏡像版本后,Kubernetes會(huì)立即觸發(fā)“滾動(dòng)升級(jí)”的過(guò)程??梢酝ㄟ^(guò)“kubectl rollout status”指令來(lái)查看Deployment資源的狀態(tài)變化。具體如下:
- $ kubectl rollout status deployment/micro-api
- Waiting for deployment "micro-api" rollout to finish: 2 out of 3 new replicas have been updated...
- Waiting for deployment "micro-api" rollout to finish: 2 out of 3 new replicas have been updated...
- Waiting for deployment "micro-api" rollout to finish: 2 out of 3 new replicas have been updated...
- Waiting for deployment "micro-api" rollout to finish: 2 of 3 updated replicas are available...
- Waiting for deployment "micro-api" rollout to finish: 2 of 3 updated replicas are available...
- deployment "micro-api" successfully rolled out
這時(shí),也可以通過(guò)查看Deployment的Events,看到這個(gè)“滾動(dòng)升級(jí)”的過(guò)程。具體如下:
- $ kubectl describe deployment micro-api
- ...
- OldReplicaSets: <none>
- NewReplicaSet: micro-api-d745d8649 (3/3 replicas created)
- Events:
- Type Reason Age From Message
- ---- ------ ---- ---- -------
- Normal ScalingReplicaSet 12m deployment-controller Scaled up replica set micro-api-677dd4d5b6 to 1
- Normal ScalingReplicaSet 12m deployment-controller Scaled down replica set micro-api-57c7cb5b74 to 2
- Normal ScalingReplicaSet 12m deployment-controller Scaled up replica set micro-api-677dd4d5b6 to 2
- Normal ScalingReplicaSet 5m1s deployment-controller Scaled down replica set micro-api-677dd4d5b6 to 0
- Normal ScalingReplicaSet 5m deployment-controller Scaled up replica set micro-api-d745d8649 to 2
- Normal ScalingReplicaSet 56s deployment-controller Scaled down replica set micro-api-57c7cb5b74 to 0
- Normal ScalingReplicaSet 56s deployment-controller Scaled up replica set micro-api-d745d8649 to 3
可以看到,當(dāng)你修改了Deployment里的Pod定義后,"Deployment Controller"會(huì)使用這個(gè)修改后的Pod模版,創(chuàng)建一個(gè)新的ReplicaSet,這個(gè)新的ReplicaSet的初始Pod副本數(shù)是:0。
然后在Age=12 m的位置,開(kāi)始將這個(gè)新的ReplicaSet所控制的Pod副本數(shù)從0個(gè)變成1個(gè)。
緊接著,在Age=12 m的位置,又將舊ReplicaSet所控制的Pod副本數(shù)減少1個(gè),即“水平收縮”成兩個(gè)副本。
如此交替進(jìn)行,新ReplicaSet所管理的Pod的副本數(shù),從0個(gè)變成1個(gè),再變成2個(gè),最后變成3個(gè);而舊ReplicaSet所管理的Pod的副本數(shù)則從3個(gè)變成2個(gè),最后變成0個(gè)。
這樣,就完成了一組Pod的版本升級(jí)過(guò)程。而像這樣將一個(gè)Kubernetes集群中正在運(yùn)行的多個(gè)Pod版本,交替逐一升級(jí)的過(guò)程,就是“滾動(dòng)升級(jí)”。
2.金絲雀(灰度)發(fā)布
前面“1.”小標(biāo)題中,比較詳細(xì)的演示了Kubernetes的“滾動(dòng)升級(jí)”的方式,雖然通過(guò)滾動(dòng)升級(jí)的方式可以方便、平滑的實(shí)現(xiàn)版本更新,但是這個(gè)過(guò)程,并沒(méi)有灰度功能。滾動(dòng)升級(jí)的方式,雖然中間有緩沖交替的過(guò)程,但這種過(guò)程是自動(dòng)的、迅速的,滾動(dòng)升級(jí)過(guò)程結(jié)束就相當(dāng)于直接進(jìn)行了新版本的全量發(fā)布。
而對(duì)于需要進(jìn)行金絲雀(灰度)發(fā)布的場(chǎng)景,“滾動(dòng)升級(jí)”的方式很顯然是不夠用的。那么,在Kubernetes中應(yīng)該如何結(jié)合版本更新做到金絲雀(灰度)發(fā)布呢?
具體步驟如下:
(1)編寫(xiě)實(shí)現(xiàn)新版本灰度發(fā)布的部署文件。
為了實(shí)現(xiàn)在Kubernetes中的金絲雀(灰度)發(fā)布過(guò)程的可觀測(cè),我們重新定義下具體的k8s發(fā)布文件(如:micro-api-canary.yaml)的內(nèi)容如下:
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: micro-api
- spec:
- selector:
- matchLabels:
- app: micro-api
- replicas: 3
- #設(shè)置滾動(dòng)升級(jí)策略
- #Kubernetes在等待設(shè)置的時(shí)間后才開(kāi)始進(jìn)行升級(jí),例如5秒
- minReadySeconds: 5
- strategy:
- type: RollingUpdate
- rollingUpdate:
- #升級(jí)過(guò)程中最多可以比原先設(shè)置多出的Pod數(shù)量
- maxSurge: 1
- #升級(jí)過(guò)程中Deployment控制器最多可以刪除多少個(gè)舊Pod,主要用于提供緩沖時(shí)間
- maxUnavailable: 1
- template:
- metadata:
- labels:
- app: micro-api
- #增加新的標(biāo)簽(演示k8s的灰度發(fā)布)
- track: canary
- spec:
- #設(shè)置的阿里云私有鏡像倉(cāng)庫(kù)登陸信息的secret(對(duì)應(yīng)2.1.2的設(shè)置)
- imagePullSecrets:
- - name: regcred
- containers:
- - name: micro-api
- image: registry.cn-hangzhou.aliyuncs.com/wudimanong/micro-api:1.3-SNAPSHOT
- imagePullPolicy: Always
- tty: true
- ports:
- - name: http
- protocol: TCP
- containerPort: 19090
上述發(fā)布文件與“1.”小標(biāo)題中演示滾動(dòng)升級(jí)時(shí),發(fā)布文件的內(nèi)容一致,只是為了方便觀察灰度發(fā)布過(guò)程的實(shí)現(xiàn),這里通過(guò)“track: canary”對(duì)新發(fā)布的Pod版本進(jìn)行標(biāo)記。
設(shè)置新版本的鏡像為:“micro-api:1.3-SNAPSHOT”。并且通過(guò)“spec.selector.matchLabels.app:micro-api”與歷史版本Pod所對(duì)應(yīng)的Service(micro-api.yaml文件中定義的Service)資源定義匹配。
(2)執(zhí)行"滾動(dòng)升級(jí)"發(fā)布命令,實(shí)現(xiàn)“灰度發(fā)布”效果。
- $ kubectl apply -f micro-api-canary.yaml && kubectl rollout pause deployment/micro-api
上面通過(guò)"kubectl rollout pause"命令實(shí)現(xiàn)對(duì)Deployment的金絲雀(灰度發(fā)布)。執(zhí)行發(fā)布命令之后的運(yùn)行效果如下:
- $ kubectl get pods --show-labels -o wide
- NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
- micro-api-57c7cb5b74-mq7m9 1/1 Running 0 6m20s 10.32.0.3 kubernetes <none> <none> app=micro-api,pod-template-hash=57c7cb5b74
- micro-api-57c7cb5b74-ptptj 1/1 Running 0 6m20s 10.32.0.4 kubernetes <none> <none> app=micro-api,pod-template-hash=57c7cb5b74
- micro-api-7dbb6c5d66-4rbdc 1/1 Running 0 5m33s 10.32.0.6 kubernetes <none> <none> app=micro-api,pod-template-hash=7dbb6c5d66,track=canary
- micro-api-7dbb6c5d66-cfk9l 1/1 Running 0 5m33s 10.32.0.5 kubernetes <none> <none> app=micro-api,pod-template-hash=7dbb6c5d66,track=canary
查看Deployment的滾動(dòng)升級(jí)情況,命令如下:
- $ kubectl get deployments
- NAME READY UP-TO-DATE AVAILABLE AGE
- micro-api 4/3 2 4 194d
可以看到此時(shí)“micro-api” ready的數(shù)量為4,其中兩個(gè)舊版本Pod,兩個(gè)新版本Pod。
(3)接下來(lái)進(jìn)行流量測(cè)試。
查詢兩組Pod版本所對(duì)應(yīng)的Service資源的IP,命令如下:
- # kubectl get svc micro-api
- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
- micro-api ClusterIP 10.110.169.161 <none> 19090/TCP 194d
接下來(lái),模擬對(duì)服務(wù)的接口進(jìn)行批量訪問(wèn),命令如下:
- $ for i in {1..10}; do curl 10.110.169.161:19090/test/test; done
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
可以看到,此時(shí)流量會(huì)隨機(jī)的流向舊版本和新版本(日志標(biāo)記為V3)的服務(wù)。
(4)將服務(wù)版本升級(jí)為新版本。
如果新版本的服務(wù)經(jīng)過(guò)線上流量測(cè)試驗(yàn)證沒(méi)有問(wèn)題,則可以通過(guò)"rollout resume"命令將整體服務(wù)的版本升級(jí)為新版本。命令如下:
- $ kubectl rollout resume deployment micro-api
- deployment.apps/micro-api resumed
升級(jí)后的效果如下:
- $ kubectl get pods --show-labels -o wide
- NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
- micro-api-7dbb6c5d66-4rbdc 1/1 Running 0 18m 10.32.0.6 kubernetes <none> <none> app=micro-api,pod-template-hash=7dbb6c5d66,track=canary
- micro-api-7dbb6c5d66-bpjtg 1/1 Running 0 84s 10.32.0.3 kubernetes <none> <none> app=micro-api,pod-template-hash=7dbb6c5d66,track=canary
- micro-api-7dbb6c5d66-cfk9l 1/1 Running 0 18m 10.32.0.5 kubernetes <none> <none> app=micro-api,pod-template-hash=7dbb6c5d66,track=canary
可以看到,此時(shí)目標(biāo)服務(wù)已經(jīng)通過(guò)“滾動(dòng)升級(jí)”的方式完成了全量更新。而如果存在問(wèn)題,則通過(guò)“kubectl rollout undo”命令進(jìn)行回滾即可!
從上述過(guò)程可以看到,Kubernetes中的金絲雀(灰度發(fā)布)主要是通過(guò)操縱(如:pause)“滾動(dòng)升級(jí)”的過(guò)程來(lái)實(shí)現(xiàn)的——通過(guò)發(fā)布一定數(shù)量的新版本Pod,并利用Service資源類型本身的負(fù)載均衡能力來(lái)實(shí)現(xiàn)流量在新/舊Pod之間的隨機(jī)交替。
這樣的方式雖然已經(jīng)可以滿足一些簡(jiǎn)單的場(chǎng)景,但是沒(méi)有辦法做到更精準(zhǔn)的灰度流量控制。這時(shí)候就需要借助 Service Mesh 中的解決方案了,下面我們來(lái)看看在 Istio 中如何做到精準(zhǔn)流量的金絲雀(灰度)發(fā)布。
Istio中的金絲雀(灰度)發(fā)布
以下內(nèi)容默認(rèn)你已經(jīng)在Kubernetes中安裝了Istio環(huán)境,如果還沒(méi)有安裝可以參考《干貨|如何步入Service Mesh微服務(wù)架構(gòu)時(shí)代》中分享的內(nèi)容。
Istio與Kubernetes實(shí)現(xiàn)金絲雀(灰度)發(fā)布的方式不一樣,Istio通過(guò)Envoy(SideCar)強(qiáng)大的路由規(guī)則管理能力,可以非常靈活地控制對(duì)應(yīng)版本的流量占比,從而實(shí)現(xiàn)具備精準(zhǔn)流量控制能力的金絲雀(灰度)發(fā)布功能。
Istio通過(guò)Envoy(SideCar)實(shí)現(xiàn)金絲雀(灰度)發(fā)布的流量路由示意圖如下(繼續(xù)以“micro-api”服務(wù)為例):
從上圖中可以大致看出,Istio具備強(qiáng)大的流量管理能力,而這種能力對(duì)于實(shí)現(xiàn)流量精準(zhǔn)控制的金絲雀(灰度)發(fā)布功能來(lái)說(shuō),自然是水到渠成的。
具體來(lái)說(shuō),在Istio中是通過(guò)VirtualService(虛擬服務(wù))這種特定的資源在服務(wù)網(wǎng)格中實(shí)現(xiàn)流量路由的。通過(guò)VirtualService可以方便地定義流量路由規(guī)則,并在客戶端試圖連接到服務(wù)時(shí)應(yīng)用這些規(guī)則,并最終到達(dá)目標(biāo)服務(wù)。
接下來(lái),具體演示如何在Istio中通過(guò)VirtualService實(shí)現(xiàn)金絲雀(灰度)發(fā)布。步驟如下:
(1)首先發(fā)布一個(gè)v1版本的服務(wù)。
要在Istio中實(shí)現(xiàn)更精準(zhǔn)的版本控制,需要在發(fā)布Pod資源時(shí),通過(guò)明確的“版本標(biāo)簽”進(jìn)行指定。準(zhǔn)備“micro-api”服務(wù)v1版本的k8s部署文件(micro-api-canary-istio-v1.yaml):
- apiVersion: v1
- kind: Service
- metadata:
- name: micro-api
- spec:
- type: ClusterIP
- ports:
- - name: http
- port: 19090
- targetPort: 9090
- selector:
- app: micro-api
- ---
- apiVersion: apps/v1
- kind: Deployment
- meta data:
- name: micro-api-v1
- spec:
- selector:
- matchLabels:
- app: micro-api
- #這里是關(guān)鍵,需要設(shè)置版本標(biāo)簽,以便實(shí)現(xiàn)灰度發(fā)布
- version: v1
- replicas: 3
- #設(shè)置滾動(dòng)升級(jí)策略
- #Kubernetes在等待設(shè)置的時(shí)間后才開(kāi)始進(jìn)行升級(jí),例如5秒
- minReadySeconds: 5
- strategy:
- type: RollingUpdate
- rollingUpdate:
- #升級(jí)過(guò)程中最多可以比原先設(shè)置多出的Pod數(shù)量
- maxSurge: 1
- #升級(jí)過(guò)程中Deployment控制器最多可以刪除多少個(gè)舊Pod,主要用于提供緩沖時(shí)間
- maxUnavailable: 1
- template:
- metadata:
- labels:
- app: micro-api
- #設(shè)置版本標(biāo)簽,便于灰度發(fā)布
- version: v1
- spec:
- #設(shè)置的阿里云私有鏡像倉(cāng)庫(kù)登陸信息的secret
- imagePullSecrets:
- - name: regcred
- containers:
- - name: micro-api
- image: registry.cn-hangzhou.aliyuncs.com/wudimanong/micro-api:1.1-SNAPSHOT
- imagePullPolicy: Always
- tty: true
- ports:
- - name: http
- protocol: TCP
- containerPort: 19090
“spec.selector.matchLabels.version:v1”標(biāo)簽用來(lái)標(biāo)注服務(wù)的版本,該標(biāo)簽是后續(xù)Istio的流量管理規(guī)則中,識(shí)別服務(wù)版本的主要依據(jù)。
準(zhǔn)備好發(fā)布文件后,執(zhí)行發(fā)布命令:
- $ kubectl apply -f micro-api-canary-istio-v1.yaml
此時(shí),一個(gè)低版本的服務(wù)就運(yùn)行成功了!接下來(lái)我們模擬對(duì)其實(shí)施金絲雀(灰度)發(fā)布。
(2)發(fā)布一個(gè)v2版本的服務(wù)(升級(jí)的目標(biāo)版本)。
與v1版本服務(wù)一樣,發(fā)布的v2版本的服務(wù)也需要明確版本標(biāo)簽,其發(fā)布文件(micro-api-canary-istio-v2.yaml)的內(nèi)容如下:
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: micro-api-v2
- spec:
- selector:
- matchLabels:
- app: micro-api
- #設(shè)置好版本標(biāo)簽,便于灰度發(fā)布
- version: v2
- replicas: 3
- #設(shè)置滾動(dòng)升級(jí)策略
- #Kubernetes在等待設(shè)置的時(shí)間后才開(kāi)始進(jìn)行升級(jí),例如5秒
- minReadySeconds: 5
- strategy:
- type: RollingUpdate
- rollingUpdate:
- #升級(jí)過(guò)程中最多可以比原先設(shè)置多出的Pod數(shù)量
- maxSurge: 1
- #升級(jí)過(guò)程中Deployment控制器最多可以刪除多少個(gè)舊Pod,主要用于提供緩沖時(shí)間
- maxUnavailable: 1
- template:
- metadata:
- labels:
- app: micro-api
- #設(shè)置好版本標(biāo)簽,便于灰度發(fā)布
- version: v2
- spec:
- #設(shè)置的阿里云私有鏡像倉(cāng)庫(kù)登陸信息的secret
- imagePullSecrets:
- - name: regcred
- containers:
- - name: micro-api
- image: registry.cn-hangzhou.aliyuncs.com/wudimanong/micro-api:1.3-SNAPSHOT
- imagePullPolicy: Always
- tty: true
- ports:
- - name: http
- protocol: TCP
- containerPort: 19090
執(zhí)行發(fā)布命令:
- $ kubectl apply -f micro-api-canary-istio-v2.yaml
- deployment.apps/micro-api-v2 created
此時(shí),系統(tǒng)中就存在了兩組版本的Pod資源,具體如下:
- # kubectl get pods
- NAME READY STATUS RESTARTS AGE
- micro-api-v1-565d749dd4-7c66z 1/1 Running 2 13h
- micro-api-v1-565d749dd4-7dqfb 1/1 Running 2 13h
- micro-api-v1-565d749dd4-l62wc 1/1 Running 2 13h
- micro-api-v2-6f98c598c9-5stlw 1/1 Running 0 82s
- micro-api-v2-6f98c598c9-f2ntq 1/1 Running 0 82s
- micro-api-v2-6f98c598c9-l8g4j 1/1 Running 0 82s
接下來(lái)將演示如何利用Istio強(qiáng)大的流量管理功能,來(lái)實(shí)現(xiàn)流量在這兩組版本Pod資源之間的精確控制!
(3)創(chuàng)建Istio網(wǎng)關(guān)資源。
在Istio中要實(shí)現(xiàn)流量的精確控制,需要將VirtualService綁定到具體的Ingressgateway(入口網(wǎng)關(guān))資源。因此在創(chuàng)建VirtualService資源實(shí)現(xiàn)流量路由及控制前,需要?jiǎng)?chuàng)建一個(gè)Istio網(wǎng)關(guān)。部署文件(micro-gateway.yaml)的內(nèi)容如下:
- apiVersion: networking.istio.io/v1alpha3
- kind: Gateway
- metadata:
- name: micro-gateway
- spec:
- selector:
- istio: ingressgateway
- servers:
- - port:
- number: 80
- name: http
- protocol: HTTP
- hosts:
- - "*"
上述部署文件執(zhí)行后將創(chuàng)建一個(gè)名稱為“micro-gateway”的Istio網(wǎng)關(guān),并允許所有主機(jī)(hosts:"*"指定)通過(guò)該網(wǎng)關(guān)。
(4)創(chuàng)建Istio虛擬服務(wù)資源VirtualService。
前面提到過(guò)在Istio中主要是通過(guò)VirtualService(虛擬服務(wù))來(lái)實(shí)現(xiàn)服務(wù)網(wǎng)格內(nèi)的流量路由及控制。接下來(lái)我們看看VirtualService資源的具體創(chuàng)建方式,準(zhǔn)備資源文件(如virtual-service-all.yaml),內(nèi)容如下:
- apiVersion: networking.istio.io/v1alpha3
- kind: VirtualService
- metadata:
- name: micro-api-route
- spec:
- #用于定義流量被發(fā)送到的目標(biāo)主機(jī)(這里為部署在k8s中的micro-api服務(wù))
- hosts:
- - micro-api.default.svc.cluster.local
- #將VirtualService綁定到Istio網(wǎng)關(guān),通過(guò)網(wǎng)關(guān)來(lái)暴露路由目標(biāo)
- gateways:
- - micro-gateway
- http:
- - route:
- #設(shè)置舊版本(V1)版本的流量占比為70%
- - destination:
- host: micro-api.default.svc.cluster.local
- subset: v1
- #通過(guò)權(quán)重值來(lái)設(shè)置流量占比
- weight: 70
- #設(shè)置新版本(V2)版本的流量占比為30%
- - destination:
- host: micro-api.default.svc.cluster.local
- subset: v2
- weight: 30
如上所示,VirtualService資源具備針對(duì)http的精準(zhǔn)流量控制能力,可以將指定占比的流量路由到特定的“subset”指定的版本。而為了實(shí)現(xiàn)這一能力,VirtualService資源還需要與Istio網(wǎng)關(guān)綁定,通過(guò)Istio網(wǎng)關(guān)來(lái)暴露路由目標(biāo)。
(5)創(chuàng)建Istio目標(biāo)路由規(guī)則資源。
虛擬服務(wù)VirtualService在Istio中主要用于控制流量的行為,而定義流量行為的路由規(guī)則則需要通過(guò)“DestinationRule”路由規(guī)則資源來(lái)定義。創(chuàng)建路由規(guī)則文件(destination-rule-all.yaml),具體內(nèi)容如下:
- apiVersion: networking.istio.io/v1alpha3
- kind: DestinationRule
- metadata:
- name: micro-api-destination
- spec:
- #與Deployment資源對(duì)應(yīng)的Service資源名稱關(guān)聯(lián)
- host: micro-api
- #流量策略設(shè)置:負(fù)載均衡策略、連接池大小、局部異常檢測(cè)等,在路由發(fā)生后作用于流量
- trafficPolicy:
- #限流策略
- connectionPool:
- tcp:
- maxConnections: 10
- http:
- http1MaxPendingRequests: 1
- maxRequestsPerConnection: 1
- #設(shè)置目的地的負(fù)債均衡算法
- loadBalancer:
- simple: ROUND_ROBIN
- #目的地指的是不同的子集(subset)或服務(wù)版本。通子集(subset),可以識(shí)別應(yīng)用程序的不同版本,以實(shí)現(xiàn)流量在不同服務(wù)版本之間的切換
- subsets:
- - name: v1
- labels:
- version: v1
- - name: v2
- labels:
- version: v2
如上所示,通過(guò)subsets屬性,定義了VirtualService資源用于路由的具體版本標(biāo)簽匹配信息。至此,針對(duì)兩個(gè)版本服務(wù)的灰度流量控制規(guī)則就設(shè)置好了,接下來(lái)測(cè)試具體的金絲雀(灰度)發(fā)布效果。
(6)測(cè)試Istio實(shí)現(xiàn)金絲雀(灰度)發(fā)布的流量控制效果。
在正式測(cè)試之前,可以通過(guò)命令查看下當(dāng)前的部署資源情況:
- #查看部署的Deployment資源
- kubectl get deploy | grep micro-api
- micro-api-v1 3/3 3 3 21h
- micro-api-v2 3/3 3 3 8h
- #查看兩組版本Pod資源對(duì)應(yīng)的K8s-Service的服務(wù)IP
- kubectl get svc micro-api
- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
- micro-api ClusterIP 10.110.169.161 <none> 19090/TCP 205d
- #查看VirtualService資源定義
- kubectl get vs
- NAME GATEWAYS HOSTS AGE
- micro-api-route [micro-gateway] [micro-api.default.svc.cluster.local] 7h34m
- #查看定義的路由規(guī)則資源
- kubectl get dr
- NAME HOST AGE
- micro-api-destination micro-api 7h27m
通過(guò)上面的資源信息查看,這里我們已經(jīng)可以查到Deployments對(duì)應(yīng)的K8s-Service資源的IP,但如果通過(guò)K8s-Service資源來(lái)進(jìn)行測(cè)試的話,會(huì)發(fā)現(xiàn)流量的控制并不精準(zhǔn),并不能達(dá)到我們?cè)O(shè)置的70%流量流向v1,30%的流量流向v2(因?yàn)檫@是隨機(jī)流量)。
因此,要使用Istio的精準(zhǔn)流量控制功能,還需要使用Istio的Ingressgateway。查看Istio的Ingressgateway資源IP的命令如下:
- #查看ingress的IP
- kubectl get svc -n istio-system | grep ingress
- istio-ingressgateway LoadBalancer 10.98.178.61 <pending> 15021:31310/TCP,80:32113/TCP,443:31647/TCP,31400:30745/TCP,15443:30884/TCP 7h54m
接下來(lái),通過(guò)Ingress的IP來(lái)訪問(wèn)“micro-api”服務(wù),命令及效果如下:
- # for i in {1..10}; do curl -H "Host:micro-api.default.svc.cluster.local" 10.98.178.61:80/test/test; done
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
如上所示,流量按照設(shè)定的比例(v1:70%;v2:30%)進(jìn)行了分流。
(7)測(cè)試將流量全部切向新版本。
為了更明顯地驗(yàn)證Istio的流量控制效果,接下來(lái),我們通過(guò)變更VirtualService資源的流量設(shè)置占比,將流量全部切到新版本。變更后的VirtualService資源的配置文件內(nèi)容如下:
- apiVersion: networking.istio.io/v1alpha3
- kind: VirtualService
- metadata:
- name: micro-api-route
- spec:
- #用于定義流量被發(fā)送到的目標(biāo)主機(jī)(這里為部署在k8s中的micro-api服務(wù))
- hosts:
- - micro-api.default.svc.cluster.local
- #將VirtualService綁定到Istio網(wǎng)關(guān),通過(guò)網(wǎng)關(guān)來(lái)暴露路由目標(biāo)
- gateways:
- - micro-gateway
- http:
- - route:
- #設(shè)置舊版本(V1)版本的流量占比為70%
- - destination:
- host: micro-api.default.svc.cluster.local
- subset: v1
- #通過(guò)權(quán)重值來(lái)設(shè)置流量占比
- weight: 0
- #設(shè)置新版本(V2)版本的流量占比為30%
- - destination:
- host: micro-api.default.svc.cluster.local
- subset: v2
- weight: 100
繼續(xù)通過(guò)Istio網(wǎng)關(guān)訪問(wèn)目標(biāo)服務(wù),命令如下:
- # for i in {1..10}; do curl -H "Host:micro-api.default.svc.cluster.local" 10.98.178.61:80/test/test; done
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
- {"code":0,"data":"V3|無(wú)依賴測(cè)試接口返回->OK!","message":"成功"}
可以觀察到,此時(shí)流量已經(jīng)全部切換到了新版本服務(wù)!
后記
在微服務(wù)時(shí)代,不同的服務(wù)之間相互聯(lián)系,關(guān)系錯(cuò)綜復(fù)雜,部署升級(jí)一個(gè)服務(wù),可能造成整個(gè)系統(tǒng)的癱瘓,因此,需要選擇合適的部署方式,從而將風(fēng)險(xiǎn)降到最低。金絲雀(灰度)發(fā)布只是多種部署方式的一種,還有藍(lán)綠部署、滾動(dòng)部署(如K8s的滾動(dòng)升級(jí))等,可以根據(jù)不同的業(yè)務(wù)場(chǎng)景選擇不同的發(fā)布形式。