Kubernetes源碼探疑:PodIP泄露排查及解決-創(chuàng)新互聯(lián)

UK8S是UCloud推出的Kubernetes容器云產(chǎn)品,完全兼容原生API,為用戶提供一站式云上Kubernetes服務(wù)。我們團(tuán)隊(duì)自研了CNI(Container Network Interface)網(wǎng)絡(luò)插件,深度集成VPC,使UK8S容器應(yīng)用擁有與云主機(jī)間等同的網(wǎng)絡(luò)性能(目前高可達(dá)10Gb/s, 100萬(wàn)pps),并打通容器和物理云/托管云的網(wǎng)絡(luò)。過(guò)程中,我們解決了開(kāi)源kubelet創(chuàng)建多余Sandbox容器導(dǎo)致Pod IP莫名消失的問(wèn)題,確保CNI插件正常運(yùn)行,并準(zhǔn)備將修復(fù)后的kubelet源碼提交給社區(qū)。

創(chuàng)新互聯(lián)的客戶來(lái)自各行各業(yè),為了共同目標(biāo),我們?cè)诠ぷ魃厦芮信浜希瑥膭?chuàng)業(yè)型小企業(yè)到企事業(yè)單位,感謝他們對(duì)我們的要求,感謝他們從不同領(lǐng)域給我們帶來(lái)的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來(lái)驚喜。專業(yè)領(lǐng)域包括做網(wǎng)站、成都做網(wǎng)站、電商網(wǎng)站開(kāi)發(fā)、微信營(yíng)銷、系統(tǒng)平臺(tái)開(kāi)發(fā)。

深度集成VPC的網(wǎng)絡(luò)方案

按照我們的設(shè)想,開(kāi)發(fā)者可以在UK8S上部署、管理、擴(kuò)展容器化應(yīng)用,無(wú)需關(guān)心Kubernetes集群自身的搭建及維護(hù)等運(yùn)維類工作。UK8S完全兼容原生的Kubernetes API, 以UCloud 公有云資源為基礎(chǔ), 通過(guò)自研的插件整合打通了ULB、UDisk、EIP等公有云網(wǎng)絡(luò)和存儲(chǔ)產(chǎn)品,為用戶提供一站式云上Kubernetes服務(wù)。

其中VPC既保障網(wǎng)絡(luò)隔離,又提供靈活的IP地址定義等,是用戶對(duì)網(wǎng)絡(luò)的必備需求之一。UK8S研發(fā)團(tuán)隊(duì)經(jīng)過(guò)考察后認(rèn)為,UCloud基礎(chǔ)網(wǎng)絡(luò)平臺(tái)具有原生、強(qiáng)大的底層網(wǎng)絡(luò)控制能力,令我們能拋開(kāi)Overlay方案,把VPC的能力上移到容器這一層,通過(guò)VPC的能力去實(shí)現(xiàn)控制和轉(zhuǎn)發(fā)。 UK8S每創(chuàng)建一個(gè)Pod都為其申請(qǐng)一個(gè)VPC IP并通過(guò)VethPair配置到Pod上,再配置策略路由。 原理如下圖所示。

此方案具有以下優(yōu)勢(shì):

無(wú)Overlay,網(wǎng)絡(luò)性能高。50臺(tái)Node下的測(cè)試數(shù)據(jù)表明,容器與容器之間的網(wǎng)絡(luò)性能,相對(duì)于云主機(jī)與云主機(jī)之間,只有輕微差異(小包場(chǎng)景下,pps 會(huì)有 3~5% 損耗),而且Pod網(wǎng)絡(luò)性能各項(xiàng)指標(biāo)(吞吐量,包量,延遲等)不會(huì)隨著節(jié)點(diǎn)規(guī)模增大而削減。而Flannel UDP,VXLan模式和Calico IPIP的模式存在明顯的性能消耗。
Pod能直通公有云和物理云。對(duì)于使用公有云和物理云的用戶而言,業(yè)務(wù)上K8S少了一層障礙,多了一份便利。而Flannel的host gw模式下,容器無(wú)法訪問(wèn)公有云和物理云主機(jī)。
而CNI的工作流程如下所示。

