您好,登錄后才能下訂單哦!
PouchContainer CRI的設(shè)計與實現(xiàn)方法是怎樣的,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。
1. CRI簡介
在每個Kubernetes節(jié)點的最底層都有一個程序負(fù)責(zé)具體的容器創(chuàng)建刪除工作,Kubernetes會對其接口進(jìn)行調(diào)用,從而完成容器的編排調(diào)度。我們將這一層軟件稱之為容器運(yùn)行時(Container Runtime),大名鼎鼎的Docker就是其中的代表。
當(dāng)然,容器運(yùn)行時并非只有Docker一種,包括CoreOS的rkt,hyper.sh的runV,Google的gvisor,以及本文的主角PouchContainer,都包含了完整的容器操作,能夠用來創(chuàng)建特性各異的容器。不同的容器運(yùn)行時有著各自獨特的優(yōu)點,能夠滿足不同用戶的需求,因此Kubernetes支持多種容器運(yùn)行時勢在必行。
最初,Kubernetes原生內(nèi)置了對Docker的調(diào)用接口,之后社區(qū)又在Kubernetes 1.3中集成了rkt的接口,使其成為了Docker以外,另一個可選的容器運(yùn)行時。不過,此時不論是對于Docker還是對于rkt的調(diào)用都是和Kubernetes的核心代碼強(qiáng)耦合的,這無疑會帶來如下兩方面的問題:
新興的容器運(yùn)行時,例如PouchContainer這樣的后起之秀,加入Kubernetes生態(tài)難度頗大。容器運(yùn)行時的開發(fā)者必須對于Kubernetes的代碼(至少是Kubelet)有著非常深入的理解,才能順利完成兩者之間的對接。
Kubernetes的代碼將更加難以維護(hù),這也體現(xiàn)在兩方面:(1)將各種容器運(yùn)行時的調(diào)用接口全部硬編碼進(jìn)Kubernetes,會讓Kubernetes的核心代碼變得臃腫不堪,(2)容器運(yùn)行時接口細(xì)微的改動都會引發(fā)Kubernetes核心代碼的修改,增加Kubernetes的不穩(wěn)定性
為了解決這些問題,社區(qū)在Kubernetes 1.5引入了CRI(Container Runtime Interface),通過定義一組容器運(yùn)行時的公共接口將Kubernetes對于各種容器運(yùn)行時的調(diào)用接口屏蔽至核心代碼以外,Kubernetes核心代碼只對該抽象接口層進(jìn)行調(diào)用。而對于各種容器運(yùn)行時,只要滿足了CRI中定義的各個接口就能順利接入Kubernetes,成為其中的一個容器運(yùn)行時選項。方案雖然簡單,但是對于Kubernetes社區(qū)維護(hù)者和容器運(yùn)行時開發(fā)者來說,都是一種解放。
2. CRI設(shè)計概述
如上圖所示,左邊的Kubelet是Kubernetes集群的Node Agent,它會對本節(jié)點上容器的狀態(tài)進(jìn)行監(jiān)控,保證它們都按照預(yù)期狀態(tài)運(yùn)行。為了實現(xiàn)這一目標(biāo),Kubelet會不斷調(diào)用相關(guān)的CRI接口來對容器進(jìn)行同步。
CRI shim則可以認(rèn)為是一個接口轉(zhuǎn)換層,它會將CRI接口,轉(zhuǎn)換成對應(yīng)底層容器運(yùn)行時的接口,并調(diào)用執(zhí)行,返回結(jié)果。對于有的容器運(yùn)行時,CRI shim是作為一個獨立的進(jìn)程存在的,例如當(dāng)選用Docker為Kubernetes的容器運(yùn)行時,Kubelet初始化時,會附帶啟動一個Docker shim進(jìn)程,它就是Docker的CRI shime。而對于PouchContainer,它的CRI shim則是內(nèi)嵌在Pouchd中的,我們將其稱之為CRI manager。關(guān)于這一點,我們會在下一節(jié)討論P(yáng)ouchContainer相關(guān)架構(gòu)時再詳細(xì)敘述。
CRI本質(zhì)上是一套gRPC接口,Kubelet內(nèi)置了一個gRPC Client,CRI shim中則內(nèi)置了一個gRPC Server。Kubelet每一次對CRI接口的調(diào)用,都將轉(zhuǎn)換為gRPC請求由gRPC Client發(fā)送給CRI shim中的gRPC Server。Server調(diào)用底層的容器運(yùn)行時對請求進(jìn)行處理并返回結(jié)果,由此完成一次CRI接口調(diào)用。
CRI定義的gRPC接口可劃分兩類,ImageService和RuntimeService:其中ImageService負(fù)責(zé)管理容器的鏡像,而RuntimeService則負(fù)責(zé)對容器生命周期進(jìn)行管理以及與容器進(jìn)行交互(exec/attach/port-forward)。
3. CRI Manager架構(gòu)設(shè)計
在PouchContainer的整個架構(gòu)體系中,CRI Manager實現(xiàn)了CRI定義的全部接口,擔(dān)任了PouchContainer中CRI shim的角色。當(dāng)Kubelet調(diào)用一個CRI接口時,請求就會通過Kubelet的gRPC Client發(fā)送到上圖的gRPC Server中。Server會對請求進(jìn)行解析,并調(diào)用CRI Manager相應(yīng)的方法進(jìn)行處理。
我們先通過一個例子來簡單了解一下各個模塊的功能。例如,當(dāng)?shù)竭_(dá)的請求為創(chuàng)建一個Pod,那么CRI Manager會先將獲取到的CRI格式的配置轉(zhuǎn)換成符合PouchContainer接口要求的格式,調(diào)用Image Manager拉取所需的鏡像,再調(diào)用Container Manager創(chuàng)建所需的容器,并調(diào)用CNI Manager,利用CNI插件對Pod的網(wǎng)絡(luò)進(jìn)行配置。最后,Stream Server會對交互類型的CRI請求,例如exec/attach/portforward進(jìn)行處理。
值得注意的是,CNI Manager和Stream Server是CRI Manager的子模塊,而CRI Manager,Container Manager以及Image Manager是三個平等的模塊,它們都位于同一個二進(jìn)制文件Pouchd中,因此它們之間的調(diào)用都是最為直接的函數(shù)調(diào)用,并不存在例如Docker shim與Docker交互時,所需要的遠(yuǎn)程調(diào)用開銷。下面,我們將進(jìn)入CRI Manager內(nèi)部,對其中重要功能的實現(xiàn)做更為深入的理解。
4. Pod模型的實現(xiàn)
在Kubernetes的世界里,Pod是最小的調(diào)度部署單元。簡單地說,一個Pod就是由一些關(guān)聯(lián)較為緊密的容器構(gòu)成的容器組。作為一個整體,這些“親密”的容器之間會共享一些東西,從而讓它們之間的交互更為高效。例如,對于網(wǎng)絡(luò),同一個Pod中的容器會共享同一個IP地址和端口空間,從而使它們能直接通過localhost互相訪問。對于存儲,Pod中定義的volume會掛載到其中的每個容器中,從而讓每個容器都能對其進(jìn)行訪問。
事實上,只要一組容器之間共享某些Linux Namespace以及掛載相同的volume就能實現(xiàn)上述的所有特性。下面,我們就通過創(chuàng)建一個具體的Pod來分析PouchContainer中的CRI Manager是如何實現(xiàn)Pod模型的:
1、當(dāng)Kubelet需要新建一個Pod時,首先會對RunPodSandbox這一CRI接口進(jìn)行調(diào)用,而CRI Manager對該接口的實現(xiàn)是創(chuàng)建一個我們稱之為"infra container"的特殊容器。從容器實現(xiàn)的角度來看,它并不特殊,無非是調(diào)用Container Manager,創(chuàng)建一個鏡像為pause-amd64:3.0的普通容器。但是從整個Pod容器組的角度來看,它是有著特殊作用的,正是它將自己的Linux Namespace貢獻(xiàn)出來,作為上文所說的各容器共享的Linux Namespace,將容器組中的所有容器聯(lián)結(jié)到一起。它更像是一個載體,承載了Pod中所有其他的容器,為它們的運(yùn)行提供基礎(chǔ)設(shè)施。而一般我們也用infra container代表一個Pod。
2、在infra container創(chuàng)建完成之后,Kubelet會對Pod容器組中的其他容器進(jìn)行創(chuàng)建。每創(chuàng)建一個容器就是連續(xù)調(diào)用CreateContainer和StartContainer這兩個CRI接口。對于CreateContainer,CRI Manager僅僅只是將CRI格式的容器配置轉(zhuǎn)換為PouchContainer格式的容器配置,再將其傳遞給Container Manager,由其完成具體的容器創(chuàng)建工作。這里我們唯一需要關(guān)心的問題是,該容器如何加入上文中提到的infra container的Linux Namespace。其實真正的實現(xiàn)非常簡單,在Container Manager的容器配置參數(shù)中有PidMode, IpcMode以及NetworkMode三個參數(shù),分別用于配置容器的Pid Namespace,Ipc Namespace和Network Namespace。籠統(tǒng)地說,對于容器的Namespace的配置一般都有兩種模式:"None"模式,即創(chuàng)建該容器自己獨有的Namespace,另一種即為"Container"模式,即加入另一個容器的Namespace。顯然,我們只需要將上述三個參數(shù)配置為"Container"模式,加入infra container的Namespace即可。具體是如何加入的,CRI Manager并不需要關(guān)心。對于StartContainer,CRI Manager僅僅只是做了一層轉(zhuǎn)發(fā),從請求中獲取容器ID并調(diào)用Container Manager的Start接口啟動容器。
3、最后,Kubelet會不斷調(diào)用ListPodSandbox和ListContainers這兩個CRI接口來獲取本節(jié)點上容器的運(yùn)行狀態(tài)。其中ListPodSandbox羅列的其實就是各個infra container的狀態(tài),而ListContainer羅列的是除了infra container以外其他容器的狀態(tài)?,F(xiàn)在問題是,對于Container Manager來說,infra container和其他container并不存在任何區(qū)別。那么CRI Manager是如何對這些容器進(jìn)行區(qū)分的呢?事實上,CRI Manager在創(chuàng)建容器時,會在已有容器配置的基礎(chǔ)之上,額外增加一個label,標(biāo)志該容器的類型。從而在實現(xiàn)ListPodSandbox和ListContainers接口的時候,以該label的值作為條件,就能對不同類型的容器進(jìn)行過濾。
綜上,對于Pod的創(chuàng)建,我們可以概述為先創(chuàng)建infra container,再創(chuàng)建pod中的其他容器,并讓它們加入infra container的Linux Namespace。
5. Pod網(wǎng)絡(luò)配置
因為Pod中所有的容器都是共享Network Namespace的,因此我們只需要在創(chuàng)建infra container的時候,對它的Network Namespace進(jìn)行配置即可。
在Kubernetes生態(tài)體系中容器的網(wǎng)絡(luò)功能都是由CNI實現(xiàn)的。和CRI類似,CNI也是一套標(biāo)準(zhǔn)接口,各種網(wǎng)絡(luò)方案只要實現(xiàn)了該接口就能無縫接入Kubernetes。CRI Manager中的CNI Manager就是對CNI的簡單封裝。它在初始化的過程中會加載目錄/etc/cni/net.d下的配置文件,如下所示:
其中指定了配置Pod網(wǎng)絡(luò)會使用到的CNI插件,例如上文中的bridge,以及一些網(wǎng)絡(luò)配置信息,例如本節(jié)點Pod所屬的子網(wǎng)范圍和路由配置。
下面我們就通過具體的步驟來展示如何將一個Pod加入CNI網(wǎng)絡(luò):
1、當(dāng)調(diào)用container manager創(chuàng)建infra container時,將NetworkMode設(shè)置為"None"模式,表示創(chuàng)建一個該infra container獨有的Network Namespace且不做任何配置。
2、根據(jù)infra container對應(yīng)的PID,獲取其對應(yīng)的Network Namespace路徑/proc/{pid}/ns/net。
3、調(diào)用CNI Manager的SetUpPodNetwork方法,核心參數(shù)為步驟二中獲取的Network Namespace路徑。該方法做的工作就是調(diào)用CNI Manager初始化時指定的CNI插件,例如上文中的bridge,對參數(shù)中指定的Network Namespace進(jìn)行配置,包括創(chuàng)建各種網(wǎng)絡(luò)設(shè)備,進(jìn)行各種網(wǎng)絡(luò)配置,將該Network Namespace加入插件對應(yīng)的CNI網(wǎng)絡(luò)中。
對于大多數(shù)Pod,網(wǎng)絡(luò)配置都是按照上述步驟操作的,大部分的工作將由CNI以及對應(yīng)的CNI插件替我們完成。但是對于一些特殊的Pod,它們會將自己的網(wǎng)絡(luò)模式設(shè)置為"Host",即和宿主機(jī)共享Network Namespace。這時,我們只需要在調(diào)用Container Manager創(chuàng)建infra container時,將NetworkMode設(shè)置為"Host",并且跳過CNI Manager的配置即可。
對于Pod中其他的容器,不論P(yáng)od是處于"Host"網(wǎng)絡(luò)模式,還是擁有獨立的Network Namespace,都只需要在調(diào)用Container Manager創(chuàng)建容器時,將NetworkMode配置為"Container"模式,加入infra container所在的Network Namespace即可。
6. IO流處理
Kubernetes提供了例如kubectl exec/attach/port-forward這樣的功能來實現(xiàn)用戶和某個具體的Pod或者容器的直接交互。如下所示:
可以看到,exec一個Pod等效于ssh登錄到該容器中。下面,我們根據(jù)kubectl exec的執(zhí)行流來分析Kubernetes中對于IO請求的處理,以及CRI Manager在其中扮演的角色。
如上圖所示,執(zhí)行一條kubectl exec命令的步驟如下:
1、kubectl exec命令的本質(zhì)其實是對Kubernetes集群中某個容器執(zhí)行exec命令,并將由此產(chǎn)生的IO流轉(zhuǎn)發(fā)到用戶的手中。所以請求將首先層層轉(zhuǎn)發(fā)到達(dá)該容器所在節(jié)點的Kubelet,Kubelet再根據(jù)配置調(diào)用CRI中的Exec接口。請求的配置參數(shù)如下:
2、令人感到意外的是,CRI Manager的Exec方法并沒有直接調(diào)用Container Manager,對目標(biāo)容器執(zhí)行exec命令,而是轉(zhuǎn)而調(diào)用了其內(nèi)置的Stream Server的GetExec方法。
3、Stream Server的GetExec方法所做的工作是將該exec請求的內(nèi)容保存到了上圖所示的Request Cache中,并返回一個token,利用該token,我們可以重新從Request Cache中找回對應(yīng)的exec請求。最后,將這個token寫入一個URL中,并作為執(zhí)行結(jié)果層層返回到ApiServer。
4、ApiServer利用返回的URL直接對目標(biāo)容器所在節(jié)點的Stream Server發(fā)起一個http請求,請求的頭部包含了"Upgrade"字段,要求將http協(xié)議升級為websocket或者SPDY這樣的streaming protocol,用于支持多條IO流的處理,本文我們以SPDY為例。
5、Stream Server對ApiServer發(fā)送的請求進(jìn)行處理,首先根據(jù)URL中的token,從Request Cache中獲取之前保存的exec請求配置。之后,回復(fù)該http請求,同意將協(xié)議升級為SPDY,并根據(jù)exec請求的配置等待ApiServer創(chuàng)建指定數(shù)量的stream,分別對應(yīng)標(biāo)準(zhǔn)輸入Stdin,標(biāo)準(zhǔn)輸出Stdout,標(biāo)準(zhǔn)錯誤輸出Stderr。
6、待Stream Server獲取指定數(shù)量的Stream之后,依次調(diào)用Container Manager的CreateExec和startExec方法,對目標(biāo)容器執(zhí)行exec操作并將IO流轉(zhuǎn)發(fā)至對應(yīng)的各個stream中。
7、最后,ApiServer將各個stream的數(shù)據(jù)轉(zhuǎn)發(fā)至用戶,開啟用戶與目標(biāo)容器的IO交互。
事實上,在引入CRI之前,Kubernetes對于IO的處理方式和我們的預(yù)期是一致的,Kubelet會直接對目標(biāo)容器執(zhí)行exec命令,并將IO流轉(zhuǎn)發(fā)回ApiServer。但是這樣會讓Kubelet承載過大的壓力,所有的IO流都需要經(jīng)過它的轉(zhuǎn)發(fā),這顯然是不必要的。因此上述的處理雖然初看較為復(fù)雜,但是有效地緩解了Kubelet的壓力,并且也讓IO的處理更為高效。
看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。