實操|如何將 Containerd 用作 Kubernetes runtime

又拍雲發表於2021-01-20

日前專為開發者提供技術分享的又拍雲 OpenTalk 公開課邀請了網易有道資深運維開發工程師張晉濤,直播分享《Containerd 上手實踐 》,詳細介紹 Containerd 的發展歷程、主要特性,以及如何將其作為 Kubernetes runtime 的上手實踐。以下是直播內容整理

關於作者:張晉濤,現就職於網易有道, 對 Docker、Kubernetes 及相關生態有大量實踐及深入原始碼的研究,《Docker 核心知識必知必會》專欄作者。PS 講師長期堅持更新 K8S 生態週報,如有興趣可訂閱其公眾號【MoeLove】。

大家好,今天分享的內容將會從 Kubernetes 宣佈棄用 dockershim 說起,介紹 Containerd 相關特性,分享如何將 Containerd 用作 Kubernetes 的 runtime。

Kubernetes 宣佈棄用 dockershim

很多媒體將該事件宣稱為 Kubernetes 宣佈棄用 Docker,其實這是一種誤導。那麼應該如何正確的去看待呢?首先是瞭解整個事情的前因後果,得需要知道 dockershim 是什麼。

dockershim

dockershim 是 Kubernetes 的一個元件,主要目的是為了通過 CRI 操作 Docker。Docker在 2013 年就出現了,2014 年 Kubernetes 釋出並預設使用 Docker 作為容器執行時,而 dockershim首次正式出現是在 2016 年。Docker 在建立之初並沒有考慮到容器編排或者是考慮 Kubernetes,但 Kubernetes 在建立之初便採用Docker 作為它的預設容器進行時,後續程式碼當中包含了很多對 Docker 相關的操作邏輯。後期 Kubernetes 為了能夠做解耦,相容更多的容器進行時,將操作 Docker 相關邏輯整體獨立起來組成了 dockershim。

Container Runtime Interface

再說 CRI(Container Runtime Interface)即容器執行時介面,該概念是由Kubernetes 提出並在 2016 年底開始應用,其主要目標是增強 Kubernetes 的可擴充套件性,可以不固定、不捆綁某一個容器執行時,實現可插拔式的容器進行時。比如可以使用 Docker 為容器執行時也可以使用其他的例如 rkt,並且希望通過開放 CRI 這個統一的介面來提高程式碼的可維護性,而不是需要支援 Docker 時就對 Docker 進行適配,需要支援另一個執行時就得對其做相關的適配。它希望是任何一個成為 Kubernetes 的容器執行時都遵守 CRI 統一的介面與規範,實現了 CRI 就可以作為 Kubernetes 的執行時,並不需要關注具體是什麼。

為什麼要棄用 dockershim

dockershim 的目的是為了 Kubernetes 通過 CRI 操作 Docker,所以Kubernetes 任何的功能變動或 Docker 有任何的功能特性變更,dockershim 程式碼必須加以改動保證能夠支援變更。另外一個原因是隨著容器技術的推進,容器執行時已經多種多樣了,比如本次分享的主角 Containerd,還有 cri-o 以及 rkt 的容器執行時,不過這個 rkt 容器執行時的專案已經不維護了。

此外,原先 Kubernetes 需要去呼叫 dockershim 跟 Docker 做溝通,Docker 的底層執行時是 containerd,可以發現最終都是要呼叫 containerd,並且 containerd 自身也是可以支援 CRI 的。那為什麼要先繞過一層 Docker 呢?是不是可以直接通過 CRI 跟 Containerd 進行互動呢?這也就造成了現在 Kubernetes 社群希望棄用 dockershim。

棄用 dockershim 的影響

終端⽤⼾⽆任何影響。這裡指的是使用來自雲廠商/使用別人提供的Kubernetes 叢集的終端使用者,他們不需要關注叢集本身的容器執行時到底是什麼,因為和你互動的都是 Kubernetes 自身的 CRI,而任何一個可以作為 Kubernetes 底層容器執行時的東西都必須是相容 CRI 介面的,上層就已經遮蔽掉這個細節了。

對負責維護 Kubernetes 叢集的工作人員有一定影響。當升級 Kubernetes 叢集版本時,需要考慮是否切換容器進行時。如果目前在用最新版本 Kubernetes V1.20,並且使用 Docker 作為容器執行時,要考慮它能否正常工作。其實是可以正常工作的,只是在啟動 Kubernetes 的時候會發現一條日誌,提醒你當前使用的容器執行時 Docker 已經不再被 Kubernetes 支援,因為已經準備棄用dockershim,因此會有這個提醒。

