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