創(chuàng)建Pod網(wǎng)絡(luò)過(guò)程:

刪除Pod網(wǎng)絡(luò)過(guò)程:

Pod IP 消失問(wèn)題的排查與解決

為了測(cè)試CNI插件的穩(wěn)定性,測(cè)試同學(xué)在UK8S上部署了一個(gè)CronJob,每分鐘運(yùn)行一個(gè)Job任務(wù),一天要運(yùn)行1440個(gè)任務(wù)。該CronJob定義如下:

apiVersion: batch/v1beta1 kind: CronJob metadata: name: hello spec: schedule: "/1 *" jobTemplate: spec: template: spec: containers: - name: hello image: busybox args: - /bin/sh - -c - date; echo Hello from the Kubernetes cluster restartPolicy: OnFailure

每運(yùn)行一次Job都要?jiǎng)?chuàng)建一個(gè)Pod, 每創(chuàng)建一個(gè)Pod,CNI插件需要申請(qǐng)一次VPC IP,當(dāng)Pod被銷毀時(shí),CNI插件需要釋放該VPC IP。 因此理論上,通過(guò)該CronJob每天需要進(jìn)行1440次申請(qǐng)VPC IP和釋放VPC IP操作。

然而,經(jīng)過(guò)數(shù)天的測(cè)試統(tǒng)計(jì),發(fā)現(xiàn)通過(guò)該CronJob,集群每天申請(qǐng)IP次數(shù)高達(dá)2500以上, 而釋放的的IP次數(shù)也達(dá)到了1800。申請(qǐng)和釋放次數(shù)都超過(guò)了1440,而且申請(qǐng)次數(shù)超過(guò)了釋放次數(shù),意味著,部分分配給Pod的VPC IP被無(wú)效占用而消失了。

CNI:待刪除的IP去哪兒了?

仔細(xì)分析CNI插件的運(yùn)行日志,很快發(fā)現(xiàn),CNI在執(zhí)行拆除SandBox網(wǎng)絡(luò)動(dòng)作(CNI_COMMAND=DEL)中,存在不少無(wú)法找到Pod IP的情況。由于UK8S 自研的CNI查找Pod IP依賴正確的Pod網(wǎng)絡(luò)名稱空間路徑(格式:/proc/10001/net/ns),而kubelet傳給CNI的NETNS環(huán)境變量參數(shù)為空字符串,因此,CNI無(wú)法獲取待釋放的VPC IP,這是造成IP泄露的直接原因,如下圖所示。

問(wèn)題轉(zhuǎn)移到kubelet, 為什么kubelet會(huì)傳入一個(gè)空的CNI_NETNS環(huán)境變量參數(shù)給CNI插件?

隨后跟蹤kubelet的運(yùn)行日志,發(fā)現(xiàn)不少Job Pod創(chuàng)建和銷毀的時(shí)候,生成了一個(gè)額外的Sandbox容器。Sandbox容器是k8s pod中的Infra容器,它是Pod中第一個(gè)創(chuàng)建出來(lái)的容器,用于創(chuàng)建Pod的網(wǎng)絡(luò)名稱空間和初始化Pod網(wǎng)絡(luò),例如調(diào)用CNI分配Pod IP,下發(fā)策略路由等。它執(zhí)行一個(gè)名為pause的進(jìn)程,這個(gè)進(jìn)程絕大部分時(shí)間處于Sleep狀態(tài),對(duì)系統(tǒng)資源消耗極低。奇怪的是,當(dāng)任務(wù)容器busybox運(yùn)行結(jié)束后,kubelet為Pod又創(chuàng)建了一個(gè)新的Sandbox容器,創(chuàng)建過(guò)程中自然又進(jìn)行了一次CNI ADD調(diào)用,再次申請(qǐng)了一次VPC IP。

