Kubernetes實(shí)戰(zhàn)指南:零宕機(jī)無縫遷移Spring Cloud至k8s
1. 項(xiàng)目遷移背景
1.1 為什么要在“太歲”上動(dòng)土?
目前公司的測(cè)試環(huán)境、UAT環(huán)境、生產(chǎn)環(huán)境均已經(jīng)使用k8s進(jìn)行維護(hù)管理,大部分項(xiàng)目均已完成容器化,并且已經(jīng)在線上平穩(wěn)運(yùn)行許久。在我們將大大小小的項(xiàng)目完成容器化以后,測(cè)試、UAT、生產(chǎn)環(huán)境的發(fā)版工具以及CICD流程慢慢的實(shí)現(xiàn)統(tǒng)一化管理,并且基于k8s開發(fā)了內(nèi)部的發(fā)版審核平臺(tái),同時(shí)接入了Jira等項(xiàng)目管理工具。
在自研平臺(tái)進(jìn)行發(fā)版時(shí),能夠自動(dòng)關(guān)聯(lián)項(xiàng)目的開發(fā)進(jìn)度以及Release版本,最重要的是其可以控制發(fā)版權(quán)限、統(tǒng)一發(fā)版工具及發(fā)版模式,并且支持一鍵式發(fā)版多個(gè)項(xiàng)目的多個(gè)模塊,同時(shí)也包括了發(fā)版失敗應(yīng)用的統(tǒng)一回滾及單個(gè)應(yīng)用的回滾。
因?yàn)樵擁?xiàng)目從始至今一直在使用GitRunner進(jìn)行發(fā)版,并且基于虛機(jī)部署,所以一直沒有集成到發(fā)版審核平臺(tái),但是由于項(xiàng)目比較重要,并且涉及的服務(wù)和機(jī)器較多,所以必須要把這個(gè)項(xiàng)目進(jìn)行容器化并且統(tǒng)一發(fā)版工具才能更好的適應(yīng)公司的環(huán)境,以及更好的應(yīng)對(duì)下一代云計(jì)算的發(fā)展。
1.2 為什么要棄用Git Runner?
首先我們看一下Git Runner發(fā)版的頁面,雖然看起來很簡潔清爽,但是也難免不了會(huì)遇到一些問題。

