鯨品堂|從Docker和Kubernetes看Containerd

2023-06-06 108

導讀:在學習Containerd之前,我們需要去了解Docker與Kubernetes這兩個使用Containerd最多的技術,也需要明白什么是容器,什么是容器運行時,以及里面涉及的組件,這些組件是用來干什么的,及容器領域的概念,如libcontainer、runc、OCI、CRI、shim等。


什么是容器?


在 Linux 內核中,容器不是一類對象。容器本質上由幾個底層的內核原語組成:namespace(允許你跟誰交談),cgroup(允許使用的資源量),和 LSM(Linux 安全模塊 —— 允許你做的事情)。這些湊在一起能夠為我們的進程設置安全、隔離和可計量的執行環境。


每次創建隔離進程時,都不需要手動隔離、自定義命名空間等,把這些組件捆綁在一起,我們稱之為容器。但是每次手動執行所有的操作將很麻煩,因此出現了容器運行時工具,它能將這些部分組合成一個隔離的、安全的執行環境變得很容易,讓我們能以重復的方式部署。


什么是容器運行時?


容器運行時是掌控容器運行的整個生命周期,以docker為例,其主要提供功能如下:


制定容器鏡像格式


構建容器鏡像


管理容器鏡像


管理容器實例


運行容器


實現容器鏡像共享


這些功能均可由小的組件單獨實現,因此容器運行時是運行和管理容器運行所需要的組件。


隨著容器運行時的發展,Docker公司與CoreOS和Google共同創建了OCI(開放容器標準),并提供了兩種規范:


運行時規范:該規范目標是定義容器的配置、執行環境和生命周期


鏡像規范:該規范的目標創建可互操作的工具,用于構建、傳輸和準備要運行的容器鏡像


在runc作為了OCI的一種實現參考之后,各種運行時工具和庫也慢慢出現。而根據這些運行時的功能不同,比如有的只運行容器(runc,lxc),有的還可以對鏡像進行管理(Containerd,cri-o),因此通俗的分為高級運行時(high-level)和低級運行時(low-level)。



低級運行時:側重于運行容器,為容器設置namespace和cgroup

  • lxc

  • rkt

  • runc

  • kata

  • gVisor


高級運行時:包含更多上層功能,如為開發人員提供API,鏡像存儲管理等

  • Containerd

  • cri-o

  • docker


Docker


Docker是第一個流行的容器技術,最初Docker使用的是LXC(0.7版本之前)但是隔離的層次不完善,后來Docker開發了libcontainer(0.7~1.10版本),最后演變為runc和Containerd(Docker被逼無奈將libcontainer捐獻出來改名為runc)

從1.11版本之后,Docker容器運行開始通過集成Containerd和runc等多個組件完成?,F在的架構中,Containerd負責容器的生命周期管理,提供了在一個節點上執行容器和管理鏡像的最小功能集,并向上為Docker Daemon提供grpc接口。


圖片關鍵詞


當請求創建一個容器時,Docker Daemon并不會直接去創建,而且請求containerd創建容器,containerd在收到請求后,也不會去直接操作容器,而是創建containerd-shim的進程去操作容器(因為需要一個父進程去做狀態收集、維持stdin、stdout、stderr打開等工作,如果父進程是contaienrd,當containerd掛掉時,整個宿主機的容器都會退出),而containerd-shim會去調用runc來啟動容器,runc在啟動完容器后會直接退出,此時containerd-shim成為容器的父進程,負責收集容器進程的狀態上報給containerd,并在容器中 pid 為 1 的進程退出后接管容器中的子進程進行清,確保不會出現僵尸進程。


runc創建容器則是根據上述的OCI去做操作,例如namespaces、cgroups的配置,以及掛載root文件系統等操作。


Docker 將容器操作都遷移到 containerd 中去是因為當時做 Swarm,想要進軍 PaaS 市場,做了這個架構切分,讓 Docker Daemon 專門去負責上層的封裝編排,當然后面的結果我們知道 Swarm 在 Kubernetes 面前是慘敗,然后 Docker 公司就把 containerd 項目捐獻給了 CNCF 基金會,這個也是現在的 Docker 架構。


Kubernetes


2014年Kubernetes誕生,由于當時Docker很流行,因此很自然的選擇了Docker,在CRI出現之前,Kubelet通過內嵌的dockershim操作Docker API來操作容器,進而達到一個面向終態的效果。

圖片關鍵詞

而隨著Docker將Containerd開源出以及更多的容器運行時出來,Kubernetes為了精簡和支持更多的容器運行時,Google和Redhat推出了CRI標準,用于Kubernetes平臺和容器運行時解耦CRI(容器運行時接口)。


CRI本質上是Kubernetes定義的一組與容器運行時進行交互的接口,因此容器運行時只要實現了CRI,就可以對接到Kubernetes平臺中。但是當時Kubernetes的地位不高,所以一些容器運行時不會去實現CRI接口,于是就出現了shim,shim的職責是作為適配器將各種容器運行時本身的接口適配到 Kubernetes 的 CRI 接口上,上圖的dockershim就是Kubernetes對接Docker到CRI接口的實現。