回到UK8S CNI,我們?cè)俅畏治鲋噩F(xiàn)案例日志。這一次有了更進(jìn)一步的發(fā)現(xiàn),所有kubelet傳遞給NETNS參數(shù)為空字符串的情形都發(fā)生在kubelet試圖銷毀Pod中第二個(gè)Sandbox的過(guò)程中。反之,kubelet試圖銷毀第二個(gè)Sandbox時(shí),給CNI傳入的NETNS參數(shù)也全部為空字符串。

到這里,思路似乎清晰了不少,所有泄露的VPC IP都是來(lái)自第二個(gè)Sandbox容器。因此,我們需要查清楚兩個(gè)問(wèn)題:

  1. 為什么會(huì)出現(xiàn)第二個(gè)Sandbox容器?

  2. 為什么kubelet在銷毀第二個(gè)Sandbox容器時(shí),給CNI傳入了不正確的NETNS參數(shù)?

第二個(gè)Sandbox:我為何而生?

在了解的第二個(gè)Sandbox的前世今生之前,需要先交待一下kubelet運(yùn)行的基本原理和流程。

kubelet是kubernetes集群中Node節(jié)點(diǎn)的工作進(jìn)程。當(dāng)一個(gè)Pod被kube-sheduler成功調(diào)度到Node節(jié)點(diǎn)上后, kubelet負(fù)責(zé)將這個(gè)Pod創(chuàng)建出來(lái),并把它所定義的各個(gè)容器啟動(dòng)起來(lái)。kubelet也是按照控制器模式工作的,它的工作核心是一個(gè)控制循環(huán),源碼中稱之為syncLoop,這個(gè)循環(huán)關(guān)注并處理以下事件:

Pod更新事件,源自API Server;
Pod生命周期(PLEG)變化, 源自Pod本身容器狀態(tài)變化, 例如容器的創(chuàng)建,開(kāi)始運(yùn)行,和結(jié)束運(yùn)行;
kubelet本身設(shè)置的周期同步(Sync)任務(wù);
Pod存活探測(cè)(LivenessProbe)失敗事件;
定時(shí)的清理事件(HouseKeeping)。
在上文描述的CronJob任務(wù)中, 每次運(yùn)行Job任務(wù)都會(huì)創(chuàng)建一個(gè)Pod。這個(gè)Pod的生命周期中,理想情況下,需要經(jīng)歷以下重要事件:

  1. Pod被成功調(diào)度到某個(gè)工作節(jié)點(diǎn),節(jié)點(diǎn)上的Kubelet通過(guò)Watch APIServer感知到創(chuàng)建Pod事件,開(kāi)始創(chuàng)建Pod流程;

  2. kubelet為Pod創(chuàng)建Sandbox容器,用于創(chuàng)建Pod網(wǎng)絡(luò)名稱空間和調(diào)用CNI插件初始化Pod網(wǎng)絡(luò),Sandbox容器啟動(dòng)后,會(huì)觸發(fā)第一次kubelet PLEG(Pod Life Event Generator)事件。

  3. 主容器創(chuàng)建并啟動(dòng),觸發(fā)第二次PLEG事件。

  4. 主容器date命令運(yùn)行結(jié)束,容器終止,觸發(fā)第三次PLEG事件。

  5. kubelet殺死Pod中殘余的Sandbox容器。

  6. Sandbox容器被殺死,觸發(fā)第四次PLEG事件。

其中3和4由于時(shí)間間隔短暫,可能被歸并到同一次PLEG事件(kubelet每隔1s進(jìn)行一次PLEG事件更新)。

然而,在我們觀察到的所有VPC IP泄露的情況中,過(guò)程6之后“意外地”創(chuàng)建了Pod的第二個(gè)Sandbox容器,如下圖右下角所示。在我們對(duì)Kubernetes的認(rèn)知中,這不應(yīng)該發(fā)生。

對(duì)kubelet源碼(1.13.1)抽絲剝繭

