国产在线观看精品免费,亚洲日本VA久久一区二区,香蕉久久99综合一区二区三区,久久精品99国产精品蜜桃小说

淺析 k8s 容器運行時(shí)演進(jìn)

2021-11-04 09:22:51 shuai.chang

睿智創(chuàng  )新RAIZ,一體化IT服務(wù)提供商

在docker/k8s時(shí)代,經(jīng)常聽(tīng)到CRI, OCI,containerd和各種shim等名詞,看完本篇博文,您會(huì )有個(gè)徹底的理解。


典型的K8S Runtime架構


從最常見(jiàn)的Docker說(shuō)起,kubelet和Docker的集成方案圖如下:
睿智創(chuàng  )新RAIZ,一體化IT服務(wù)提供商當kubelet要創(chuàng )建一個(gè)容器時(shí),需要以下幾步:


  1. Kubelet 通過(guò) CRI 接口(gRPC)調用 dockershim,請求創(chuàng )建一個(gè)容器。CRI 即容器運行時(shí)接口(Container Runtime Interface),這一步中,Kubelet 可以視作一個(gè)簡(jiǎn)單的 CRI Client,而 dockershim 就是接收請求的 Server。目前 dockershim 的代碼其實(shí)是內嵌在 Kubelet 中的,所以接收調用的湊巧就是 Kubelet 進(jìn)程;

  2. dockershim 收到請求后,轉化成 Docker Daemon 能聽(tīng)懂的請求,發(fā)到 Docker Daemon 上請求創(chuàng )建一個(gè)容器。

  3. Docker Daemon 早在 1.12 版本中就已經(jīng)將針對容器的操作移到另一個(gè)守護進(jìn)程——containerd 中了,因此 Docker Daemon 仍然不能幫我們創(chuàng )建容器,而是要請求 containerd 創(chuàng )建一個(gè)容器;

  4. containerd 收到請求后,并不會(huì )自己直接去操作容器,而是創(chuàng )建一個(gè)叫做 containerd-shim 的進(jìn)程,讓 containerd-shim 去操作容器。這是因為容器進(jìn)程需要一個(gè)父進(jìn)程來(lái)做諸如收集狀態(tài),維持 stdin 等 fd 打開(kāi)等工作。而假如這個(gè)父進(jìn)程就是 containerd,那每次 containerd 掛掉或升級,整個(gè)宿主機上所有的容器都得退出了。而引入了 containerd-shim 就規避了這個(gè)問(wèn)題(containerd 和 shim 并不是父子進(jìn)程關(guān)系);

  5. 我們知道創(chuàng )建容器需要做一些設置 namespaces 和 cgroups,掛載 root filesystem 等等操作,而這些事該怎么做已經(jīng)有了公開(kāi)的規范了,那就是 OCI(Open Container Initiative,開(kāi)放容器標準)。它的一個(gè)參考實(shí)現叫做 runC。于是,containerd-shim 在這一步需要調用 runC 這個(gè)命令行工具,來(lái)啟動(dòng)容器;

  6. runC 啟動(dòng)完容器后本身會(huì )直接退出,containerd-shim 則會(huì )成為容器進(jìn)程的父進(jìn)程,負責收集容器進(jìn)程的狀態(tài),上報給 containerd,并在容器中 pid 為 1 的進(jìn)程退出后接管容器中的子進(jìn)程進(jìn)行清理,確保不會(huì )出現僵尸進(jìn)程。


這個(gè)過(guò)程乍一看像是在搞我們:Docker Daemon 和 dockershim 看上去就是兩個(gè)不干活躺在中間劃水的啊,Kubelet 為啥不直接調用 containerd 呢?

當然可以,先看下現在的架構為什么如此繁雜。


容器歷史小敘


早期的k8s runtime架構,遠沒(méi)這么復雜,kubelet創(chuàng )建容器,直接調用docker daemon,docker daemon自己調用libcontainer就把容器運行起來(lái)。


但往往,事情不會(huì )如此簡(jiǎn)單,一系列政治斗爭開(kāi)始了,先是大佬們認為運行時(shí)標準不能被 Docker 一家公司控制,于是就攛掇著(zhù)搞了開(kāi)放容器標準 OCI。Docker 則把 libcontainer 封裝了一下,變成 runC 捐獻出來(lái)作為 OCI 的參考實(shí)現。