1.2.1 多分支并行開發(fā)問題
當(dāng)多分支并行開發(fā)或者能夠發(fā)版到生產(chǎn)環(huán)境的分支較多時(shí),很容易在手動(dòng)部署的階段點(diǎn)錯(cuò),或者看串行,當(dāng)然這種概率很小。
但是我們可以看到另外一個(gè)問題,每次提交或者合并,都會(huì)觸發(fā)構(gòu)建,當(dāng)我們使用Git Flow分支流時(shí),可能同時(shí)有很多分支都在并行開發(fā)、并行測(cè)試、并行構(gòu)建,如果Git Runner是基于虛機(jī)創(chuàng)建的,很有可能會(huì)出現(xiàn)構(gòu)建排隊(duì)的情況,當(dāng)然這個(gè)排隊(duì)的問題,也是能解決的。
1.2.2 多微服務(wù)配置維護(hù)問題
其次,如果一個(gè)項(xiàng)目稍微大一些,維護(hù)起來也不是很方便。比如這個(gè)準(zhǔn)備要遷移的項(xiàng)目,一個(gè)前端和二十多個(gè)業(yè)務(wù)應(yīng)用,再加上Zuul、ConfigServer、Eureka將近三十個(gè)服務(wù),每個(gè)服務(wù)對(duì)應(yīng)一個(gè)Git倉庫,然后每個(gè)服務(wù)同時(shí)在開發(fā)的分支又有很多,如果想要升級(jí)GitLab CI腳本或者微服務(wù)的機(jī)器想要添加節(jié)點(diǎn),這將是一個(gè)枯燥乏味的工作。
1.2.3 安全問題
最后,還有一個(gè)安全的問題,GitLab的CI腳本一般都是內(nèi)置在代碼倉庫里面的,這就意味著任何有Push或者M(jìn)erge權(quán)限的人都可以隨意的修改CI腳本,這會(huì)導(dǎo)致意想不到的結(jié)果,同時(shí)也會(huì)威脅到服務(wù)器和業(yè)務(wù)安全,針對(duì)發(fā)版而言,可能任何的開發(fā)者都可以點(diǎn)擊發(fā)版按鈕,這些可能一直都是一個(gè)安全隱患。
但是這些并不意味著Git Runner是一個(gè)不被推薦的工具,新版的GitLab內(nèi)置的Auto DevOps和集成Kubernetes依舊很香。但是可能對(duì)于我們而言,使用Git Runner進(jìn)行發(fā)版的項(xiàng)目并不多,所以我們想要統(tǒng)一發(fā)版工具、統(tǒng)一管理CI腳本,所以可能其它的CI工具更為合適。
1.3 為什么要容器化?
1.3.1 端口沖突問題
容器化之前這個(gè)項(xiàng)目采用虛機(jī)部署的,每個(gè)虛擬機(jī)交叉的啟動(dòng)了兩個(gè)或者三個(gè)微服務(wù),這會(huì)遇到一個(gè)問題,就是端口沖突的問題,在項(xiàng)目加入新應(yīng)用時(shí),需要考慮服務(wù)器之間端口沖突問題的,還要考慮每個(gè)微服務(wù)的端口不能一樣,因?yàn)槭褂锰摂M機(jī)部署應(yīng)用時(shí),可能會(huì)有機(jī)器節(jié)點(diǎn)故障需要手動(dòng)遷移應(yīng)用的情況,如果部分微服務(wù)端口一樣,遷移的過程可能會(huì)受阻。
另外,當(dāng)一個(gè)項(xiàng)目只有幾個(gè)應(yīng)用時(shí),端口維護(hù)起來可能沒有什么問題,像本項(xiàng)目,涉及三十多個(gè)微服務(wù),這就會(huì)成為一件很痛苦的事情。而使用容器部署時(shí),每個(gè)容器相互隔離,所有應(yīng)用可以采用同樣的端口,就無需再去關(guān)心端口的問題。
1.3.2 程序健康問題
使用過Java程序的人大部分都遇到過程序假死的情況,比如端口明明是通的,但是請(qǐng)求就是不處理,這就是一種程序假死的現(xiàn)象。而我們?cè)谑褂锰摍C(jī)部署時(shí),往往不能把健康檢查做的很好,或許在虛機(jī)上面并沒有做接口級(jí)的健康檢查,這就會(huì)造成程序假死無法自動(dòng)處理的問題,并且在虛機(jī)上面做一些接口級(jí)的健康檢查及處理操作并不是一件簡單的事情,同樣也是一件枯燥乏味的事情,尤其是當(dāng)一個(gè)項(xiàng)目微服務(wù)過多,健康檢查接口不一致時(shí)更為痛苦。
但在k8s上面,自帶的Read和Live探針用以處理上面的問題就極其簡單,如圖所示,我們可以看到目前支持三種方式的健康檢查:

- tcpSocket: 端口健康檢查
- exec: 根據(jù)指定命令的返回值
- httpGet: 接口級(jí)健康檢查
同時(shí)這些健康檢查的靈活性也很高,可以自定義檢查間隔、錯(cuò)誤次數(shù)、成功次數(shù)、檢查Host等參數(shù),而且上面提到的接口級(jí)健康檢查httpGet也支持自定義主機(jī)名、請(qǐng)求頭、檢查路徑以及HTTP或者HTTPS等配置,可以看到用k8s自帶的健康檢查可以省去我們很大一部分工作,不用再去維護(hù)非常多令人討厭的腳本。
1.3.3 故障恢復(fù)問題
在使用虛機(jī)部署應(yīng)用時(shí),有時(shí)可能會(huì)碰到宿主機(jī)故障,單節(jié)點(diǎn)的應(yīng)用無法使用,或者多節(jié)點(diǎn)部署的應(yīng)用由于其他副本不可用,導(dǎo)致自身壓力大出現(xiàn)服務(wù)延遲的情況。而恰恰宿主機(jī)無法很快恢復(fù),這時(shí)可能就需要手動(dòng)添加節(jié)點(diǎn)或者需要新加服務(wù)器才能解決這類問題,這個(gè)過程可能會(huì)很漫長,或許也很痛苦。因?yàn)樾枰?zhǔn)備依賴環(huán)境,然后才能去部署自己的應(yīng)用,并且有時(shí)候你可能還需要更改CI腳本。。。
而使用k8s編排時(shí),我們無需關(guān)心這類問題,一切的故障恢復(fù)、容災(zāi)機(jī)制都由強(qiáng)大的k8s負(fù)責(zé),你可以去喝杯咖啡,或者你剛打開電腦去處理這個(gè)問題時(shí),一切都已經(jīng)恢復(fù)如初。
1.3.4 其他小問題
當(dāng)然k8s給我們帶來的便利性和解決的問題遠(yuǎn)不止上面所說的,容器鏡像幫我們解決了依賴環(huán)境的問題,服務(wù)編排幫我們解決了故障容災(zāi)的問題,我們可以使用k8s的包管理工具一鍵創(chuàng)建一套新的環(huán)境,我們可以使用k8s的服務(wù)發(fā)現(xiàn)讓開發(fā)人員無需再關(guān)注網(wǎng)絡(luò)部分的開發(fā),我們可以使用k8s的權(quán)限控制讓運(yùn)維人員無需再去管理每臺(tái)服務(wù)器的權(quán)限,我們可以使用k8s強(qiáng)大的應(yīng)用程序發(fā)布策略讓我們無需過多的考慮如何實(shí)現(xiàn)零宕機(jī)發(fā)布應(yīng)用及應(yīng)用回滾,等等,這一切的便利性正在悄悄的改變著我們的行為。
2. 遷移計(jì)劃
2.1 藍(lán)綠遷移
首先來看一下遷移之前的架構(gòu)