前文提到,syncLoop循環(huán)會(huì)監(jiān)聽(tīng)PLEG事件變化并處理之。而PLEG事件,則來(lái)源kubelet內(nèi)部的一個(gè)pleg relist定時(shí)任務(wù)。kubelet每隔一秒鐘執(zhí)行一次relist操作,及時(shí)獲取容器的創(chuàng)建,啟動(dòng),容器,刪除事件。

relist的主要責(zé)任是通過(guò)CRI來(lái)獲取Pod中所有容器的實(shí)時(shí)狀態(tài),這里的容器被區(qū)分成兩大類:Sandbox容器和非Sandbox容器,kubelet通過(guò)給容器打不同的label來(lái)識(shí)別之。CRI是一個(gè)統(tǒng)一的容器操作gRPC接口,kubelet對(duì)容器的操作,都要通過(guò)CRI請(qǐng)求來(lái)完成,而Docker,Rkt等容器項(xiàng)目則負(fù)責(zé)實(shí)現(xiàn)各自的CRI實(shí)現(xiàn),Docker的實(shí)現(xiàn)即為dockershim,dockershim負(fù)責(zé)將收到的CRI請(qǐng)求提取出來(lái),翻譯成Docker API發(fā)給Docker Daemon。

relist通過(guò)CRI請(qǐng)求更新到Pod中Sandbox容器和非Sandbox容器最新?tīng)顟B(tài),然后將狀態(tài)信息寫(xiě)入kubelet的緩存podCache中,如果有容器狀態(tài)發(fā)生變化,則通過(guò)pleg channel通知到syncLoop循環(huán)。對(duì)于單個(gè)pod,podCache分配了兩個(gè)數(shù)組,分別用于保存Sandbox容器和非Sandbox容器的最新?tīng)顟B(tài)。

syncLoop收到pleg channel傳來(lái)事件后,進(jìn)入相應(yīng)的sync同步處理流程。對(duì)于PLEG事件來(lái)說(shuō),對(duì)應(yīng)的處理函數(shù)是HandlePodSyncs。這個(gè)函數(shù)開(kāi)啟一個(gè)新的pod worker goroutine,獲取pod最新的podCache信息,然后進(jìn)入真正的同步操作:syncPod函數(shù)。

syncPod將podCache中的pod最新?tīng)顟B(tài)信息(podStatus)轉(zhuǎn)化成Kubernetes API PodStatus結(jié)構(gòu)。這里值得一提的是,syncPod會(huì)通過(guò)podCache里各個(gè)容器的狀態(tài),來(lái)計(jì)算出Pod的狀態(tài)(getPhase函數(shù)),比如Running,F(xiàn)ailed或者Completed。然后進(jìn)入Pod容器運(yùn)行時(shí)同步操作:SyncPod函數(shù),即將當(dāng)前的各個(gè)容器狀態(tài)與Pod API定義的SPEC期望狀態(tài)做同步。下面源碼流程圖可以總結(jié)上述流程。

SyncPod:我做錯(cuò)了什么?

SyncPod首先計(jì)算Pod中所有容器的當(dāng)前狀態(tài)與該P(yáng)od API期望狀態(tài)做對(duì)比同步。這一對(duì)比同步分為兩個(gè)部分:

檢查podCache中的Sandbox容器的狀態(tài)是否滿足此條件:Pod中有且只有一個(gè)Sandbox容器,并且該容器處于運(yùn)行狀態(tài),擁有IP。如不滿足,則認(rèn)為該P(yáng)od需要重建Sandbox容器。如果需要重建Sandbox容器,Pod內(nèi)所有容器都需要銷毀并重建。
檢查podCache中非Sandbox容器的運(yùn)行狀態(tài),保證這些容器處于Pod API Spec期望狀態(tài)。例如,如果發(fā)現(xiàn)有容器主進(jìn)程退出且返回碼不為0,則根據(jù)Pod API Spec中的RestartPolicy來(lái)決定是否重建該容器。
回顧前面提到的關(guān)鍵線索:所有的VPC IP泄露事件,都源于一個(gè)意料之外的Sandbox容器,被泄露的IP即為此Sandbox容器的IP。剛才提到,SyncPod函數(shù)中會(huì)對(duì)Pod是否需要重建Sandbox容器進(jìn)行判定,這個(gè)意外的第二個(gè)Sandbox容器是否和這次判定有關(guān)呢? 憑kubelet的運(yùn)行日志無(wú)法證實(shí)該猜測(cè),必須修改源碼增加日志輸出。重新編譯kubelet后,發(fā)現(xiàn)第二個(gè)Sandbox容器確實(shí)來(lái)自SyncPod函數(shù)中的判定結(jié)果。進(jìn)一步確認(rèn)的是,該SyncPod調(diào)用是由第一個(gè)Sandbox容器被kubelet所殺而導(dǎo)致的PLEG觸發(fā)的。

那為什么SyncPod在第一個(gè)Sandbox容器被銷毀后認(rèn)為Pod需要重建Sandbox容器呢?進(jìn)入判定函數(shù)podSandboxChanged仔細(xì)分析。

podSandboxChanged獲取了podCache中Sandbox容器結(jié)構(gòu)體實(shí)例,發(fā)現(xiàn)第一個(gè)Sandbox已經(jīng)被銷毀,處于NOT READY狀態(tài),于是認(rèn)為pod中已無(wú)可用的Sandbox容器,需要重建之,源碼如下圖所示。

注意本文前面我們定位的CronJob yaml配置, Job模板里的restartPolicy被設(shè)置成了OnFailure。SyncPod完成Sandbox容器狀態(tài)檢查判定后,認(rèn)為該P(yáng)od需要重建Sandbox容器,再次檢查Pod的restartPolicy為OnFailure后,決定重建Sandbox容器,對(duì)應(yīng)源碼如下。

可以看出kubelet在第一個(gè)Sandbox容器死亡后觸發(fā)的SyncPod操作中,只是簡(jiǎn)單地發(fā)現(xiàn)唯一的Sandbox容器處于NOT READY狀態(tài),便認(rèn)為Pod需要重建Sandbox,忽視了Job的主容器已經(jīng)成功結(jié)束的事實(shí)。

事實(shí)上,在前面syncPod函數(shù)中通過(guò)podCache計(jì)算API PodStatus Phase的過(guò)程中,kubelet已經(jīng)知道該P(yáng)od處于Completed狀態(tài)并存入apiPodStatus變量中作為參數(shù)傳遞給SyncPod函數(shù)。如下圖所示。

Job已經(jīng)進(jìn)入Completed狀態(tài),此時(shí)不應(yīng)該重建Sandbox容器。而SyncPod函數(shù)在判定Sandbox是否需要重建時(shí), 并沒(méi)有參考調(diào)用者syncPod傳入的apiPodStatus參數(shù),甚至這個(gè)參數(shù)是被忽視的。

第二個(gè)Sandbox容器的來(lái)源已經(jīng)水落石出,解決辦法也非常簡(jiǎn)單,即kubelet不為已經(jīng)Completed的Pod創(chuàng)建Sandbox,具體代碼如下所示。

重新編譯kubelet并更新后,VPC IP泄露的問(wèn)題得到解決。

下圖可以總結(jié)上面描述的第二個(gè)Sandbox容器誕生的原因。

事情離真相大白還有一段距離。還有一個(gè)問(wèn)題需要回答:

為什么kubelet在刪除第二個(gè)Sandbox容器的時(shí)候, 調(diào)用CNI拆除容器網(wǎng)絡(luò)時(shí),傳入了不正確的NETNS環(huán)境變量參數(shù)?

失去的NETNS