Kubernetes 社群計劃在 2021 年將dockershim 正式移除。換個角度考慮,既然社群不想在 Kubernetes 原始碼當中維護 dockershim 了,那是不是可以把 dockershim 元件給單獨的拿出來呢?答案是可以的,現在 Mirantis 和 Docker 已經決定之後共同合作維護 dockershim 元件。此外,還可以通過樹外的 dockershim 獨立元件,繼續使用 Docker 作為容器執行時,並且使用這種方式只需要做一些簡單的配置,把原先使用內建的Kubernetes 自身攜帶的 dockershim 元件,改成使用一個獨立的 dockershim 元件,本身變動很小。

那麼 Docker 到底還能否使用呢?在我看來,毋庸置疑,Docker 仍然是現階段容器構建和執行的最佳選擇。

快速瞭解 Containerd

Containerd 是中間層的容器執行時。它構建在平臺之下,作為平臺下層的一個容器執行時,但又比最底層的容器執行時像 runc、gVisor 要高一點,所以被稱為中間層的容器執行時。除此之外也可稱作為資源管理器,可以用來管理容器的程式、映象以及管理檔案系統的快照,還有後設資料和依賴的管理。既然它可以作為一個資源管理器來使用,如果想要在此之上構建一個屬於自己的容器平臺就會很方便。

Containerd 是由 Docker 公司建立,並且在 2017年捐贈給了 CNCF,2019 年 Containerd 從 CNCF 正式畢業。Containerd 專案一開始的目標是用來管理容器的程式,之後逐步變更成為一個完整的容器執行時,是 Docker 的底層容器執行時。需要說明的是,containerd 是可以拋開 Docker 與 Kubernetes 自身獨立工作的。

Containerd 與 CRI

Containerd 在之前的版本中考慮到了 CRI,但它是將CRI 作為獨立的程式存在的。在上圖中看到,CRI-Containerd 其實是一個獨立元件,Kubernetes 通過 CRI 介面呼叫 CRI-Containerd,再由這個元件去呼叫 containerd。在 Containerd1.1 版本之後對該特性做了重新的設計,它將 CRI 的支援通過外掛化的方式來實現,Kubernetes 通過 CRI 介面呼叫的其實是 Containerd 當中 CNI 的外掛,以此來達到通訊的目的,呼叫鏈更少更短了。

Containerd 的特性

  • 支援 OCI 映象規範,即前文所提到的 runc

  • 支援 OCI 執行時規範。Docker 引導了 OCI 組織的成立,該組織主要有兩個規範:映象規範與執行時規範。這兩個規範在 Docker 成立時把 Docker 映象規範與底層容器執行時規範都給捐贈出來作為它的初始工作

  • 支援映象的 push/pull 作用

  • 支援容器網路管理。因為可以啟動和執行容器,容器啟動後支援相互之間的訪問,或彼此之間網路的隔離,所以需要支援容器網路的管理

  • 儲存支援多租戶。Containerd 的相關操作有通過 namespace 來做隔離的,可以指定不同的 namespace 來實現,它預設的 namespace 叫 default,在 default 的 namespace下面下載多個映象。但是在其他的 namespace 下看不到這些映象,也用不到,以此來達到多租戶的隔離

  • 支援容器執行時和容器的生命週期管理

  • 支援管理網路名稱空間容器以加入現有名稱空間,可以讓某一個容器加入到現有的 namespace 當中

Containerd 的整體架構

上圖是 Containerd 整體的架構。由下往上,Containerd支援的作業系統和架構有 Linux、Windows 以及像 ARM 的一些平臺。在這些底層的作業系統之上執行的就是底層容器執行時,其中有上文提到的runc、gVisor 等。在底層容器執行時之上的是Containerd 相關的元件,比如 Containerd 的 runtime、core、API、backend、store 還有metadata 等等。構築在 Containerd 元件之上以及跟這些元件做互動的都是 Containerd 的 client,Kubernetes 跟 Containerd 通過 CRI 做互動時,本身也作為 Containerd 的一個 client。Containerd 本身有提供了一個 CRI,叫 ctr,不過這個命令列工具並不是很好用。

在這些元件之上就是真正的平臺,Google Cloud、Docker、IBM、阿里雲、微軟雲還有RANCHER等等都是,這些平臺目前都已經支援 containerd, 並且有些已經作為自己的預設容器執行時了。

Containerd 主要功能與上手實踐

映象管理

首先是上文中頻繁提到的映象管理。具體操作是需要通過一個 client 去跟 Containerd 做互動。如圖中所示,這裡選擇了ctr 的命令列工具。ctr指定一個address 引數跟 Containerd互動,它是在後臺持續執行的一個服務,需要指定它的地址。圖中是通過 pull 一個 redis alpine linux 的映象,接下來通過 image ls就可以看到已經成功 pull 下來的映象。

