AWS EKS中實現(xiàn)應用平滑升級,使用aws的ecs構(gòu)建的架構(gòu)圖AWS EKS中實現(xiàn)應用平滑升級在EKS中,運行的應用大多都會考慮和CI/CD或者DevOps流程相集成,從而可以更快發(fā)布新版本應用,將新產(chǎn)品功能推向市場。同時,由于EKS自身的版本也有生命周期,所以也會面臨由于EKS升級而導致的應用向新的NodeGrou......
在EKS中,運行的應用大多都會考慮和CI/CD或者DevOps流程相集成,從而可以更快發(fā)布新版本應用,將新產(chǎn)品功能推向市場。同時,由于EKS自身的版本也有生命周期,所以也會面臨由于EKS升級而導致的應用向新的NodeGroup進行的平級遷移。如何做到應用的平滑升級是快速上線新產(chǎn)品特性的重要前提。
在EKS環(huán)境中,為了實現(xiàn)應用的平滑升級,需要滿足以下幾個方面的需求:
1.在舊的容器實例被終止前,必須要有新的實例被創(chuàng)建來為后續(xù)的新請求服務。這也就要求應用能同時運行多個容器副本,也就要求有客戶端或者服務端的負載均衡器將請求在新舊版本之間進行分發(fā),同時要求容器應用的無狀態(tài)化,有狀態(tài)的容器應用不在此文章的討論范圍內(nèi)。
2.確保創(chuàng)建的的新版本容器能處理應用請求后再加入到負載均衡器后端:在新的實例被創(chuàng)建后,必須等待完成應用的初始化,才能被設置為可處理新請求的Ready狀態(tài),避免新請求路由到未準備好的應用容器。由于Kubernetes的Pod機制支持在Pod創(chuàng)建時,可以使用Readiness探針機制來保證只有Pod中的應用已經(jīng)就緒時才加入到前端Service的Endpoints列表中,從而可以正常接受請求輸入。由于此部分實現(xiàn)方式較為簡單明了,所以本文不作過多討論。
3.確保舊版本容器在徹底被移除前處理完遺留請求:在停止容器時,需要保證容器處理完當前的請求才能最終退出,避免引起客戶端錯誤。如何實現(xiàn)這個保證,將會是本文討論的重點。
4.需要使用特定的升級方法,如滾動升級和藍綠部署方法。Kubernetes的Recreate方法不適用于應用的平滑升級,因為這種方法會有應用上的停機時間。
本文將深入內(nèi)里討論在EKS環(huán)境中以下幾種情形下是否可以實現(xiàn)平滑升級,以及如何實現(xiàn)。(EKS中Pod的IP地址是平坦模式,也就是和計算節(jié)點地址在同一范圍內(nèi)的物理地址,不同于采用Overlay方式時的地址模式,所以本文的部分內(nèi)容可能不適用其他Kubernetes環(huán)境)。
1.在純EKS場景下.Kubernetes的Service對象作為服務端負載均衡器,Deployment的滾動部署作為升級方法。
2.在EKS中使用SpringCloud微服務框架的場景下.Ribbon作為客戶端負載均衡器,Deployment的滾動部署作為升級方法。
3.在EKS中使用AWS AppMesh/Istio這種以SideCar形式實現(xiàn)微服務框架的場景下.Virtual Service作為客戶端負載均衡器,采用Virtual Service的藍綠部署方法。
在純EKS場景下實現(xiàn)平滑升級
在純屬EKS環(huán)境中,Kubernetes的Service對象作為服務端負載均衡器,Deployment的滾動部署作為升級方法。
在升級應用過程中停止掉舊版本的容器時,需要停止向正在被停止的容器分發(fā)新請求,同時也要容忍容器將連接中未處理請求處理完成再退出,也即要滿足如下兩個條件:
條件1:在停止容器后,如果有新請求發(fā)國際快遞此容器,請求自然會得不到正確的響應,所以要求在前端的請求分發(fā)層面,不再將請求路由到將即將刪除的容器。
條件2:但同時要求針對未完成的請求能繼續(xù)處理,比如在客戶端和后端服務容器之間建立的連接上,仍有正在處理的請求或者有后續(xù)的連續(xù)請求,應該要允許這些請求處理完成。
如何在Pod的停止過程中實現(xiàn)如上兩個條件,需要解析Pod生命周期中的停止過程。
圖一:Pod關(guān)閉流程
開始Pod的delete流程,Pod進入關(guān)閉流程,觸發(fā)原因可能是用戶執(zhí)行Pod的delete操作,也有可能是執(zhí)行rollout操作,也有可能是減少replicaset的數(shù)量。其整個流程如下:(以下過程從第1到第4步是同時發(fā)生)。
1.Kubernetes將容器狀態(tài)置為Terminating,使用命令查看狀態(tài)時會處于Terminating狀態(tài)。
2.同時通知API Server將Pod從其所屬的Endpoints中移除,比如從使用selector歸屬的某一個Service中移除,此操作執(zhí)行過程,Service的后端列表中將移除此Pod。
3.啟動Grace shotdown定時器(值由deployment中的terminationGracePeriodSeconds參數(shù)指定,默認值為30)。通知Pod所屬節(jié)點的Kubelet開始執(zhí)行Pod的shutdown操作。
4.通知計算節(jié)點上的Kubelet守護進程關(guān)閉相應的Pod。
如果Pod定義了preStop hook,kubelet會觸發(fā)此hook腳本在Pod中的執(zhí)行.preStop Hook中執(zhí)行用戶定義過程。執(zhí)行完成后,發(fā)快遞TERM信號給Pod里面的所有容器。
如果未定義PreStop Hook,直接發(fā)快遞TERM給Pod中的所有容器。
5.執(zhí)行上述過程后,如果所有清楚過程在Grace Period周期內(nèi)結(jié)束,則Pod結(jié)束工作完成。如果Pod仍然未退出,則發(fā)快遞KILL信號,強制結(jié)束Pod。針對有preStop Hood的Pod,在發(fā)快遞KILL信號前,有額外一次2秒的時延。
6.到此,Pod實體已經(jīng)結(jié)束,繼續(xù)將Pod從API Server記錄中清除,此時,客戶端無法再檢索到Pod。
首先分析Pod被刪除時,是否會從其所屬的負載均衡器中移除,從而不再接收新的請求。純EKS模式下,Pod采用Kubernetes Service作為負載均衡器,Kubernetes Service是依賴在計算節(jié)點中的Iptables服務來實現(xiàn)。以一個Service有兩個對應的Pod為例,查看其在Iptables表中的實現(xiàn)。
1.找到Service的Cluster IP:kubectl get service phpapacheo customcolumns=ClusterIP:.spec.clusterIP
2.登錄任意節(jié)點,查看Cluster IP對應的Iptables條目
iptablesLt nat
Chain KUBESERVICES(2 references)
target prot opt source destination
KUBESVCBJ47X7EXARPH4MMJ tcp—0.0.0.0/0 10.100.81.62/default/phpapache:cluster IP/tcp dpt:80
Chain KUBESVCBJ47X7EXARPH4MMJ(1 references)
target prot opt source destination
KUBESEPFEFBLPTU36TWIWKJ all—0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.50000000000
KUBESEP2CJO3665RSYCSLOT all—0.0.0.0/0 0.0.0.0/0
Chain KUBESEP2CJO3665RSYCSLOT(1 references)
target prot opt source destination
KUBEMARKMASQ all—192.168.92.92 0.0.0.0/0
DNAT tcp—0.0.0.0/0 0.0.0.0/0 tcp to:192.168.92.92:80
KUBEMARKMASQ all—192.168.92.92 0.0.0.0/0
DNAT tcp—0.0.0.0/0 0.0.0.0/0 tcp to:192.168.92.92:80
每一個Cluster IP都是在Iptables的chain中,以ClusterIP為匹配條件,鏈的背后是兩個目標KUBESEPFEFBLPTU36TWIWKJ和KUBESEP2CJO3665RSYCSLOT,分別對應的是后端兩個Pod的地址。兩個目標之間是利用statistic模式采用random選擇,否則流量只會路由到第一個目標中。0.50000000表示選擇到第一個目標的機率是50%,那選擇到第二個目標的機率也是50%。
在刪除Pod的同時,觀察在刪除Pod時iptables中的變化,watchn 0.5“iptablesLt natngrep KUBESVCDTU5BYSZICDKSKGDA 3″。在delete命令執(zhí)行后,API Server立即通知各計算節(jié)點上kubelet守護進程將Iptables中對應Pod的target刪除,此實現(xiàn)可以滿足條件一:執(zhí)行刪除Pod的命令后,不再將新請求發(fā)國際快遞正在被結(jié)束的Pod上。
假如請求是通過基于TCP,或者基于Http1.1或者Http2.0這種長連接機制的協(xié)議,后續(xù)請求可能會共用同一連接,而將后續(xù)的請求發(fā)國際快遞被刪除的Pod,那Iptables實現(xiàn)的Service負載均衡是否也可以將同一連接中未完成的后續(xù)請求繼續(xù)發(fā)國際快遞舊版本Pod呢?這就涉及到Iptables中的兩條規(guī)范:
Chain中目標的選擇,只在建立TCP連接的時候進行。
IPtables的ConnCtrack機制通過Iptables建立的連接,記錄連接對應的后端目標。
通過規(guī)范一,可以保證針對舊版本Pod的后續(xù)的請求不會發(fā)國際快遞新的Pod。
通過規(guī)范二,可以保證針對舊版本Pod的后續(xù)的請求仍然發(fā)國際快遞正在被停止的舊版本Pod上。
也就是先前通過iptables建立的到后端的Iptables的連接,會在conntrace模塊中記錄連接信息。通過在發(fā)起節(jié)點上的connctrack中記錄連接信息,連接中的后續(xù)請求可以直接經(jīng)過同一連接到達后端。連接保持信息可以通過cat/proc/net/nfconntrack文件查看,如
ipv4 2 tcp 6 86400 ESTABLISHED src=192.168.25.253 dst=10.100.81.62 sport=34784 dport=80 src=192.168.23.104 dst=192.168.25.253 sport=80 dport=34784[ASSURED]mark=0 zone=0 use=2
也就是建立的連接信息會保持一段時間,此時間由系統(tǒng)選項/proc/sys/net/netfilter/nfconntracktcptimeoutestablished控制,也就是這里的86400s/24小時。也即是只要雙方不主動關(guān)閉連接,仍然能將請求在后續(xù)24小時內(nèi)通過conntrack發(fā)國際快遞對應的后端。
連接信息已經(jīng)保存,請求可以繼續(xù)向目標發(fā)快遞,只要求目標Pod繼續(xù)在這段時間內(nèi)存活就可以繼續(xù)處理后續(xù)的請求??梢酝ㄟ^terminationGracePeriodSeconds參數(shù)延緩對Pod的KILL信號發(fā)快遞而延長Pod的存活時間,因為KILL信號無法被Pod捕獲,Pod中的進程會被系統(tǒng)強制結(jié)束。通過此參數(shù)指定一個Pod結(jié)束的最長時間,并配合如下機制:
*利用preStop hook,在這個hook中執(zhí)行例如sleep一段時間這樣的操作,目的是為了延緩進入到下一步結(jié)束過程的處理。在hook執(zhí)行的這一段時間里,Pod可以繼續(xù)正常運行。
preStop hook配合terminationGracePeriodSeconds就能實現(xiàn)延緩Pod結(jié)束的目的,當然也可以考慮在TERM的信號處理函數(shù)中執(zhí)行延緩退出操作,但推薦采用preStop hook的方式,因為TERM信號處理需要在應用代碼中進程編程,而且需要Pod中每個容器都要進行TERM捕獲的捕獲,復雜度較高。相反preStop hook這種方式可以直接在deployment中配置,簡單明了。配置如下:
spec:
containers:
lifecycle:
preStop:
exec:
command:
/bin/sh c sleep 120
terminationGracePeriodSeconds: 120
通過以上過程的分析,通過EKS的基于Iptables的Service服務結(jié)合滾動升級是可以實現(xiàn)平滑升級的。包含以下Service類型
*ClusterIP/NodePort/EKS中類型為LoadBalancer,因為這些Service都是基于利用Iptables實現(xiàn)服務端負載均衡。
但不包含類型為Headless的Service,因為此種Service的實現(xiàn)不基于Iptables。
不過由于以上機制是kubernetes配合計算節(jié)點的實現(xiàn),不是純粹的應用層機制。如果能完全在應用層實現(xiàn)平滑升級,則整個過程更優(yōu)雅。
在EKS中使用SpringCloud微服務框架的場景下實現(xiàn)平滑升級
在SpringCloud微服務框架下,服務組件之間采用Ribbon進行請求的分發(fā)。Ribbon是客戶端負載均衡模式,后端服務節(jié)點的選擇由客戶端負責,在Pod的delete過程中需要讓客戶端不再分發(fā)新的請求到正被刪除的節(jié)點。需要有機制讓客戶端感知到后端Pod的刪除行為,此機制同樣可以利用Pod的preStop hook加以實現(xiàn)。以客戶端應用通過Zuul訪問后端服務為例,與在純粹的EKS環(huán)境中preStop hook單純執(zhí)行sleep操作不同的是,要在preStop hoo中執(zhí)行:
1.向Eureka Server注銷自己
2.等待客戶端Ribbon中的ServerList過期,在此時間內(nèi),Pod仍然能接收前端新的請求
圖二:SpringCloud中服務注銷及失效周期
由于Eureka和Zuul中組件都有各自的緩存,為了讓Pod在客戶端徹底過期,需要準確計算應該在上述第二步中的等待時間,涉及以下幾個階段。
Pod向Eurora注銷自己,清除readwritemap中的記錄,同時停止發(fā)快遞心跳防止再注冊
Eureka的readwritemap map同步信息到readonlymap的間隔時間eureka.server.responsecacheupdateintervalms(30s)
Zuul作為Eureka客戶端,每隔eureka.client.registryfetchintervalseconds(30)去從readonly map中去拉取服務列表
Ribbon更新其ServerList的時間間隔ribbon.ServerListRefreshInterva(30)
Pod在結(jié)束前主動向Eureka發(fā)起注銷操作,則要保證在preStop hook中等待30+30+30=90秒的時間,客戶端才不會再使用此服務的endpoint建立新的請求連接,所以此時的terminationGracePeriodSeconds值應該設置為90。如果此連接是長連接,則還要加上在此連接中處理所有請求需要的時間作為最終的等待時間,比如5s,那在hoop中等待的時間應該是95s。
在內(nèi)部服務之間使用Feign進行訪問,F(xiàn)eign內(nèi)部也依賴Ribbon進行負載均衡,所以整個實現(xiàn)方法和Zuul場景下并無不同。
在EKS環(huán)境中使用AppMesh/Istio Sidecar形式的微服務框架實現(xiàn)平滑升級
AppMesh是AWS推出的基于sidecar的微服務框架,基于每個集成到每個Pod的Sidecar,可以實現(xiàn)請求在多個版本的Pod之間分發(fā),從而可以以藍綠部署的方式實現(xiàn)Pod級的應用平滑升級。
AppMesh在標準的Kubernetes的Service和Pod基礎之上,將一個Service抽象成如下架構(gòu)。
圖三:AppMesh架構(gòu)圖
AppMesh中各組件定義如下:
由于Virtual Router里面在定義路由規(guī)則時,可以將請求分別定向到不同版本的后端,并且可以在不同后端之間通過調(diào)整Weight值進行按比例分配,所以就為應用提供了藍綠部署的能力。通過藍綠部署,應用可以以最小的代價實現(xiàn)平滑升級,不需要考慮Kubernes在停止Pod時的具體實現(xiàn),也不需要考慮SpringCloud中服務向客戶端注銷的問題。
在AppMesh中升級后端應用時,只需要使用新的Deployment和Service創(chuàng)建新的后端,并建立新的Virtual Node,將在Virtual Rotuer中將部分流量分配到新的Virtual Node。在確認新版本應用組件工作正常后,在Virtual Router中只需要將舊版本應用組件對應的Virtual Node的Weight設置為0,新版本應用組件對應的Virtual Node的Weight權(quán)重設置為100即可,這樣可以滿足條件1:新進入的請求不會到達舊版本的Pod,而是全部發(fā)快遞給新版本的Pod。那是否能滿足條件2:舊版本的請求仍然發(fā)國際快遞與舊版本建立的連接中呢 ?為了進一步說明AppMesh及Istio場景下,在進行藍綠部署時,是否能保證舊版本的服務仍能提供連接供未完成的請求執(zhí)行,特對Envoy的請求連接作進一步說明。以AWS的EKS Workshop中AppMesh集成實驗為例:https://www.eksworkshop.com/advanced/310servicemeshwithistio/。
圖四:AppMesh客戶端與服務請求服務流程圖
1.客戶端中的envoy會和服務端中的每條路由條目的對象所屬的envoy建立一個長鏈接。
2.客戶端發(fā)起對服務端的訪問時,實際上的新建一個向與自身同Pod的Envoy Proxy建立連接發(fā)起請求。
3.Envoy Proxy通過長連接發(fā)快遞請求到服務端Pod的Envoy Proxy
4.服務端的Envoy Proxy再建立一個臨時連接發(fā)快遞請求到同Pod的應用容器,從而完成整個請求鏈。
5.在設置到舊版本服務的Weight為0時,Envoy Proxy到服務端之間的長鏈接仍然保持,而且是永久保持,除非一端主動發(fā)起關(guān)閉。所以不轉(zhuǎn)發(fā)新的請求到舊版本服務端,但仍然可以繼續(xù)處理完未完成的舊請求。
綜上雖然舊版本的Weight權(quán)限設置為0,但客戶端和其建立的連接卻會繼續(xù)保持,所以可以平滑切換,不會造成業(yè)務中斷。
針對Istio,里面同樣有Virtual Service的抽象,且數(shù)據(jù)層面采用的是與AppMesh一致的Envoy Proxy,以Istio自然而然也支持通過藍綠部署的方式實現(xiàn)平滑升級。
總結(jié)
通過在不同的使用模式下,有不同的方法來實現(xiàn)應用的平滑升級
1.純Kubernetes模式下:可以實現(xiàn)滾動模式的平滑升級,需要借助Service的負載均衡能力,以及Pod的preStop能力,讓Pod在退出前能繼續(xù)處理未完成的請求,在preStop中的等待時間取決于客戶端一次完整請求的持續(xù)時間。
2.SpringCloud微服務框架下:此時Pod的preStop hook中,還需要執(zhí)行向服務注冊服務如Eureka注銷自己,并等待客戶端中Ribbon記錄過期從而不同轉(zhuǎn)發(fā)新請求到正在被停止的Pod,同樣地,其等待時間還需要加上客戶端一次完整請求的持續(xù)時間。所以preStop中hook的等待時間為客戶端記錄過期時間加上一次完整請求需要的處理時間。
3.在AppMesh及Istio微服務框架下:每個Pod的SideCar中包含了完整的Virtual Service及請求Weight分配能力實現(xiàn)藍綠部署,所以可以做到對每個Pod的平滑升級,而且不需要針對Pod配置preStop,這種模式下實現(xiàn)應用服務的平滑升級最為方便。
特別聲明:以上文章內(nèi)容僅代表作者本人觀點,不代表ESG跨境電商觀點或立場。如有關(guān)于作品內(nèi)容、版權(quán)或其它問題請于作品發(fā)表后的30日內(nèi)與ESG跨境電商聯(lián)系。
二維碼加載中...
使用微信掃一掃登錄
使用賬號密碼登錄
平臺顧問
微信掃一掃
馬上聯(lián)系在線顧問
小程序
ESG跨境小程序
手機入駐更便捷
返回頂部