再接下來(lái)就是 rkt(coreos推出的,類(lèi)似docker) 想從 Docker 那邊分一杯羹,希望 Kubernetes 原生支持 rkt 作為運行時(shí),而且 PR 還真的合進(jìn)去了。維護過(guò)一塊業(yè)務(wù)同時(shí)接兩個(gè)需求方的讀者老爺應該都知道類(lèi)似的事情有多坑,Kubernetes 中負責維護 kubelet 的小組 sig-node 也是被狠狠坑了一把。


大家一看這么搞可不行,今天能有 rkt,明天就能有更多幺蛾子出來(lái),這么搞下去我們小組也不用干活了,整天搞兼容性的 bug 就夠嗆。于是乎,Kubernetes 1.5 推出了 CRI 機制,即容器運行時(shí)接口(Container Runtime Interface),Kubernetes 告訴大家,你們想做 Runtime 可以啊,我們也資瓷歡迎,實(shí)現這個(gè)接口就成,成功反客為主。


不過(guò) CRI 本身只是 Kubernetes 推的一個(gè)標準,當時(shí)的 Kubernetes 尚未達到如今這般武林盟主的地位,容器運行時(shí)當然不能說(shuō)我跟 Kubernetes 綁死了只提供 CRI 接口,于是就有了 shim(墊片)這個(gè)說(shuō)法,一個(gè) shim 的職責就是作為 Adapter 將各種容器運行時(shí)本身的接口適配到 Kubernetes 的 CRI 接口上。


接下來(lái)就是 Docker 要搞 Swarm 進(jìn)軍 PaaS 市場(chǎng),于是做了個(gè)架構切分,把容器操作都移動(dòng)到一個(gè)單獨的 Daemon 進(jìn)程 containerd 中去,讓 Docker Daemon 專(zhuān)門(mén)負責上層的封裝編排??上?Swarm 在 Kubernetes 面前實(shí)在是不夠打,慘敗之后 Docker 公司就把 containerd 項目捐給 CNCF 縮回去安心搞 Docker 企業(yè)版了。


最后就是我們在上一張圖里看到的這一坨東西了,盡管現在已經(jīng)有 CRI-O,containerd-plugin 這樣更精簡(jiǎn)輕量的 Runtime 架構,dockershim 這一套作為經(jīng)受了最多生產(chǎn)環(huán)境考驗的方案,迄今為止仍是 Kubernetes 默認的 Runtime 實(shí)現。


OCI, CRI


OCI(開(kāi)放容器標準),規定了2點(diǎn):

  1. 容器鏡像要長(cháng)啥樣,即 ImageSpec。里面的大致規定就是你這個(gè)東西需要是一個(gè)壓縮了的文件夾,文件夾里以 xxx 結構放 xxx 文件;

  2. 容器要需要能接收哪些指令,這些指令的行為是什么,即 RuntimeSpec。這里面的大致內容就是“容器”要能夠執行 “create”,“start”,“stop”,“delete” 這些命令,并且行為要規范。


runC 為啥叫參考實(shí)現呢,就是它能按照標準將符合標準的容器鏡像運行起來(lái),標準的好處就是方便搞創(chuàng )新,反正只要我符合標準,生態(tài)圈里的其它工具都能和我一起愉快地工作(……當然 OCI 這個(gè)標準本身制定得不怎么樣,真正工程上還是要做一些 adapter 的),那我的鏡像就可以用任意的工具去構建,我的“容器”就不一定非要用 namespace 和 cgroups 來(lái)做隔離。這就讓各種虛擬化容器可以更好地參與到游戲當中,我們暫且不表。


而 CRI 更簡(jiǎn)單,單純是一組 gRPC 接口,掃一眼 kubelet/apis/cri/services.go 就能歸納出幾套核心接口:

  • 一套針對容器操作的接口,包括創(chuàng )建,啟停容器等等;

  • 一套針對鏡像操作的接口,包括拉取鏡像刪除鏡像等;

  • 一套針對 PodSandbox(容器沙箱環(huán)境)的操作接口,我們之后再說(shuō)。


現在我們可以找到很多符合 OCI 標準或兼容了 CRI 接口的項目,而這些項目就大體構成了整個(gè) Kuberentes 的 Runtime 生態(tài):


  • OCI Compatible:runC,Kata(以及它的前身 runV 和 Clear Containers),gVisor。其它比較偏門(mén)的還有 Rust 寫(xiě)的 railcar

  • CRI Compatible:Docker(借助 dockershim),containerd(借助 CRI-containerd),CRI-O,Frakti,etc