容器管理

作為一個容器執行時對容器進行管理是必不可少的功能。同樣的通過 -a 引數來指定 address 與Containerd 進行通訊,通過 container create+映象名稱+容器名稱來建立一個容器,通過 container ls 可以看到剛才建立的容器。需要注意的是,最後一列叫做 runtime,它的 runtime 叫做 io.containerd.runc.v2,表明是 v2 版本的 Containerd API。

在 Containerd 中通過 container create 建立出來的容器其實並不管用,還需要讓其執行起來。這時通常會把它當作一個 task,對它執行 task start,就可以把剛才建立的映象跑起來了。通過 task ls 就可以看到名叫 redis 的 Containerd,其中有一個正在執行的程式,並且展現出了程式號。

名稱空間

需要說明的是,可以通過 -n 來指定一個預設的叫做 default 的名稱空間,而後通過 task ls 就看到剛才啟動容器的程式,它其實是在執行中的。如果把 namespace 換一個,比如圖中的 moby 就是 Docker 專案當前使用的 namespace 名稱,Docker 在使用 Containerd 作為容器執行時的時候,會預設使用它。

繼續往下看,通過 ctr -n 指定使用 moby 名稱空間,-a 引數指定containerd 的地址,然後 task ls 來看看moby 專案當中到底執行著什麼。可以看到有一條記錄是正在執行當中的。這條記錄如何和 Docker 當中的容器或任務做匹配比較呢?一個簡單的辦法就是通過 docker ps --no-trunc-- format 跟容器完整的 ID,然後 grep 就可以看到剛才通過 ctr 命令得到的ID 了。

需要注意的是,如果使用 Containerd 作為 Kubernetes 的容器執行時,那麼它的 namespace 叫 k8s.io。到這裡可能有些人已經發現,Containerd 作為 Docker 的執行時可以使用不同的名稱空間,比如 moby。用作 Kubernetes 容器執行時也可以使用不同的名稱空間,比如 k8s.io。那是否存在一種辦法可以讓平臺當中既有 Kubernetes 又有 Docker,還有 Containerd 呢?答案是肯定的,直接將其全部裝到一起,但不配置 Docker 作為容器執行時,先觀察一段時間看看,這也是一種辦法。

Containerd 用作 Kubernetes 的 runtime

上圖是 Containerd 用作 Kubernetes 的 runtime 整個流程圖。Kubernetes 通過 CRI 介面,呼叫到 CRI plugin,plugin 是Containerd 一個內建的外掛,其中包含了最主要的兩部分:一是 image service,包含了映象服務相關的;二是 runtime service,即執行時的服務。如果在 containerd 當中部署了一個專案或服務,它首先會排程到某一臺機器,這臺機器上的 Kubernetes 就會開始工作,它會查詢服務、需要哪些映象,而後把相關的映象讓執行時給拉下來,再去啟動對應的 pod 或者相關的容器。

其次它還會跟 CNI 做互動。CNI 即 Container Network Interface,是Kubernetes 提供的一個容器網路介面)。主要注意的是,互動過程中可能會提前建立出來 pause的 container,是一個佔位的過程,這裡先不對此做更深入的介紹。

當 Containerd 作為 Kubernetes 的容器執行時,配置相對很簡單:通過 containerd config default命令可以直接查詢到 Containerd 完整的預設配置,下圖中可以看到主要是配置 CRI。所以在這裡的配置檔案當中,通過 plugins.io.containerd.grpc.vi.cri 對其進行配置,首先是 default-runtime,其次配置一個 runtime.runc。

這裡簡單介紹配置 runc 需要注意的引數。比如 io.containerd.runc.v2 需要配置runtime.type;涉及配置與 runc 相關的一些配置會包含一些 CNI 的配置、目錄之類的,具體的配置上圖中已經展示了。總而言之如果想要提起來一個服務、一個 pod 或是 container,要注意都是需要配置的。它都會有一個 pause的映象,即 sandbox_image,可以從中指定一個預設的映象。當然也可以通過此處換源,加快國內環境下的拉取速度。

最後還有些其他的資源,如本人長期參與的一個專案 KIND( Kubernetes in docker)。這個專案相當於是使用docker 容器作為不同的 node,可以把這些 node 組成一個叢集網路,搭建一套 Kubernetes。而這個叢集使用的容器執行時就是 containerd,雖然一開始使用的是 Docker,但後期逐步都將其替換成了 containerd,類似的還有包括 K3C、K3S,效果都是差不多的。

推薦閱讀

雲原生網路代理(MOSN)的進化之路

聊聊 HTTP 常見的請求方式

相關文章