您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關(guān)如何解析一次客戶需求引發(fā)的K8s網(wǎng)絡(luò)探究,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。
第一部分:“頗有個性”的需求
客戶在云上環(huán)境使用了托管K8s集群產(chǎn)品部署測試集群。因業(yè)務(wù)需要,研發(fā)同事需要在辦公網(wǎng)環(huán)境能直接訪問K8s集群的clueterIP類型的service和后端的pod。通常K8s的pod只能在集群內(nèi)通過其他pod或者集群node訪問,不能直接在集群外進行訪問。而pod對集群內(nèi)外提供服務(wù)時需要通過service對外暴露訪問地址和端口,service除了起到pod應(yīng)用訪問入口的作用,還會對pod的相應(yīng)端口進行探活,實現(xiàn)健康檢查。同時當(dāng)后端有多個Pod時,service還將根據(jù)調(diào)度算法將客戶端請求轉(zhuǎn)發(fā)至不同的pod,實現(xiàn)負載均衡的作用。常用的service類型有如下幾種:
service類型簡介
1、 clusterIP類型,創(chuàng)建service時如果不指定類型的話的默認會創(chuàng)建該類型service,clusterIP類型的service只能在集群內(nèi)通過cluster IP被pod和node訪問,集群外無法訪問。通常像K8s集群系統(tǒng)服務(wù)kubernetes等不需要對集群外提供服務(wù),只需要在集群內(nèi)部進行訪問的service會使用這種類型;
2、 nodeport類型,為了解決集群外部對service的訪問需求,設(shè)計了nodeport類型,將service的端口映射至集群每個節(jié)點的端口上。當(dāng)集群外訪問service時,通過對節(jié)點IP和指定端口的訪問,將請求轉(zhuǎn)發(fā)至后端pod;
3、 loadbalancer類型,該類型通常需要調(diào)用云廠商的API接口,在云平臺上創(chuàng)建負載均衡產(chǎn)品,并根據(jù)設(shè)置創(chuàng)建監(jiān)聽器。在K8s內(nèi)部,loadbalancer類型服務(wù)實際上還是和nodeport類型一樣將服務(wù)端口映射至每個節(jié)點的固定端口上。然后將節(jié)點設(shè)置為負載均衡的后端,監(jiān)聽器將客戶端請求轉(zhuǎn)發(fā)至后端節(jié)點上的服務(wù)映射端口,請求到達節(jié)點端口后,再轉(zhuǎn)發(fā)至后端pod。Loadbalancer類型的service彌補了nodeport類型有多個節(jié)點時客戶端需要訪問多個節(jié)點IP地址的不足,只要統(tǒng)一訪問LB的IP即可。同時使用LB類型的service對外提供服務(wù),K8s節(jié)點無需綁定公網(wǎng)IP,只需要給LB綁定公網(wǎng)IP即可,提升了節(jié)點安全性,也節(jié)約了公網(wǎng)IP資源。利用LB對后端節(jié)點的健康檢查功能,可實現(xiàn)服務(wù)高可用。避免某個K8s節(jié)點故障導(dǎo)致服務(wù)無法訪問。
通過對K8s集群service類型的了解,我們可以知道客戶想在集群外對service進行訪問,首先推薦使用的是LB類型的service。由于目前K8s集群產(chǎn)品的節(jié)點還不支持綁定公網(wǎng)IP,因此使用nodeport類型的service無法實現(xiàn)通過公網(wǎng)訪問,除非客戶使用專線連接或者IPSEC將自己的辦公網(wǎng)與云上網(wǎng)絡(luò)打通,才能訪問nodeport類型的service。而對于pod,只能在集群內(nèi)部使用其他pod或者集群節(jié)點進行訪問。同時K8s集群的clusterIP和pod設(shè)計為不允許集群外部訪問,也是出于提高安全性的考慮。如果將訪問限制打破,可能會導(dǎo)致安全問題發(fā)生。所以我們的建議客戶還是使用LB類型的service對外暴露服務(wù),或者從辦公網(wǎng)連接K8s集群的NAT主機,然后通過NAT主機可以連接至K8s節(jié)點,再訪問clusterIP類型的service,或者訪問后端pod。
客戶表示目前測試集群的clusterIP類型服務(wù)有上百個,如果都改造成LB類型的service就要創(chuàng)建上百個LB實例,綁定上百個公網(wǎng)IP,這顯然是不現(xiàn)實的,而都改造成Nodeport類型的service的工作量也十分巨大。同時如果通過NAT主機跳轉(zhuǎn)登錄至集群節(jié)點,就需要給研發(fā)同事提供NAT主機和集群節(jié)點的系統(tǒng)密碼,不利于運維管理,從操作便利性上也不如研發(fā)可以直接通過網(wǎng)絡(luò)訪問service和pod簡便。
雖然客戶的訪問方式違背了K8s集群的設(shè)計邏輯,顯得有些“非主流”,但是對于客戶的使用場景來說也是迫不得已的強需求。作為技術(shù)中臺的攻城獅,我們要盡最大努力幫助客戶解決技術(shù)問題!因此我們根據(jù)客戶的需求和場景架構(gòu),來規(guī)劃實現(xiàn)方案。
既然是網(wǎng)絡(luò)打通,首先要從客戶的辦公網(wǎng)和云上K8s集群網(wǎng)絡(luò)架構(gòu)分析。客戶辦公網(wǎng)有統(tǒng)一的公網(wǎng)出口設(shè)備,而云上K8s集群的網(wǎng)絡(luò)架構(gòu)如下,K8s集群master節(jié)點對用戶不可見,用戶創(chuàng)建K8s集群后,會在用戶選定的VPC網(wǎng)絡(luò)下創(chuàng)建三個子網(wǎng)。分別是用于K8s節(jié)點通訊的node子網(wǎng),用于部署NAT主機和LB類型serivce創(chuàng)建的負載均衡實例的NAT與LB子網(wǎng),以及用于pod通訊的pod子網(wǎng)。K8s集群的節(jié)點搭建在云主機上,node子網(wǎng)訪問公網(wǎng)地址的路由下一跳指向NAT主機,也就是說集群節(jié)點不能綁定公網(wǎng)IP,使用NAT主機作為統(tǒng)一的公網(wǎng)訪問出口,做SNAT,實現(xiàn)公網(wǎng)訪問。由于NAT主機只有SNAT功能,沒有DNAT功能,因此也就無法從集群外通過NAT主機訪問node節(jié)點。
關(guān)于pod子網(wǎng)的規(guī)劃目的,首先要介紹下pod在節(jié)點上的網(wǎng)絡(luò)架構(gòu)。如下圖所示:
在節(jié)點上,pod中的容器通過veth對與docker0設(shè)備連通,而docker0與節(jié)點的網(wǎng)卡之間通過自研CNI網(wǎng)絡(luò)插件連通。為了實現(xiàn)集群控制流量與數(shù)據(jù)流量的分離,提高網(wǎng)絡(luò)性能,集群在每個節(jié)點上單獨綁定彈性網(wǎng)卡,專門供pod通訊使用。創(chuàng)建pod時,會在彈性網(wǎng)卡上為Pod分配IP地址。每個彈性網(wǎng)卡最多可以分配21個IP,當(dāng)一張彈性網(wǎng)卡上的IP分配滿后,會再綁定一張新的網(wǎng)卡供后續(xù)新建的pod使用。彈性網(wǎng)卡所屬的子網(wǎng)就是pod子網(wǎng),基于這樣的架構(gòu),可以降低節(jié)點eth0主網(wǎng)卡的負載壓力,實現(xiàn)控制流量與數(shù)據(jù)流量分離,同時pod的IP在VPC網(wǎng)絡(luò)中有實際對應(yīng)的網(wǎng)絡(luò)接口和IP,可實現(xiàn)VPC網(wǎng)絡(luò)內(nèi)對pod地址的路由。
你需要了解的打通方式
了解完兩端的網(wǎng)絡(luò)架構(gòu)后我們來選擇打通方式。通常將云下網(wǎng)絡(luò)和云上網(wǎng)絡(luò)打通,有專線產(chǎn)品連接方式,或者用戶自建VPN連接方式。專線產(chǎn)品連接需要布設(shè)從客戶辦公網(wǎng)到云上機房的網(wǎng)絡(luò)專線,然后在客戶辦公網(wǎng)側(cè)的網(wǎng)絡(luò)出口設(shè)備和云上網(wǎng)絡(luò)側(cè)的bgw邊界網(wǎng)關(guān)配置到彼此對端的路由。如下圖所示:
基于現(xiàn)有專線產(chǎn)品BGW的功能限制,云上一側(cè)的路由只能指向K8s集群所在的VPC,無法指向具體的某個K8s節(jié)點。而想要訪問clusterIP類型service和pod,必須在集群內(nèi)的節(jié)點和pod訪問。因此訪問service和pod的路由下一跳,必須是某個集群節(jié)點。所以使用專線產(chǎn)品顯然是無法滿足需求的。
我們來看自建VPN方式,自建VPN在客戶辦公網(wǎng)和云上網(wǎng)絡(luò)各有一個有公網(wǎng)IP的端點設(shè)備,兩個設(shè)備之間建立加密通訊隧道,實際底層還是基于公網(wǎng)通訊。如果使用該方案,云上的端點我們可以選擇和集群節(jié)點在同一VPC的不同子網(wǎng)下的有公網(wǎng)IP的云主機。辦公網(wǎng)側(cè)對service和pod的訪問數(shù)據(jù)包通過VPN隧道發(fā)送至云主機后,可以通過配置云主機所在子網(wǎng)路由,將數(shù)據(jù)包路由至某個集群節(jié)點,然后在集群節(jié)點所在子網(wǎng)配置到客戶端的路由下一跳指向端點云主機,同時需要在pod子網(wǎng)也做相同的路由配置。至于VPN的實現(xiàn)方式,通過和客戶溝通,我們選取ipsec隧道方式。
確定了方案,我們需要在測試環(huán)境實施方案驗證可行性。由于我們沒有云下環(huán)境,因此選取和K8s集群不同地域的云主機代替客戶的辦公網(wǎng)端點設(shè)備。在華東上海地域創(chuàng)建云主機office-ipsec-sh模擬客戶辦公網(wǎng)客戶端,在華北北京地域的K8s集群K8s-BJTEST01所在VPC的NAT/LB子網(wǎng)創(chuàng)建一個有公網(wǎng)IP的云主機K8s-ipsec-bj,模擬客戶場景下的ipsec云上端點,與華東上海云主機office-ipsec-sh建立ipsec隧道。設(shè)置NAT/LB子網(wǎng)的路由表,添加到service網(wǎng)段的路由下一跳指向K8s集群節(jié)點K8s-node-vmlppp-bs9jq8pua,以下簡稱node A。由于pod子網(wǎng)和NAT/LB子網(wǎng)同屬于一個VPC,所以無需配置到pod網(wǎng)段的路由,訪問pod時會直接匹配local路由,轉(zhuǎn)發(fā)至對應(yīng)的彈性網(wǎng)卡上。為了實現(xiàn)數(shù)據(jù)包的返回,在node子網(wǎng)和pod子網(wǎng)分別配置到上海云主機office-ipsec-sh的路由,下一跳指向K8s-ipsec-bj。完整架構(gòu)如下圖所示:
既然確定了方案,我們就開始搭建環(huán)境了。首先在K8s集群的NAT/LB子網(wǎng)創(chuàng)建K8s-ipsec-bj云主機,并綁定公網(wǎng)IP。然后與上海云主機office-ipsec-sh建立ipsec隧道。關(guān)于ipsec部分的配置方法網(wǎng)絡(luò)上有很多文檔,在此不做詳細敘述,有興趣的童鞋可以參照文檔自己實踐下。隧道建立后,在兩端互ping對端的內(nèi)網(wǎng)IP,如果可以ping通的話,證明ipsec工作正常。按照規(guī)劃配置好NAT/LB子網(wǎng)和node子網(wǎng)以及pod子網(wǎng)的路由。我們在K8s集群的serivce中,選擇一個名為nginx的serivce,clusterIP為10.0.58.158,如圖所示:
該服務(wù)后端的pod是10.0.0.13,部署nginx默認頁面,并監(jiān)聽80端口。在上海云主機上測試ping service的IP 10.0.58.158,可以ping通,同時使用paping工具ping服務(wù)的80端口,也可以ping通!
使用curl http://10.0.58.158進行http請求,也可以成功!
再測試直接訪問后端pod,也沒有問題:)
正當(dāng)攻城獅心里美滋滋,以為一切都大功告成的時候,測試訪問另一個service的結(jié)果猶如一盆冷水潑來。我們接著選取了mysql這個service,測試訪問3306端口。該serivce的clusterIP是10.0.60.80,后端pod的IP是10.0.0.14。
在上海云主機直接ping service的clusterIP,沒有問題。但是paping 3306端口的時候,居然不通了!
然后我們測試直接訪問serivce的后端pod,詭異的是,后端pod無論是ping IP還是paping 3306端口,都是可以連通的!
腫么回事?
這是腫么回事?經(jīng)過攻城獅一番對比分析,發(fā)現(xiàn)兩個serivce唯一的不同是,可以連通nginx服務(wù)的后端pod 10.0.0.13就部署在客戶端請求轉(zhuǎn)發(fā)到的node A上。而不能連通的mysql服務(wù)的后端pod不在node A上,在另一個節(jié)點上。為了驗證問題原因是否就在于此,我們單獨修改NAT/LB子網(wǎng)路由,到mysql服務(wù)的下一跳指向后端pod所在的節(jié)點。然后再次測試。果然!現(xiàn)在可以訪問mysql服務(wù)的3306端口了!
此時此刻,攻城獅的心中有三個疑問:
(1)為什么請求轉(zhuǎn)發(fā)至service后端pod所在的節(jié)點時可以連通?
(2)為什么請求轉(zhuǎn)發(fā)至service后端pod不在的節(jié)點時不能連通?
(3)為什么不管轉(zhuǎn)發(fā)至哪個節(jié)點,service的IP都可以ping通?
深入分析,消除問號
為了消除我們心中的小問號,我們就要深入分析,了解導(dǎo)致問題的原因,然后再對癥下藥。既然要排查網(wǎng)絡(luò)問題,當(dāng)然還是要祭出經(jīng)典法寶——tcpdump抓包工具。為了把焦點集中,我們對測試環(huán)境的架構(gòu)進行了調(diào)整。上海到北京的ipsec部分維持現(xiàn)有架構(gòu)不變,我們對K8s集群節(jié)點進行擴容,新建一個沒有任何pod的空節(jié)點K8s-node-vmcrm9-bst9jq8pua,以下簡稱node B,該節(jié)點只做請求轉(zhuǎn)發(fā)。修改NAT/LB子網(wǎng)路由,訪問service地址的路由下一跳指向該節(jié)點。測試的service我們選取之前使用的nginx服務(wù)10.0.58.158和后端pod 10.0.0.13,如下圖所示:
當(dāng)需要測試請求轉(zhuǎn)發(fā)至pod所在節(jié)點的場景時,我們將service路由下一跳修改為K8s-node-A即可。
首先探究疑問1場景,我們在K8s-node-A上執(zhí)行命令抓取與上海云主機172.16.0.50的包,命令如下:
tcpdump -i any host 172.16.0.50 -w /tmp/dst-node-client.cap
各位童鞋是否還記得我們之前提到過,在托管K8s集群中,所有pod的數(shù)據(jù)流量均通過節(jié)點的彈性網(wǎng)卡收發(fā)?在K8s-node-A上pod使用的彈性網(wǎng)卡是eth2。我們首先在上海云主機上使用curl命令請求http://10.0.58.158,同時執(zhí)行命令抓取K8s-node-A的eth2上是否有pod 10.0.0.13的包收發(fā),命令如下:
tcpdump –i eth2 host 10.0.0.13
結(jié)果如下圖:
并沒有任何10.0.0.13的包從eth2收發(fā),但此時上海云主機上的curl操作是可以請求成功的,說明10.0.0.13必然給客戶端回包了,但是并沒有通過eth2回包。那么我們將抓包范圍擴大至全部接口,命令如下:
tcpdump -i any host 10.0.0.13
結(jié)果如下圖:
可以看到這次確實抓到了10.0.0.13和172.16.0.50交互的數(shù)據(jù)包,為了便于分析,我們使用命令tcpdump -i any host 10.0.0.13 -w /tmp/dst-node-pod.cap將包輸出為cap文件。
同時我們再執(zhí)行tcpdump -i any host 10.0.58.158,對service IP進行抓包。
可以看到172.16.0.50執(zhí)行curl請求時可以抓到數(shù)據(jù)包,且只有10.0.58.158與172.16.0.50交互的數(shù)據(jù)包,不執(zhí)行請求時沒有數(shù)據(jù)包。由于這一部分?jǐn)?shù)據(jù)包會包含在對172.16.0.50的抓包中,因此我們不再單獨分析。
將針對172.16.0.50和10.0.0.13的抓包文件取出,使用wireshark工具進行分析,首先分析對客戶端172.16.0.50的抓包,詳情如下圖所示:
可以發(fā)現(xiàn)客戶端172.16.0.50先給service IP 10.0.58.158發(fā)了一個包,然后又給pod IP 10.0.0.13發(fā)了一個包,兩個包的ID,內(nèi)容等完全一致。而最后回包時,pod 10.0.0.13給客戶端回了一個包,然后service IP 10.0.58.158也給客戶端回了一個ID和內(nèi)容完全相同的包。這是什么原因?qū)е碌哪兀?/strong>
通過之前的介紹,我們知道service將客戶端請求轉(zhuǎn)發(fā)至后端pod,在這個過程中客戶端請求的是service的IP,然后service會做DNAT(根據(jù)目的IP做NAT轉(zhuǎn)發(fā)),將請求轉(zhuǎn)發(fā)至后端的pod IP。雖然我們抓包看到的是客戶端發(fā)了兩次包,分別發(fā)給service和pod,實際上客戶端并沒有重新發(fā)包,而是由service完成了目的地址轉(zhuǎn)換。而pod回包時,也是將包回給service,然后再由service轉(zhuǎn)發(fā)給客戶端。因為是相同節(jié)點內(nèi)請求,這一過程應(yīng)該是在節(jié)點的內(nèi)部虛擬網(wǎng)絡(luò)中完成,所以我們在pod使用的eth2網(wǎng)卡上并沒有抓到和客戶端交互的任何數(shù)據(jù)包。再結(jié)合pod維度的抓包,我們可以看到針對client抓包時抓到的http get請求包在對pod的抓包中也能抓到,也驗證了我們的分析。
那么pod是通過哪個網(wǎng)絡(luò)接口進行收發(fā)包的呢?執(zhí)行命令netstat -rn查看node A上的網(wǎng)絡(luò)路由,我們有了如下發(fā)現(xiàn):
在節(jié)點內(nèi),所有訪問10.0.0.13的路由都指向了cni34f0b149874這個網(wǎng)絡(luò)接口。很顯然這個接口是CNI網(wǎng)絡(luò)插件創(chuàng)建的虛擬網(wǎng)絡(luò)設(shè)備。為了驗證pod所有的流量是否都通過該接口收發(fā),我們再次在客戶端請求service地址,在node A以客戶端維度和pod維度抓包,但是這次以pod維度抓包時,我們不再使用-i any參數(shù),而是替換為-i cni34f0b149874。抓包后分析對比,發(fā)現(xiàn)如我們所料,客戶端對pod的所有請求包都能在對cni34f0b149874的抓包中找到,同時對系統(tǒng)中除了cni34f0b149874之外的其他網(wǎng)絡(luò)接口抓包,均沒有抓到與客戶端交互的任何數(shù)據(jù)包。因此可以證明我們的推斷正確。
綜上所述,在客戶端請求轉(zhuǎn)發(fā)至pod所在節(jié)點時,數(shù)據(jù)通路如下圖所示:
接下來我們探究最為關(guān)心的問題2場景,修改NAT/LB子網(wǎng)路由到service的下一跳指向新建節(jié)點node B,如圖所示
這次我們需要在node B和node A上同時抓包。在客戶端還是使用curl方式請求service地址。在轉(zhuǎn)發(fā)節(jié)點node B上,我們先執(zhí)行命令tcpdump -i eth0 host 10.0.58.158抓取service維度的數(shù)據(jù)包,發(fā)現(xiàn)抓取到了客戶端到service的請求包,但是service沒有任何回包,如圖所示:
各位童鞋可能會有疑惑,為什么抓取的是10.0.58.158,但抓包中顯示的目的端是該節(jié)點名?實際上這與service的實現(xiàn)機制有關(guān)。在集群中創(chuàng)建service后,集群網(wǎng)絡(luò)組件會在各個節(jié)點上都選取一個隨機端口進行監(jiān)聽,然后在節(jié)點的iptables中配置轉(zhuǎn)發(fā)規(guī)則,凡是在節(jié)點內(nèi)請求service IP均轉(zhuǎn)發(fā)至該隨機端口,然后由集群網(wǎng)絡(luò)組件進行處理。所以在節(jié)點內(nèi)訪問service時,實際訪問的是節(jié)點上的某個端口。如果將抓包導(dǎo)出為cap文件,可以看到請求的目的IP仍然是10.0.58.158,如圖所示:
這也解釋了為什么clusterIP只能在集群內(nèi)的節(jié)點或者pod訪問,因為集群外的設(shè)備沒有K8s網(wǎng)絡(luò)組件創(chuàng)建的iptables規(guī)則,不能將請求service地址轉(zhuǎn)為請求節(jié)點的端口,即使數(shù)據(jù)包發(fā)送至集群,由于service的clusterIP在節(jié)點的網(wǎng)絡(luò)中實際是不存在的,因此會被丟棄。(奇怪的姿勢又增長了呢)
回到問題本身,在轉(zhuǎn)發(fā)節(jié)點上抓取service相關(guān)包,發(fā)現(xiàn)service沒有像轉(zhuǎn)發(fā)到pod所在節(jié)點時給客戶端回包。我們再執(zhí)行命令tcpdump -i any host 172.16.0.50 -w /tmp/fwd-node-client.cap以客戶端維度抓包,包內(nèi)容如下:
我們發(fā)現(xiàn)客戶端請求轉(zhuǎn)發(fā)節(jié)點node B上的service后,service同樣做了DNAT,將請求轉(zhuǎn)發(fā)到node A上的10.0.0.13。但是在轉(zhuǎn)發(fā)節(jié)點上沒有收到10.0.0.13回給客戶端的任何數(shù)據(jù)包,之后客戶端重傳了幾次請求包,均沒有回應(yīng)。
那么node A是否收到了客戶端的請求包呢?pod又有沒有給客戶端回包呢?我們移步node A進行抓包。在node B上的抓包我們可以獲悉node A上應(yīng)該只有客戶端IP和pod IP的交互,因此我們就從這兩個維度抓包。根據(jù)之前抓包的分析結(jié)果,數(shù)據(jù)包進入節(jié)點內(nèi)之后,應(yīng)該通過虛擬設(shè)備cni34f0b149874與pod交互。而node B節(jié)點訪問pod應(yīng)該從node A的彈性網(wǎng)卡eth2進入節(jié)點,而不是eth0,為了驗證,首先執(zhí)行命令tcpdump -i eth0 host 172.16.0.50和tcpdump -i eth0 host 10.0.0.13,沒有抓到任何數(shù)據(jù)包。
說明數(shù)據(jù)包沒有走eth0。再分別執(zhí)行tcpdump -i eth2 host 172.16.0.50 -w /tmp/dst-node-client-eth2.cap和tcpdump -i cni34f0b149874 host 172.16.0.50 -w /tmp/dst-node-client-cni.cap抓取客戶端維度數(shù)據(jù)包,對比發(fā)現(xiàn)數(shù)據(jù)包內(nèi)容完全一致,說明數(shù)據(jù)包從eth2進入Node A后,通過系統(tǒng)內(nèi)路由轉(zhuǎn)發(fā)至cni34f0b149874。數(shù)據(jù)包內(nèi)容如下:
可以看到客戶端給pod發(fā)包后,pod給客戶端回了包。執(zhí)行tcpdump -i eth2 host 10.0.0.13 -w /tmp/dst-node-pod-eth2.cap和tcpdump -i host 10.0.0.13 -w /tmp/dst-node-pod-cni.cap抓取pod維度數(shù)據(jù)包,對比發(fā)現(xiàn)數(shù)據(jù)包內(nèi)容完全一致,說明pod給客戶端的回包通過cni34f0b149874發(fā)出,然后從eth2網(wǎng)卡離開node A節(jié)點。數(shù)據(jù)包內(nèi)容也可以看到pod給客戶端返回了包,但沒有收到客戶端對于返回包的回應(yīng),觸發(fā)了重傳。
那么既然pod的回包已經(jīng)發(fā)出,為什么node B上沒有收到回包,客戶端也沒有收到回包呢?查看eth2網(wǎng)卡所屬的pod子網(wǎng)路由表,我們恍然大悟!
由于pod給客戶端回包是從node A的eth2網(wǎng)卡發(fā)出的,所以雖然按照正常DNAT規(guī)則,數(shù)據(jù)包應(yīng)該發(fā)回給node B上的service端口,但是受eth2子網(wǎng)路由表影響,數(shù)據(jù)包直接被“劫持”到了K8s-ipsec-bj這個主機上。而數(shù)據(jù)包到了這個主機上之后,由于沒有經(jīng)過service的轉(zhuǎn)換,回包的源地址是pod地址10.0.0.13,目的地址是172.16.0.50,這個數(shù)據(jù)包回復(fù)的是源地址172.16.0.50,目的地址10.0.58.158這個數(shù)據(jù)包。相當(dāng)于請求包的目的地址和回復(fù)包的源地址不一致,對于K8s-ipsec-bj來說,只看到了10.0.0.13給172.16.0.50的reply包,但是沒有收到過172.16.0.50給10.0.0.13的request包,云平臺虛擬網(wǎng)絡(luò)的機制是遇到只有reply包,沒有request包的情況會將request包丟棄,避免利用地址欺騙發(fā)起網(wǎng)絡(luò)攻擊。所以客戶端不會收到10.0.0.13的回包,也就無法完成對service的請求。在這個場景下,數(shù)據(jù)包的通路如下圖所示:
此時客戶端可以成功請求pod的原因也一目了然 ,請求pod的數(shù)據(jù)通路如下:
請求包和返回包的路徑一致,都經(jīng)過K8s-ipsec-bj節(jié)點且源目IP沒有發(fā)生改變,因此pod可以連通。
看到這里,機智的童鞋可能已經(jīng)想到,那修改eth2所屬的pod子網(wǎng)路由,讓去往172.16.0.50的數(shù)據(jù)包下一跳不發(fā)送到K8s-ipsec-bj,而是返回給K8s-node-B,不就可以讓回包沿著來路原路返回,不會被丟棄嗎?是的,經(jīng)過我們的測試驗證,這樣確實可以使客戶端成功請求服務(wù)。但是別忘了,用戶還有一個需求是客戶端可以直接訪問后端pod,如果pod回包返回給node B,那么客戶端請求pod時的數(shù)據(jù)通路是怎樣的呢?
如圖所示,可以看到客戶端對Pod的請求到達K8s-ipsec-bj后,由于是同一vpc內(nèi)的地址訪問,所以遵循local路由規(guī)則直接轉(zhuǎn)發(fā)到node A eth2網(wǎng)卡,而pod給客戶端回包時,受eth2網(wǎng)卡路由控制,發(fā)送到了node B上。node B之前沒有收到過客戶端對pod的request包,同樣會遇到只有reply包沒有request包的問題,所以回包被丟棄,客戶端無法請求pod。
至此,我們搞清楚了為什么客戶端請求轉(zhuǎn)發(fā)至service后端pod不在的節(jié)點上時無法成功訪問service的原因。那么為什么在此時雖然請求service的端口失敗,但是可以ping通service地址呢?攻城獅推斷,既然service對后端的pod起到DNAT和負載均衡的作用,那么當(dāng)客戶端ping service地址時,ICMP包應(yīng)該是由service直接應(yīng)答客戶端的,即service代替后端pod答復(fù)客戶端的ping包。為了驗證我們的推斷是否正確,我們在集群中新建一個沒有關(guān)聯(lián)任何后端的空服務(wù),如圖所示:
然后在客戶端ping 10.0.62.200,結(jié)果如下:
果不其然,即使service后端沒有任何pod,也可以ping通,因此證明ICMP包均為service代答,不存在實際請求后端pod時的問題,因此可以ping通。
既然費盡周折找到了訪問失敗的原因,接下來我們就要想辦法解決這個問題。事實上只要想辦法讓pod跨節(jié)點給客戶端回包時隱藏自己的IP,對外顯示的是service的IP,就可以避免包被丟棄。原理上類似于SNAT(基于源IP的地址轉(zhuǎn)換)。可以類比為沒有公網(wǎng)IP的局域網(wǎng)設(shè)備有自己的內(nèi)網(wǎng)IP,當(dāng)訪問公網(wǎng)時需要通過統(tǒng)一的公網(wǎng)出口,而此時外部看到的客戶端IP是公網(wǎng)出口的IP,并不是局域網(wǎng)設(shè)備的內(nèi)網(wǎng)IP。實現(xiàn)SNAT,我們首先會想到通過節(jié)點操作系統(tǒng)上的iptables規(guī)則。我們在pod所在節(jié)點node A上執(zhí)行iptables-save命令,查看系統(tǒng)已有的iptables規(guī)則都有哪些。
敲黑板,注意啦
可以看到系統(tǒng)創(chuàng)建了近千條iptables規(guī)則,大多數(shù)與K8s有關(guān)。我們重點關(guān)注上圖中的nat類型規(guī)則,發(fā)現(xiàn)了有如下幾條引起了我們的注意:
首先看紅框部分規(guī)則
-A KUBE-SERVICES -m comment --comment "Kubernetes service cluster ip + port for masquerade purpose" -m set --match-set KUBE-CLUSTER-IP src,dst -j KUBE-MARK-MASQ
該規(guī)則表示如果訪問的源地址或者目的地址是cluster ip +端口,出于masquerade目的,將跳轉(zhuǎn)至KUBE-MARK-MASQ鏈,masquerade也就是地址偽裝的意思!在NAT轉(zhuǎn)換中會用到地址偽裝。
接下來看藍框部分規(guī)則
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
該規(guī)則表示對于數(shù)據(jù)包打上需要做地址偽裝的標(biāo)記0x4000/0x4000。
最后看黃框部分規(guī)則
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
該規(guī)則表示對于標(biāo)記為0x4000/0x4000需要做SNAT的數(shù)據(jù)包,將跳轉(zhuǎn)至MASQUERADE鏈進行地址偽裝。
這三條規(guī)則所做的操作貌似正是我們需要iptables幫我們實現(xiàn)的,但是從之前的測試來看顯然這三條規(guī)則并沒有生效。這是為什么呢?是否是K8s的網(wǎng)絡(luò)組件里有某個參數(shù)控制著是否會對訪問clusterIP時的數(shù)據(jù)包進行SNAT?
這就要從負責(zé)service與pod之間網(wǎng)絡(luò)代理轉(zhuǎn)發(fā)的組件——kube-proxy的工作模式和參數(shù)進行研究了。我們已經(jīng)知道service會對后端pod進行負載均衡和代理轉(zhuǎn)發(fā),要想實現(xiàn)該功能,依賴的是kube-proxy組件,從名稱上可以看出這是一個代理性質(zhì)的網(wǎng)絡(luò)組件。它以pod形式運行在每個K8s節(jié)點上,當(dāng)以service的clusterIP+端口方式訪問時,通過iptables規(guī)則將請求轉(zhuǎn)發(fā)至節(jié)點上對應(yīng)的隨機端口,之后請求由kube-proxy組件接手處理,通過kube-proxy內(nèi)部的路由和調(diào)度算法,轉(zhuǎn)發(fā)至相應(yīng)的后端Pod。最初,kube-proxy的工作模式是userspace(用戶空間代理)模式,kube-proxy進程在這一時期是一個真實的TCP/UDP代理,類似HA Proxy。由于該模式在1.2版本K8s開始已被iptables模式取代,在此不做贅述,有興趣的童鞋可以自行研究下。
1.2版本引入的iptables模式作為kube-proxy的默認模式,kube-proxy本身不再起到代理的作用,而是通過創(chuàng)建和維護對應(yīng)的iptables規(guī)則實現(xiàn)service到pod的流量轉(zhuǎn)發(fā)。但是依賴iptables規(guī)則實現(xiàn)代理存在無法避免的缺陷,在集群中的service和pod大量增加后,iptables規(guī)則的數(shù)量也會急劇增加,會導(dǎo)致轉(zhuǎn)發(fā)性能顯著下降,極端情況下甚至?xí)霈F(xiàn)規(guī)則丟失的情況。
為了解決iptables模式的弊端,K8s在1.8版本開始引入IPVS(IP Virtual Server)模式。IPVS模式專門用于高性能負載均衡,使用更高效的hash表數(shù)據(jù)結(jié)構(gòu),為大型集群提供了更好的擴展性和性能。比iptables模式支持更復(fù)雜的負載均衡調(diào)度算法等。托管集群的kube-proxy正是使用了IPVS模式。
但是IPVS模式無法提供包過濾,地址偽裝和SNAT等功能,所以在需要使用這些功能的場景下,IPVS還是要搭配iptables規(guī)則使用。等等,地址偽裝和SNAT,這不正是我們之前在iptables規(guī)則中看到過的?這也就是說,iptables在不進行地址偽裝和SNAT時,不會遵循相應(yīng)的iptables規(guī)則,而一旦設(shè)置了某個參數(shù)開啟地址偽裝和SNAT,之前看到的iptables規(guī)則就會生效!于是我們到kubernetes官網(wǎng)查找kube-proxy的工作參數(shù),有了令人激動的發(fā)現(xiàn):
好一個驀然回首!攻城獅的第六感告訴我們,--masquerade-all參數(shù)就是解決我們問題的關(guān)鍵!
第六部分:真·方法比困難多
我們決定測試開啟下--masquerade-all這個參數(shù)。kube-proxy在集群中的每個節(jié)點上以pod形式運行,而kube-proxy的參數(shù)配置都以configmap形式掛載到pod上。我們執(zhí)行kubectl get cm -n kube-system查看kube-proxy的configmap,如圖所示:
紅框里的就是kube-proxy的配置configmap,執(zhí)行kubectl edit cm kube-proxy-config-khc289cbhd -n kube-system編輯這個configmap,如圖所示
找到了masqueradeALL參數(shù),默認是false,我們修改為true,然后保存修改。
要想使配置生效,需要逐一刪除當(dāng)前的kube-proxy pod,daemonset會自動重建pod,重建的pod會掛載修改過的configmap,masqueradeALL功能也就開啟了。如圖所示:
期待地搓手手
接下來激動人心的時刻到來了,我們將訪問service的路由指向node B,然后在上??蛻舳松蠄?zhí)行paping 10.0.58.158 -p 80觀察測試結(jié)果(期待地搓手手):
此情此景,不禁讓攻城獅流下了欣喜的淚水……
再測試下curl http://10.0.58.158 同樣可以成功!奧力給~
再測試下直接訪問后端Pod,以及請求轉(zhuǎn)發(fā)至pod所在節(jié)點,都沒有問題。至此客戶的需求終于卍解,長舒一口氣!
雖然問題已經(jīng)解決,但是我們的探究還沒有結(jié)束。開啟masqueradeALL參數(shù)后,service是如何對數(shù)據(jù)包做SNAT,避免了之前的丟包問題呢?還是通過抓包進行分析。
首先分析轉(zhuǎn)發(fā)至pod不在的節(jié)點時的場景,客戶端請求服務(wù)時,在pod所在節(jié)點對客戶端IP進行抓包,沒有抓到任何包。
說明開啟參數(shù)后,到后端pod的請求不再是以客戶端IP發(fā)起的。
在轉(zhuǎn)發(fā)節(jié)點對pod IP進行抓包可以抓到轉(zhuǎn)發(fā)節(jié)點的service端口與pod之間的交互包。
說明pod沒有直接回包給客戶端172.16.0.50。這樣看來,相當(dāng)于客戶端和pod互相不知道彼此的存在,所有交互都通過service來轉(zhuǎn)發(fā)。
再在轉(zhuǎn)發(fā)節(jié)點對客戶端進行抓包,包內(nèi)容如下:
同時在pod所在節(jié)點對pod進行抓包,包內(nèi)容如下:
可以看到轉(zhuǎn)發(fā)節(jié)點收到序號708的curl請求包后,在pod所在節(jié)點收到了序號相同的請求包,只不過源目IP從172.16.0.50/10.0.58.158轉(zhuǎn)換為了10.0.32.23/10.0.0.13。這里10.0.32.23是轉(zhuǎn)發(fā)節(jié)點的內(nèi)網(wǎng)IP,實際上就是節(jié)點上service對應(yīng)的隨機端口,所以可以理解為源目IP轉(zhuǎn)換為了10.0.58.158/10.0.0.13。而回包時的流程相同,pod發(fā)出序號17178的包,轉(zhuǎn)發(fā)節(jié)點將相同序號的包發(fā)給客戶端,源目IP從10.0.0.13/10.0.58.158轉(zhuǎn)換為了10.0.58.158/172.16.0.50
根據(jù)以上現(xiàn)象可以得知,service對客戶端和后端都做了SNAT,可以理解為關(guān)閉了透傳客戶端源IP的負載均衡,即客戶端和后端都不知道彼此的存在,只知道service的地址。該場景下的數(shù)據(jù)通路如下圖:
對Pod的請求不涉及SNAT轉(zhuǎn)換,與masqueradeALL參數(shù)不開啟時是一樣的,因此我們不再做分析。
當(dāng)客戶端請求轉(zhuǎn)發(fā)至pod所在節(jié)點時,service依然會進行SNAT轉(zhuǎn)換,只不過這一過程均在節(jié)點內(nèi)部完成。通過之前的分析我們也已經(jīng)了解,客戶端請求轉(zhuǎn)發(fā)至pod所在節(jié)點時,是否進行SNAT對訪問結(jié)果沒有影響。
關(guān)于如何解析一次客戶需求引發(fā)的K8s網(wǎng)絡(luò)探究就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。