OCI, CRI 確實(shí)不是一個(gè)好名字,在這篇文章的語(yǔ)境中更準確的說(shuō)法:cri-runtime 和 oci-runtime。通過(guò)這個(gè)粗略的分類(lèi),我們其實(shí)可以總結出整個(gè) Runtime 架構萬(wàn)變不離其宗的三層抽象:


Orchestration API  -> Container API(cri-runtime)  -> Kernel API(oci-runtime)


根據這個(gè)思路,我們就很容易理解下面這兩種東西:


  • 各種更為精簡(jiǎn)的 cri-runtime(反正就是要干掉 Docker)

  • 各種“強隔離”容器方案


Containerd和CRI-O


上一節看到現在的 Runtime 實(shí)在是有點(diǎn)復雜了,而復雜是萬(wàn)惡之源(其實(shí)本質(zhì)上就是想干掉 Docker),于是就有了直接拿 containerd 做 oci-runtime 的方案。當然,除了 Kubernetes 之外,containerd 還要接諸如 Swarm 等調度系統,因此它不會(huì )去直接實(shí)現 CRI,這個(gè)適配工作當然就要交給一個(gè) shim 了。


containerd 1.0 中,對 CRI 的適配通過(guò)一個(gè)單獨的進(jìn)程 CRI-containerd 來(lái)完成:
睿智創(chuàng  )新RAIZ,一體化IT服務(wù)提供商


containerd 1.1 中做的又更漂亮一點(diǎn),砍掉了 CRI-containerd 這個(gè)進(jìn)程,直接把適配邏輯作為插件放進(jìn)了 containerd 主進(jìn)程中:
睿智創(chuàng  )新RAIZ,一體化IT服務(wù)提供商


但在 containerd 做這些事情前,社區就已經(jīng)有了一個(gè)更為專(zhuān)注的 cri-runtime:CRI-O,它非常純粹,就是兼容 CRI 和 OCI,做一個(gè) Kubernetes 專(zhuān)用的運行時(shí):
睿智創(chuàng  )新RAIZ,一體化IT服務(wù)提供商


其中 conmon 就對應 containerd-shim,大體意圖是一樣的。


CRI-O 和(直接調用)containerd 的方案比起默認的 dockershim 確實(shí)簡(jiǎn)潔很多,但沒(méi)啥生產(chǎn)環(huán)境的驗證案例,我所知道的僅僅是 containerd 在 GKE 上是 beta 狀態(tài)。因此假如你對 Docker 沒(méi)有特殊的政治恨意,大可不必把 dockershim 這套換掉。

強隔離容器:Kata,gVisor,Firecracker


一直以來(lái),K8S都難以實(shí)現真正的多租戶(hù)。


理想來(lái)說(shuō),平臺的各個(gè)租戶(hù)(tenant)之間應該無(wú)法感受到彼此的存在,表現得就像每個(gè)租戶(hù)獨占這整個(gè)平臺一樣。具體來(lái)說(shuō),我不能看到其它租戶(hù)的資源,我的資源跑滿(mǎn)了不能影響其它租戶(hù)的資源使用,我也無(wú)法從網(wǎng)絡(luò )或內核上攻擊其它租戶(hù)。
Kubernetes 當然做不到,其中最大的兩個(gè)原因是:

  • kube-apiserver 是整個(gè)集群中的單例,并且沒(méi)有多租戶(hù)概念

  • 默認的 oci-runtime 是 runC,而 runC 啟動(dòng)的容器是共享內核的


對于第二個(gè)問(wèn)題,一個(gè)典型的解決方案就是提供一個(gè)新的 OCI 實(shí)現,用 VM 來(lái)跑容器,實(shí)現內核上的硬隔離。runV 和 Clear Containers 都是這個(gè)思路。因為這兩個(gè)項目做得事情是很類(lèi)似,后來(lái)就合并成了一個(gè)項目 Kata Container。Kata 的一張圖很好地解釋了基于虛擬機的容器與基于 namespaces 和 cgroups 的容器間的區別:

睿智創(chuàng  )新RAIZ,一體化IT服務(wù)提供商當然,沒(méi)有系統是完全安全的,假如 hypervisor 存在漏洞,那么用戶(hù)仍有可能攻破隔離。但所有的事情都要對比而言,在共享內核的情況下,暴露的攻擊面是非常大的,做安全隔離的難度就像在美利堅和墨西哥之間修 The Great Wall,而當內核隔離之后,只要守住 hypervisor 這道關(guān)子就后顧無(wú)虞了。


嗯,一個(gè) VM 里跑一個(gè)容器,聽(tīng)上去隔離性很不錯,但不是說(shuō)虛擬機又笨重又不好管理才切換到容器的嗎,怎么又要走回去了?


Kata 告訴你,虛擬機沒(méi)那么邪惡,只是以前沒(méi)玩好:

  • 不好管理是因為沒(méi)有遵循“不可變基礎設施”,大家都去虛擬機上這摸摸那碰碰,這臺裝 Java 8 那臺裝 Java 6,Admin 是要 angry 的。Kata 則支持 OCI 鏡像,完全可以用上 Dockerfile + 鏡像,讓不好管理成為了過(guò)去時(shí);

  • 笨重是因為之前要虛擬化整個(gè)系統,現在我們只著(zhù)眼于虛擬化應用,那就可以裁剪掉很多功能,把 VM 做得很輕量,因此即便用虛擬機來(lái)做容器,Kata 還是可以將容器啟動(dòng)時(shí)間壓縮得非常短,啟動(dòng)后在內存上和 IO 上的 overhead 也盡可能去優(yōu)化。


不過(guò)話(huà)說(shuō)回來(lái),Kubernetes 上的調度單位是 Pod,是容器組啊,Kata 這樣一個(gè)虛擬機里一個(gè)容器,同一個(gè) Pod 間的容器還怎么做 namespace 的共享?


這就要說(shuō)回我們前面講到的 CRI 中針對 PodSandbox(容器沙箱環(huán)境)的操作接口了。第一節中,我們刻意簡(jiǎn)化了場(chǎng)景,只考慮創(chuàng )建一個(gè)容器,而沒(méi)有討論創(chuàng )建一個(gè) Pod。大家都知道,真正啟動(dòng) Pod 里定義的容器之前,kubelet 會(huì )先啟動(dòng)一個(gè) infra 容器,并執行 /pause 讓 infra 容器的主進(jìn)程永遠掛起。這個(gè)容器存在的目的就是維持住整個(gè) Pod 的各種 namespace,真正的業(yè)務(wù)容器只要加入 infra 容器的 network 等 namespace 就能實(shí)現對應 namespace 的共享。而 infra 容器創(chuàng )造的這個(gè)共享環(huán)境則被抽象為 PodSandbox。每次 kubelet 在創(chuàng )建 Pod 時(shí),就會(huì )先調用 CRI 的 RunPodSandbox 接口啟動(dòng)一個(gè)沙箱環(huán)境,再調用 CreateContainer 在沙箱中創(chuàng )建容器。


這里就已經(jīng)說(shuō)出答案了,對于 Kata Container 而言,只要在 RunPodSandbox 調用中創(chuàng )建一個(gè) VM,之后再往 VM 中添加容器就可以了。最后運行 Pod 的樣子就是這樣的:
睿智創(chuàng  )新RAIZ,一體化IT服務(wù)提供商


說(shuō)完了 Kata,其實(shí) gVisor 和 Firecracker 都不言自明了,大體上都是類(lèi)似的,只是:

gVisor 并不會(huì )去創(chuàng )建一個(gè)完整的 VM,而是實(shí)現了一個(gè)叫 “Sentry” 的用戶(hù)態(tài)進(jìn)程來(lái)處理容器的 syscall,而攔截 syscall 并重定向到 Sentry 的過(guò)程則由 KVM 或 ptrace 實(shí)現。


Firecracker 稱(chēng)自己為 microVM,即輕量級虛擬機,它本身還是基于 KVM 的,不過(guò) KVM 通常使用 QEMU 來(lái)虛擬化除 CPU 和內存外的資源,比如 IO 設備,網(wǎng)絡(luò )設備。Firecracker 則使用 rust 實(shí)現了最精簡(jiǎn)的設備虛擬化,為的就是壓榨虛擬化的開(kāi)銷(xiāo),越輕量越好。


我要咨詢(xún)