和大多數(shù)SpringCloud架構(gòu)一樣,使用NodeJS作為前端,Eureka用作服務(wù)發(fā)現(xiàn),Zuul進(jìn)行路由分發(fā),ConfigServer作為配置中心。這種架構(gòu)也是SpringCloud在企業(yè)中最普遍的架構(gòu),沒有使用更多額外的組件,所以我們?cè)诘谝淮芜w移時(shí),也沒有考慮太多,還是按照遷移其他項(xiàng)目使用的方案,即在k8s上新建一套環(huán)境(本次遷移沒有涉及到中間件),也就是容器化環(huán)境,配置一個(gè)同樣的域名,然后添加hosts解析進(jìn)行測(cè)試,沒有問題的話直接進(jìn)行域名切換即可完成遷移。這種方式是最簡單也是最常用的方式,類似于程序發(fā)版的藍(lán)綠部署,此時(shí)在k8s新建一套環(huán)境對(duì)應(yīng)的架構(gòu)圖如下:

在進(jìn)行測(cè)試時(shí),此項(xiàng)目同時(shí)并行了兩套環(huán)境,一套虛機(jī)環(huán)境,一套容器環(huán)境,容器環(huán)境只接收測(cè)試人員的流量,兩套環(huán)境連接的是同一套中間件服務(wù),因?yàn)槠渌?xiàng)目大部分也是按照這種方式遷移的,并且該項(xiàng)目在測(cè)試環(huán)境也進(jìn)行過同樣的流程,沒有出現(xiàn)什么問題,所以也同樣認(rèn)為這種方式在本項(xiàng)目也不會(huì)出現(xiàn)什么問題。但往往現(xiàn)實(shí)總會(huì)與預(yù)期有所差異,在測(cè)試過程中由于兩套環(huán)境并存,導(dǎo)致了部分生產(chǎn)數(shù)據(jù)出現(xiàn)問題,由于容器環(huán)境沒有經(jīng)過完整性測(cè)試,也沒有強(qiáng)制切換域名,后來緊急關(guān)停了所有的容器問題才得以恢復(fù)。由于時(shí)間比較緊迫,我們并沒有仔細(xì)排查問題所在,只是修復(fù)了部分?jǐn)?shù)據(jù),后來我們認(rèn)為可能是遷移過程中部分微服務(wù)master分支和生產(chǎn)代碼不一致造成的,當(dāng)然也可能并不是這么簡單。為了規(guī)避這類問題再次發(fā)生只能去修改遷移方案。
2.2 灰度遷移
由于上面的遷移方案出了點(diǎn)問題,就重新定了一個(gè)方案,較上次略微麻煩,采用逐個(gè)微服務(wù)遷移至k8s,類似于應(yīng)用程序發(fā)版的灰度發(fā)布。
單個(gè)應(yīng)用遷移時(shí),需要確保容器環(huán)境和虛機(jī)環(huán)境的代碼一致,在遷移時(shí)微服務(wù)采用域名注冊(cè)的方式。也就是每個(gè)微服務(wù)都配置一個(gè)內(nèi)部域名,通過域名去注冊(cè)到Eureka,而不是采用容器的IP和端口去注冊(cè)(因?yàn)閗8s內(nèi)部的IP和虛擬機(jī)未打通),此時(shí)的環(huán)境如下圖所示:

此時(shí)有一個(gè)域名service-c.interservice.k8s指向ServiceC,然后ServiceC注冊(cè)到Eureka時(shí)修改自己的地址為該域名(默認(rèn)是宿主機(jī)IP+端口),之后別的應(yīng)用通過該地址調(diào)用ServiceC,當(dāng)ServiceC測(cè)試無問題后,下線虛擬機(jī)里面的ServiceC,最后的架構(gòu)如圖所示:

除了Zuul、前端UI和Eureka,其他服務(wù)都使用灰度的方式遷移到k8s,比藍(lán)綠的形式更為復(fù)雜,需要為每個(gè)微服務(wù)單獨(dú)創(chuàng)建Service、域名,在遷移完成之后還需要?jiǎng)h除。到這一步后,除了Eureka其他服務(wù)都已經(jīng)部署在k8s上,而對(duì)于Eureka的遷移,涉及的細(xì)節(jié)更多。
2.3 Eureka遷移
到這一步后,服務(wù)訪問沒有出現(xiàn)其他問題,除了Eureka之外的服務(wù),都已經(jīng)部署在k8s,而Eureka的過渡性遷移設(shè)計(jì)的問題可能會(huì)更多。因?yàn)槲覀儾荒苤苯釉趉8s上部署一套高可用的Eureka集群,然后直接把ConfigServer里面的微服務(wù)注冊(cè)地址改成k8s中的Eureka地址,因?yàn)榇藭r(shí)兩個(gè)Eureka集群都是獨(dú)立的Zone,注冊(cè)信息并不會(huì)共享,這種會(huì)在更改配置的過程中丟失注冊(cè)信息,此時(shí)架構(gòu)圖可能會(huì)出現(xiàn)如下情況:

也就是在替換配置的過程中,可能會(huì)有ServiceA注冊(cè)到了之前的Eureka上,ServiceB注冊(cè)到了k8s中的Eureka,就會(huì)導(dǎo)致ServiceA找不到ServiceB,反過來也是同樣的問題。
所以在k8s搭建Eureka的集群后,需要給每個(gè)Eureka實(shí)例配置一個(gè)臨時(shí)域名,然后更改之前的Eureka集群和k8s里面的Eureka集群的zone配置,讓k8s里面的Eureka和虛機(jī)里面的Eureka組成一個(gè)新的集群,這樣注冊(cè)信息就會(huì)被同步,無論注冊(cè)到Eureka都不會(huì)造成服務(wù)找不到,此時(shí)的架構(gòu)圖如下(此時(shí)所有的服務(wù)還是注冊(cè)到原來的Eureka集群中):

接下來需要做的事情,就是更改微服務(wù)的配置,此時(shí)需要更改地方有三處:
- 微服務(wù)注冊(cè)到Eureka的地址更為容器IP和端口,不再使用域名注冊(cè),因?yàn)榇藭r(shí)微服務(wù)都已經(jīng)在k8s中,直接通過內(nèi)部Pod IP即可連接;
- 更改服務(wù)注冊(cè)的Eureka地址為k8s Eureka的service地址,Eureka使用StatefulSet部署,直接通過eureka-0/1/2.eureka-headless-svc就可以連接;
- 待所有的微服務(wù)都已經(jīng)遷移完畢后,更改k8s的Eureka集群的zone為:eureka-0/1/2.eureka-headless-svc,并刪除其他微服務(wù)的Service和域名。
最終的架構(gòu)圖如圖:

3. 總結(jié)
為了保證服務(wù)的可用性,我們無奈的采用灰度的方式進(jìn)行遷移,比藍(lán)綠的方式麻煩了很多,而且需要考慮的問題也有很多。在程序沒有任何問題的前提下,還是建議采用藍(lán)綠的方式進(jìn)行遷移,不僅遇到的問題少,遷移也比較方便快捷。當(dāng)然采用灰度的方式對(duì)于大型的項(xiàng)目或者不能中斷服務(wù)的項(xiàng)目可能更為穩(wěn)妥,因?yàn)橐淮涡匀壳袚Q可能會(huì)有遺漏的需要測(cè)試的地方。當(dāng)然無論哪種方式,對(duì)應(yīng)用的容器化、遷移至Kubernetes才是比較重要的事情,畢竟云計(jì)算才是未來,而Kubernetes是云計(jì)算的未來。
原文鏈接:https://www.cnblogs.com/dukuan/p/13285941.html