導(dǎo)讀
接觸kubernetes的時(shí)候, 搞不懂OCI,CRI,runC,containerd,shim 之間的區(qū)別和聯(lián)系, 下面梳理一下OCI產(chǎn)生的背景,對(duì)docker的影響,以及編排工具kubernetes CRI的產(chǎn)生背景及變化
爭(zhēng)取通過下面的梳理說明白
談到OCI,會(huì)想到docker,先有docker后有OCI,說到docker就說到容器技術(shù), docker不是最早的容器技術(shù),比如早期的chroot Jails,Solaris Containers等,但是是docker把容器技術(shù)推向了巔峰,讓更多人關(guān)注并使用了容器技術(shù)
docker自2013發(fā)布以來,github中docker的代碼活躍度居高不下,更多的個(gè)人或企業(yè)使用docker,容器就是docker也逐漸被大家認(rèn)可,如果docker就是容器的標(biāo)準(zhǔn),那其他容器怎么辦(比如coreOS推出的rkt),
其次容器上層的編排工具(容器集群調(diào)度,比如kubernetes,mesos等)和docker緊密耦合,docker 接口的變化導(dǎo)致上層編排的穩(wěn)定性甚至服務(wù)異常.
如果容器以docker作為標(biāo)準(zhǔn),那么docker接口的變化可能導(dǎo)致社區(qū)中所有相關(guān)工具都要更新,不然就無法使用,如果沒有標(biāo)準(zhǔn),這將導(dǎo)致容器實(shí)現(xiàn)的碎片化,出現(xiàn)大量的沖突和冗余,
這兩種情況都是社區(qū)不愿意看到的事情艾船,于是OCI出現(xiàn)了
它的核心目標(biāo)圍繞容器的格式和運(yùn)行時(shí)制定一個(gè)開放的工業(yè)化標(biāo)準(zhǔn),并推動(dòng)這個(gè)標(biāo)準(zhǔn),保持容器的靈活性和開放性,容器能運(yùn)行在任何的硬件和系統(tǒng)上.
容器不應(yīng)該綁定到特定的客戶機(jī)或編排堆棧,不應(yīng)該與任何特定的供應(yīng)商緊密關(guān)聯(lián),并且可以跨多種操作系統(tǒng)
官網(wǎng)上對(duì) OCI 的介紹如下:
Established in June 2015 by Docker and other leaders in the container industry, the OCI currently contains two specifications: the Runtime Specification (runtime-spec) and the Image Specification (image-spec). The Runtime Specification outlines how to run a “filesystem bundle” that is unpacked on disk. At a high-level an OCI implementation would download an OCI Image then unpack that image into an OCI Runtime filesystem bundle. At this point the OCI Runtime Bundle would be run by an OCI Runtime.
OCI由docker以及其他容器行業(yè)領(lǐng)導(dǎo)者創(chuàng)建于2015年高每,目前主要有兩個(gè)標(biāo)準(zhǔn):容器運(yùn)行時(shí)標(biāo)準(zhǔn)(runtime-spec)和容器鏡像標(biāo)準(zhǔn)(image-spec)
這兩個(gè)標(biāo)準(zhǔn)通過OCI runtime filesytem bundle的標(biāo)準(zhǔn)格式連接在一起,OCI鏡像可以通過工具轉(zhuǎn)換成bundle,然后 OCI 容器引擎能夠識(shí)別這個(gè)bundle來運(yùn)行容器
文檔主要做了兩個(gè)事情:
- 創(chuàng)建鏡像的規(guī)則
- 運(yùn)行鏡像的規(guī)則
-
容器鏡像標(biāo)準(zhǔn)(image-spec)
- 文件系統(tǒng): 以layer保存的文件系統(tǒng),每個(gè)layer保存了和上層之間變化的部分,layer應(yīng)該保存哪些文件,怎么表示增加鲸匿、修改和刪除的文件等
- config文件: 保存了文件系統(tǒng)的層級(jí)信息(每個(gè)層級(jí)的hash值,以及歷史信息)以及容器運(yùn)行時(shí)需要的一些信息(比如環(huán)境變量、工作目錄晒骇、命令參數(shù)、mount 列表)
- manifest文件: 鏡像的config文件索引,有哪些layer,額外的annotation信息,manifest文件中保存了很多和當(dāng)前平臺(tái)有關(guān)的信息
- index文件: 可選的文件,指向不同平臺(tái)的manifest文件,這個(gè)文件能保證一個(gè)鏡像可以跨平臺(tái)使用,每個(gè)平臺(tái)擁有不同的manifest文件,使用index作為索引
-
容器運(yùn)行時(shí)標(biāo)準(zhǔn)(runtime spec)
容器的狀態(tài)包括如下屬性
- ociVersion: OCI版本
- id: 容器的ID,在宿主機(jī)唯一
-
status: 容器運(yùn)行時(shí)狀態(tài),生命周期
- creating: 使用 create 命令創(chuàng)建容器,這個(gè)過程稱為創(chuàng)建中,創(chuàng)建包括文件系統(tǒng)徒坡、namespaces喇完、cgroups剥啤、用戶權(quán)限在內(nèi)的各項(xiàng)內(nèi)容
- created: 容器創(chuàng)建出來,但是還沒有運(yùn)行,表示鏡像和配置沒有錯(cuò)誤,容器能夠運(yùn)行在當(dāng)前平臺(tái)
- running: 容器的運(yùn)行狀態(tài),里面的進(jìn)程處于up狀態(tài),正在執(zhí)行用戶設(shè)定的任務(wù)
- stopped: 容器運(yùn)行完成,或者運(yùn)行出錯(cuò)或者stop命令之后,容器處于暫停狀態(tài),這個(gè)狀態(tài),容器還有很多信息保存在平臺(tái)中,并沒有完全被刪除
- pid: 容器進(jìn)程在宿主機(jī)的進(jìn)程ID
- bundle: 容器文件目錄,存放容器rootfs及相應(yīng)配置的目錄
- annotations: 與容器相關(guān)的注釋
了解了OCI標(biāo)準(zhǔn), 我們?cè)倏匆幌耫ocker架構(gòu)的演變,早期docker所有功能都在docker daemon里面的,但是之后功能越來越多,越來越重,同時(shí)響應(yīng)社區(qū)兼容OCI標(biāo)準(zhǔn),docker做了架構(gòu)調(diào)整
調(diào)整后將容器運(yùn)行時(shí)相關(guān)的程序從docker daemon剝離出來刻诊,形成了containerd, Containerd向docker提供運(yùn)行容器的API牺丙,二者通過gRPC進(jìn)行交互, containerd最后會(huì)通過runC來實(shí)際運(yùn)行容器,
調(diào)整完后的docker架構(gòu)圖如下(docker 1.11版本以后)
runC的前身是docker的libcontainer項(xiàng)目,在libcontainer的基礎(chǔ)上做了封裝,捐贈(zèng)給OCI的一個(gè)符合標(biāo)準(zhǔn)的runtime實(shí)現(xiàn),上圖可以看出docker引擎內(nèi)部也是基于runC構(gòu)建的
關(guān)于libcontainer和runC stackoverflow 的一個(gè)回答:
The Open Container Format (OCF) specification is a written document (or set of documents) defining what a "standard container" is,
in terms of filesystem, available operations and execution environment.
The document seems to be backed up with Go code. This specification is currently (July 2015) a work-in-progress.
Runc is an implementation of the standard. At the time of writing, it is basically a repackaging of libcontainer.
Docker uses libcontainer/runc, but adds a lot of tooling and features on top, such as volumes, networking and management of containers.
There is more information on the Docker blog and Open Containers site
If you're just getting started with containers, I would start with Docker and look into the other projects later once you understand how containers work.
runC只做一件事情就是運(yùn)行容器,提供創(chuàng)建和運(yùn)行容器的CLI(command-line interface)工具, runC直接與容器所依賴的cgroup/namespace linux kernel等進(jìn)行交互冲簿,
負(fù)責(zé)為容器配置cgroup/namespace等啟動(dòng)容器所需的環(huán)境,創(chuàng)建啟動(dòng)容器的相關(guān)進(jìn)程
關(guān)于containerd-shim 看一下Michael Crosby (runC,containerd作者)的解釋
The shim allows for daemonless containers. It basically sits as the parent of the container's process to facilitate a few things. First it allows the runtimes, i.e. runc,to exit after it starts the container. This way we don't have to have the long running runtime processes for containers. When you start mysql you should only see the mysql process and the shim. Second it keeps the STDIO and other fds open for the container incase containerd and/or docker both die. If the shim was not running then the parent side of the pipes or the TTY master would be closed and the container would exit.
Finally it allows the container's exit status to be reported back to a higher level tool like docker without having the be the actual parent of the container's process and do a wait4.
I did a talk on this last week at dockercon US. You can see my slides here. https://github.com/crosbymichael/dockercon-2016
Hopefully that will explain a little more about how containerd and the shim work.
containerd-shim進(jìn)程由containerd進(jìn)程拉起,即containerd進(jìn)程是containerd-shim的父進(jìn)程, 容器進(jìn)程由containerd-shim進(jìn)程拉起, 這樣的優(yōu)點(diǎn)比如升級(jí),重啟docker或者containerd 不會(huì)影響已經(jīng)running的容器進(jìn)程, 而假如這個(gè)父進(jìn)程就是containerd,那每次containerd掛掉或升級(jí),整個(gè)宿主機(jī)上所有的容器都得退出了. 而引入了 containerd-shim 就規(guī)避了這個(gè)問題(當(dāng) containerd 退出或重啟時(shí), shim 會(huì) re-parent 到 systemd 這樣的 1 號(hào)進(jìn)程上)
containerd是一個(gè)簡(jiǎn)單的守護(hù)進(jìn)程,它可以使用runC管理容器,使用gRPC暴露容器的其他功能. 相比較Docker引擎使用gRPC, containerd暴露出針對(duì)容器的增刪改查的接口,然而Docker引擎只是使用 full-blown HTTP API接口對(duì)Images呻澜,Volumes,network宰衙,builds等暴露出這些方法
了解了OCI,以及docker在兼容OCI標(biāo)準(zhǔn)架構(gòu)的調(diào)整后, 迎來我們的重點(diǎn) CRI
CRI是kubernetes推出的一個(gè)標(biāo)準(zhǔn), 推出標(biāo)準(zhǔn)可見其在容器編排領(lǐng)域的地位
講CRI之前我們先簡(jiǎn)單了解一下kubelet拉起一個(gè)容器的過程,如下:
- Kubelet通過CRI接口(gRPC)調(diào)用docker-shim,請(qǐng)求創(chuàng)建一個(gè)容器這一步中,kubelet可以視作一個(gè)簡(jiǎn)單的CRI Client,而docker-shim就是接收請(qǐng)求的Server,
注意的是docker-shim是內(nèi)嵌在Kubelet中的 - docker-shim收到請(qǐng)求后,轉(zhuǎn)化成Docker Daemon能聽懂的請(qǐng)求,發(fā)到Docker Daemon上請(qǐng)求創(chuàng)建一個(gè)容器
- Docker Daemon請(qǐng)求containerd創(chuàng)建一個(gè)容器
- containerd收到請(qǐng)求后創(chuàng)建一個(gè)containerd-shim進(jìn)程,通過containerd-shim操作容器,容器進(jìn)程需要一個(gè)父進(jìn)程來做諸如收集狀態(tài), 維持stdin等fd打開等工作
- containerd-shim在調(diào)用runC來啟動(dòng)容器
- runC 啟動(dòng)完容器后本身會(huì)直接退出,containerd-shim則會(huì)成為容器進(jìn)程的父進(jìn)程,負(fù)責(zé)收集容器進(jìn)程的狀態(tài),上報(bào)給containerd
通過上面kubelet創(chuàng)建容器的流程, 我們可以看到kubelet通過CRI的標(biāo)準(zhǔn)來與外部容器運(yùn)行時(shí)進(jìn)行交互
kubernetes 早期版本1.5之前內(nèi)置了docker和rkt,也就是支持兩種運(yùn)行時(shí), 這個(gè)時(shí)候如果用戶想自定義運(yùn)行時(shí)就比較痛苦了,需要修改kubelet源碼
同時(shí)不同的容器運(yùn)行時(shí)各有所長(zhǎng),隨著k8s在容器編排領(lǐng)域里面老大的地位,許多用戶希望kubernetes支持更多的容器運(yùn)行時(shí),滿足不同用戶,不同環(huán)境的使用
于是從kubernetes1.5開始增加了CRI接口, 有了CRI接口無需修改kubelet源碼就可以支持更多的容器運(yùn)行時(shí)
于此同時(shí)內(nèi)置的docker和rtk逐漸從kubernetes源碼中移除,到kubernetes1.11版本Kubelet內(nèi)置的rkt代碼刪除,CNI的實(shí)現(xiàn)遷移到dockers-shim之內(nèi),
除了docker之外,其他的容器運(yùn)行時(shí)都通過CRI接入.
外部的容器運(yùn)行時(shí)一般稱為CRI shim,它除了實(shí)現(xiàn)CRI接口外,也要負(fù)責(zé)為容器配置網(wǎng)絡(luò),即CNI,有了CNI可以支持社區(qū)內(nèi)的眾多網(wǎng)絡(luò)插件.
CRI主要定義兩個(gè)接口, ImageService和RuntimeService,如下圖
- ImageService:負(fù)責(zé)鏡像的生命管理周期
- 查詢鏡像列表
- 拉取鏡像到本地
- 查詢鏡像狀態(tài)
- 刪除本地鏡像
- 查詢鏡像占用空間
- RuntimeService:負(fù)責(zé)管理Pod和容器的生命周期
- PodSandbox 的管理接口
PodSandbox是對(duì)kubernete Pod的抽象,用來給容器提供一個(gè)隔離的環(huán)境(比如掛載到相同的cgroup下面)并提供網(wǎng)絡(luò)等共享的命名空間.PodSandbox通常對(duì)應(yīng)到一個(gè)Pause容器或者一臺(tái)虛擬機(jī) - Container 的管理接口
在指定的 PodSandbox 中創(chuàng)建袋哼、啟動(dòng)闸衫、停止和刪除容器。 - Streaming API接口
包括Exec弟翘、Attach和PortForward 等三個(gè)和容器進(jìn)行數(shù)據(jù)交互的接口,這三個(gè)接口返回的是運(yùn)行時(shí)Streaming Server的URL,而不是直接跟容器交互 - 狀態(tài)接口
包括查詢API版本和查詢運(yùn)行時(shí)狀態(tài)
- PodSandbox 的管理接口
- cri-o:同時(shí)兼容OCI和CRI的容器運(yùn)行時(shí)
- cri-containerd:基于Containerd的Kubernetes CRI 實(shí)現(xiàn)
- rkt:由CoreOS主推的用來跟docker抗衡的容器運(yùn)行時(shí)
- docker:kuberentes最初就開始支持的容器運(yùn)行時(shí)稀余,目前還沒完全從kubelet中解耦趋翻,docker公司同時(shí)推廣了OCI標(biāo)準(zhǔn)
- Kata Containers:符合OCI規(guī)范同時(shí)兼容CRI
- gVisor:由谷歌推出的容器運(yùn)行時(shí)沙箱(Experimental)
容器生態(tài)可以下面的三層抽象:
Orchestration API -> Container API -> Kernel API
- Orchestration API: kubernetes API標(biāo)準(zhǔn)就是這層的標(biāo)準(zhǔn),無可非議
- Container API: 標(biāo)準(zhǔn)就是CRI
- Kernel API: 標(biāo)準(zhǔn)就是OCI
參考資料
- https://www.opencontainers.org/
- https://blog.docker.com/2017/07/demystifying-open-container-initiative-oci-specifications/
- https://github.com/kubernetes/community/blob/master/contributors/devel/sig-node/container-runtime-interface.md
- https://searchitoperations.techtarget.com/definition/Open-Container-Initiative
- https://github.com/opencontainers/
- https://github.com/containerd/containerd
- https://stackoverflow.com/questions/31213126/libcontainer-vs-docker-vs-ocf-vs-runc
- https://aleiwu.com/post/cncf-runtime-landscape/
- https://groups.google.com/forum/#!topic/docker-dev/zaZFlvIx1_k
- https://feisky.xyz/posts/kubernetes-container-runtime/