圖片關鍵詞


在引入CRI后,Kubelet的架構如圖所示:圖片關鍵詞

圖片關鍵詞


通過觀察分析能夠發現,Kubernetes使用Docker的調用鏈比較長,而Docker的一些功能對于Kubernetes來說又不需要,所以自然的將容器運行時切換到Containerd。切換到Containerd后取消掉了中間環節,但操作體驗和以前一樣,在Containerd1.0時,對CRI的適配是通過一個單獨的CRI-Containerd實現(因為最開始containerd還會去適配其他系統,所以沒有直接實現CRI)。到了Containerd1.1版本后就去掉了CRI-Containerd,直接把適配邏輯作為插件集成到Containerd主進程中,變得更加簡潔。


圖片關鍵詞

 圖片關鍵詞

CRI的接口主要分為兩類:


ImageService:鏡像相關的操作


RuntimeService:容器和Sandbox運行時管理


RuntimeService 中 CRI 設計的一個重要原則,就是確保這個接口本身,只關注容器,不關注 Pod,這么做是因為:


Pod 是 Kubernetes 的編排概念,而不是容器運行時的概念。所以,我們就不能假設所有下層容器項目,都能夠暴露出可以直接映射為 Pod 的 API;


如果 CRI 里引入了關于 Pod 的概念,那么接下來只要 Pod API 對象的字段發生變化,那么 CRI 就很有可能需要變更。而在 Kubernetes 開發的前期,Pod 對象的變化還是比較頻繁的,但對于 CRI 這樣的標準接口來說,這個變更頻率就有點麻煩了。

圖片關鍵詞

雖然 CRI 里還是有一組叫做 RunPodSandbox 的接口。但是,這個 PodSandbox,對應的并不是 Kubernetes 里的 Pod API 對象,而只是抽取了 Pod 里的一部分與容器運行時相關的字段,比如 HostName、DnsConfig、CgroupParent 等。所以說,PodSandbox 這個接口描述的其實是 Kubernetes 將 Pod 這個概念映射到容器運行時層面所需要的字段,或者說是一個 Pod 對象的子集。而創建、管理 Pod 的邏輯則放置在 kubernetes 中,而不是 CRI 要實現的接口中。


隨著 CRI 方案的發展,以及其他容器運行時對 CRI 的支持越來越完善,Kubernetes 社區在2020年7月份就開始著手移除 dockershim 方案了,現在的移除計劃是在 1.20 版本中將 kubelet 中內置的 dockershim 代碼分離,將內置的 dockershim 標記為維護模式,當然這個時候仍然還可以使用 dockershim,目標是在 1.24 版本發布沒有 dockershim 的版本(代碼還在,但是要默認支持開箱即用的 docker 需要自己構建 kubelet,會在某個寬限期過后從 kubelet 中刪除內置的 dockershim 代碼)。


CRI的實現


目前,CRI領域有兩個主要的參與者,一個是Docker的高級運行時Containerd,一個是RedHat專門為Kubernetes設計的運行時CRI-O。



CRI-O

當容器運行時的標準被提出以后,RedHat的一些人開始想他們可以構建一個更簡單的運行時,而且這個運行時僅僅為Kubernetes所用。這樣就有了skunkworks項目,最后定名為 CRI-O, 它實現了一個最小的CRI接口,旨在充當CRI和支持的OCI運行時的輕量級橋梁。

圖片關鍵詞


CONTAINERD

Containerd 是一個工業級標準的容器運行時,它強調簡單性、健壯性和可移植性,可以在宿主機中管理完整的容器生命周期:容器鏡像的傳輸和存儲、容器的執行和管理、存儲和網絡等,主要有以下功能:


管理容器的生命周期(從創建容器到銷毀容器)


拉取/推送容器鏡像


存儲管理(管理鏡像及容器數據的存儲)


調用 runc 運行容器(與 runc 等容器運行時交互)


管理容器網絡接口及網絡(CNI)


Containerd在Docker或者Kunernetes中都是使用最多的運行時,同時也是我們環境中接觸最多的,因此后續著重學習Containerd。


Containerd


Containerd 可用作 Linux 和 Windows 的守護程序,它管理其主機系統完整的容器生命周期,從鏡像傳輸和存儲到容器執行和監測,再到底層存儲到網絡附件等等。圖片關鍵詞


圖片關鍵詞


為了解耦,Containerd 將系統劃分成了不同的組件,每個組件都由一個或多個模塊協作完成(Core 部分),每一種類型的模塊都以插件的形式集成到 Containerd 中,而且插件之間是相互依賴的,例如,上圖中的每一個長虛線的方框都表示一種類型的插件,包括 Service Plugin、Metadata Plugin、GC Plugin、Runtime Plugin 等,其中 Service Plugin 又會依賴 Metadata Plugin、GC Plugin 和 Runtime Plugin。每一個小方框都表示一個細分的插件,例如 Metadata Plugin 依賴 Containers Plugin、Content Plugin 等。比如:


Content Plugin: 提供對鏡像中可尋址內容的訪問,所有不可變的內容都被存儲在這里


Snapshot Plugin: 用來管理容器鏡像的文件系統快照,鏡像中的每一層都會被解壓成文件系統快照,類似于 Docker 中的 graphdriver


總體來看 Containerd 可以分為三個大塊:


Storage 管理鏡像文件的存儲


Metadata 管理鏡像和容器的元數據


Runtime 由Task觸發的運行時

圖片關鍵詞

Containerd被設計成可以很容易的嵌入到更大的系統中,例如Docker使用containerd運行容器,Kubernetes通過CRI使用containerd管理單個 節點上的容器 除了編程方式使用外,它還可以通過命令行使用,但不像docker全面,主要用于調試和學習目的,主要有:


ctr Containerd依據自身開發的命令行工具


nerdctl 與docker命令行風格兼容的命令行工具


crictl K8S根據CRI規范定義的命令行工具

圖片關鍵詞



GRPC API

Containerd通過暴露的gRPC API給外部管理容器,而Containerd中主要提供的API有:圖片關鍵詞

其他還包括events、diffs等,具體見containerd的gRPC API

圖片關鍵詞



創建容器流程

Docker、ctr、nerdctl都是通過Containerd提供的API進行容器的管理,Kubernetes、crictl則是通過CRI接口實現。


使用gRPC API創建容器


分配一個新的讀寫快照(snapshot),使得容器可以存儲持久化數據(為容器創建新快照時,需要提供快照ID以及容器使用的鏡像)


創建一個Container對象,用于分配數據


創建一個Task,用于實際的運行容器(當Task已創建時,意味著命名空間、根文件系統和各種容器級別的設置已被初始化,但容器定義的進程尚未啟動)


在啟動Task之前需要等待Task創建成功,然后再調用Start去啟動Task

圖片關鍵詞


Kubelet創建Pod流程


調用CRI插件,通過RuntimeService創建Pod


CRI調用CNI接口創建和配置Pod的網絡命令空間


CRI調用Containerd內部接口創建特殊的pause容器,并將該容器放入Pod的cgroups和namespace中(使用不同的容器運行時,PodSandbox的實現方式也不一樣,比如使用kata作為runtime,PodSandbox被實現為一個虛擬機;而使用runc作為runtime,PodSandbox就是一個獨立的namespace和cgroups)


調用CRI插件,通過ImageServie拉取應用容器鏡像


如果節點上不存在鏡像,則使用Contianerd拉取鏡像


調用CRI插件,使用RuntimeService創建和啟動應用容器


CRI調用Containerd內部接口創建容器,放到Pod的cgroups和namespace中


圖片關鍵詞


Containerd創建任務流程

上述說的創建容器流程和創建Pod流程都是調用Containerd內部接口的邏輯,實際的過程由Containerd啟動Containerd-shim進程調用runc創建容器,具體步驟如下:


Containerd調用Containerd-shim start 啟動用于創建runc的Containerd-shim,這樣Containerd-shim就與Containerd脫離了關系,重啟Containerd也不會影響Containerd-shim進程


通過ttrpc調用Containerd-shim的Newtask方法,之后調用runc create


再通過ttrpc調用Containerd-shim的Start方法,之后調用runc start啟動pause容器


以同樣的方式啟動Pod中定義的container

圖片關鍵詞圖片關鍵詞


1.   Containerd 被設計成嵌入到一個更大的系統中,而不是直接由開發人員或終端用戶使用

2.   Docker有網絡功能模塊,比如它會創建 docker0 網橋,所以在使用 docker 時可以直接實現端口映射等功能,而這些網絡能力都是 Docker Daemon 實現的。但是Containerd 中不包含相應的網絡功能,想要啟動的容器有網絡能力,需要額外安裝 CNI 相關的工具和插件(bridge、flannel 等)

*Containerd一切皆插件


總結


本文通過引入Docker和Kubernetes的發展介紹容器、容器運行時,將容器領域c常見的概念OCI、CRI、shim、runc,containerd串聯起來,能夠幫組我們進一步理解Docker和Kubernetes背后是怎么創建容器的,以及Containerd的實際運行原理。

參考

  • https://github.com/containerd/containerd

  • https://github.com/kubernetes

  • https://github.com/moby/moby

  • https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2221-remove-dockershim

  • 一文搞懂容器運行時

  • containerd shim原理深入解讀



官方微信公眾號

浩鯨云計算科技股份有限公司 版權所有 2003-2023

蘇ICP備10224443號-6       蘇公網安備 32011402011374號

亚洲精品免费视频_热99re6久精品国产首页青柠_精品国产专区91在线_亚洲美洲欧洲偷拍片区