還記得前面介紹kubelet工作核心循環(huán)syncLoop的時(shí)候,里面提到的定期清理事件(HouseKeeping)嗎?HouseKeeping是一個(gè)每隔2s運(yùn)行一次的定時(shí)任務(wù),負(fù)責(zé)掃描清理孤兒Pod,刪除其殘余的Volume目錄并停止該P(yáng)od所屬的Pod worker goroutine。HouseKeeping發(fā)現(xiàn)Job Pod進(jìn)入Completed狀態(tài)后,會(huì)查找該P(yáng)od是否還有正在運(yùn)行的殘余容器,如有則請(qǐng)理之。由于第二個(gè)Sandbox容器依然在運(yùn)行,因此HouseKeeping會(huì)將其清理,其中的一個(gè)步驟是清理該P(yáng)od所屬的cgroup,殺死該group中的所有進(jìn)程,這樣第二個(gè)Sandbox容器里的pause進(jìn)程被殺,容器退出。

已經(jīng)死亡的第二個(gè)Sandbox容器會(huì)被kubelet里的垃圾回收循環(huán)接管,它將被徹底停止銷毀。然而由于之前的Housekeeping操作已經(jīng)銷毀了該容器的cgroup, 網(wǎng)絡(luò)名稱空間不復(fù)存在,因此在調(diào)用CNI插件拆除Sandbox網(wǎng)絡(luò)時(shí),kubelet無(wú)法獲得正確的NETNS參數(shù)傳給CNI,只能傳入空字符串。

到此,問(wèn)題的原因已經(jīng)確認(rèn)。

問(wèn)題解決

一切水落石出后,我們開(kāi)始著手解決問(wèn)題。為了能確保找到所刪除的Pod對(duì)應(yīng)的VPC IP,CNI需要在ADD操作成功后,將PodName,Sandbox容器ID,NameSpace,VPC IP等對(duì)應(yīng)關(guān)聯(lián)信息進(jìn)行額外存儲(chǔ)。這樣當(dāng)進(jìn)入DEL操作后,只需要通過(guò)kubelet傳入的PodName,Sandbox容器ID和NameSpace即可找到VPC IP,然后通過(guò)UCloud 公有云相關(guān)API刪除之,無(wú)需依賴NETNS操作。

考慮到問(wèn)題的根因是出現(xiàn)在kubelet源碼中的SyncPod函數(shù),UK8S團(tuán)隊(duì)也已修復(fù)kubelet相關(guān)源碼并準(zhǔn)備提交patch給Kubernetes社區(qū)。

寫(xiě)在最后

Kubernetes依然是一個(gè)高速迭代中的開(kāi)源項(xiàng)目,生產(chǎn)環(huán)境中會(huì)不可用避免遇見(jiàn)一些異?,F(xiàn)象。UK8S研發(fā)團(tuán)隊(duì)在學(xué)習(xí)理解Kubernetes各個(gè)組件運(yùn)行原理的同時(shí),積極根據(jù)現(xiàn)網(wǎng)異常現(xiàn)象深入源碼逐步探索出問(wèn)題根因,進(jìn)一步保障UK8S服務(wù)的穩(wěn)定性和可靠性,提升產(chǎn)品體驗(yàn)。

2019年內(nèi)UK8S還將支持節(jié)點(diǎn)彈性伸縮(Cluster AutoScaler)、物理機(jī)資源、GPU資源、混合云和ServiceMesh等一系列特性,敬請(qǐng)期待。

歡迎掃描下方二維碼,加入U(xiǎn)Cloud K8S技術(shù)交流群,和我們共同探討Kubernetes前沿技術(shù)。

如顯示群人數(shù)已加滿,可添加群主微信zhaoqi628543,備注K8S即可邀請(qǐng)入群。

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)cdcxhl.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。

標(biāo)題名稱:Kubernetes源碼探疑:PodIP泄露排查及解決-創(chuàng)新互聯(lián)
瀏覽路徑:http://bm7419.com/article22/gdojc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App開(kāi)發(fā)、App設(shè)計(jì)、建站公司、網(wǎng)站維護(hù)、網(wǎng)站設(shè)計(jì)公司響應(yīng)式網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

網(wǎng)站優(yōu)化排名