Kubernetes為什么要棄用Docker?
圖片來(lái)自 Pexels
然而在 2020 年 12 月,Kubernetes 社區(qū)決定著手移除倉(cāng)庫(kù)中 Dockershim 相關(guān)代碼,這對(duì)于 Kubernetes 和 Docker 兩個(gè)社區(qū)來(lái)說(shuō)都意義重大。
圖 1:Dockershim
相信大多數(shù)的開(kāi)發(fā)者都聽(tīng)說(shuō)過(guò) Kubernetes 和 Docker,也知道我們可以使用 Kubernetes 管理 Docker 容器,但是可能沒(méi)有聽(tīng)說(shuō)過(guò) Dockershim,即 Docker 墊片。
如上圖所示,Kubernetes 中的節(jié)點(diǎn)代理 Kubelet 為了訪問(wèn) Docker 提供的服務(wù)需要先經(jīng)過(guò)社區(qū)維護(hù)的 Dockershim,Dockershim 會(huì)將請(qǐng)求轉(zhuǎn)發(fā)給管理容器的 Docker 服務(wù)。
其實(shí)從上面的架構(gòu)圖中,我們就能猜測(cè)出 Kubernetes 社區(qū)從代碼倉(cāng)庫(kù)移除 Dockershim 的原因:
- Kubernetes 引入容器運(yùn)行時(shí)接口(Container Runtime Interface、CRI)隔離不同容器運(yùn)行時(shí)的實(shí)現(xiàn)機(jī)制,容器編排系統(tǒng)不應(yīng)該依賴于某個(gè)具體的運(yùn)行時(shí)實(shí)現(xiàn)。
- Docker 沒(méi)有支持也不打算支持 Kubernetes 的 CRI 接口,需要 Kubernetes 社區(qū)在倉(cāng)庫(kù)中維護(hù) Dockershim。
可擴(kuò)展性
Kubernetes 通過(guò)引入新的容器運(yùn)行時(shí)接口將容器管理與具體的運(yùn)行時(shí)解耦,不再依賴于某個(gè)具體的運(yùn)行時(shí)實(shí)現(xiàn)。
很多開(kāi)源項(xiàng)目在早期為了降低用戶的使用成本,都會(huì)提供開(kāi)箱即用的體驗(yàn),而隨著用戶群體的擴(kuò)大,為了滿足更多定制化的需求、提供更強(qiáng)的可擴(kuò)展性,會(huì)引入更多的接口。
Kubernetes 通過(guò)下面的一系列接口為不同模塊提供了擴(kuò)展性:
圖 2:Kubernetes 接口和可擴(kuò)展性
Kubernetes 在較早期的版本中就引入了 CRD、CNI、CRI 和 CSI 等接口,只有用于擴(kuò)展調(diào)度器的調(diào)度框架是 Kubernetes 中比較新的特性。
我們?cè)谶@里就不展開(kāi)分析其他的接口和擴(kuò)展了,簡(jiǎn)單介紹一下容器運(yùn)行時(shí)接口。
Kubernetes 早在 1.3 就在代碼倉(cāng)庫(kù)中同時(shí)支持了 rkt 和 Docker 兩種運(yùn)行時(shí)。
但是這些代碼為 Kubelet 組件的維護(hù)帶來(lái)了很大的困難,不僅需要維護(hù)不同的運(yùn)行時(shí),接入新的運(yùn)行時(shí)也很困難。
容器運(yùn)行時(shí)接口(Container Runtime Interface、CRI)是 Kubernetes 在 1.5 中引入的新接口,Kubelet 可以通過(guò)這個(gè)新接口使用各種各樣的容器運(yùn)行時(shí)。
其實(shí) CRI 的發(fā)布就意味著 Kubernetes 一定會(huì)將 Dockershim 的代碼從倉(cāng)庫(kù)中移除。
CRI 是一系列用于管理容器運(yùn)行時(shí)和鏡像的 gRPC 接口,我們能在它的定義中找到 RuntimeService 和 ImageService 兩個(gè)服務(wù)。
它們的名字很好地解釋了各自的作用:
- service RuntimeService {
- rpc Version(VersionRequest) returns (VersionResponse) {}
- rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse) {}
- rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse) {}
- rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse) {}
- rpc PodSandboxStatus(PodSandboxStatusRequest) returns (PodSandboxStatusResponse) {}
- rpc ListPodSandbox(ListPodSandboxRequest) returns (ListPodSandboxResponse) {}
- rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {}
- rpc StartContainer(StartContainerRequest) returns (StartContainerResponse) {}
- rpc StopContainer(StopContainerRequest) returns (StopContainerResponse) {}
- rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse) {}
- rpc ListContainers(ListContainersRequest) returns (ListContainersResponse) {}
- rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse) {}
- rpc UpdateContainerResources(UpdateContainerResourcesRequest) returns (UpdateContainerResourcesResponse) {}
- rpc ReopenContainerLog(ReopenContainerLogRequest) returns (ReopenContainerLogResponse) {}
- ...
- }
- service ImageService {
- rpc ListImages(ListImagesRequest) returns (ListImagesResponse) {}
- rpc ImageStatus(ImageStatusRequest) returns (ImageStatusResponse) {}
- rpc PullImage(PullImageRequest) returns (PullImageResponse) {}
- rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse) {}
- rpc ImageFsInfo(ImageFsInfoRequest) returns (ImageFsInfoResponse) {}
- }
對(duì) Kubernetes 稍有了解的人都能從上面的定義中找到一些熟悉的方法,它們都是容器運(yùn)行時(shí)需要暴露給 Kubelet 的接口。
Kubernetes 將 CRI 墊片實(shí)現(xiàn)成 gRPC 服務(wù)器與 Kubelet 中的客戶端通信,所有的請(qǐng)求都會(huì)被轉(zhuǎn)發(fā)給容器運(yùn)行時(shí)處理。
圖 3:Kubernetes 和 CRI
Kubernetes 中的聲明式接口非常常見(jiàn),作為聲明式接口的擁躉,CRI 沒(méi)有使用聲明式的接口是一件聽(tīng)起來(lái)『非常怪異』的事情。
不過(guò) Kubernetes 社區(qū)考慮過(guò)讓容器運(yùn)行時(shí)重用 Pod 資源,這樣容器運(yùn)行時(shí)可以實(shí)現(xiàn)不同的控制邏輯來(lái)管理容器,能夠極大地簡(jiǎn)化 Kubelet 和容器運(yùn)行時(shí)之間的接口。
但是社區(qū)出于以下兩點(diǎn)考慮,最終沒(méi)有選擇聲明式的接口:
- 所有的運(yùn)行時(shí)都需要重新實(shí)現(xiàn)相同的邏輯支持很多 Pod 級(jí)別的功能和機(jī)制。
- Pod 的定義在 CRI 設(shè)計(jì)時(shí)演進(jìn)地非常快,初始化容器等功能都需要運(yùn)行時(shí)的配合。
雖然社區(qū)最終為 CRI 選擇了命令式的接口,但是 Kubelet 仍然會(huì)保證 Pod 的狀態(tài)會(huì)不斷地向期望狀態(tài)遷移。
不兼容接口
與容器運(yùn)行時(shí)相比,Docker 更像是一個(gè)復(fù)雜的開(kāi)發(fā)者工具,它提供了從構(gòu)建到運(yùn)行的全套功能。
開(kāi)發(fā)者可以很快地上手 Docker 并在本地運(yùn)行并管理一些 Docker 容器,然而在集群中運(yùn)行的容器運(yùn)行時(shí)往往不需要這么復(fù)雜的功能,Kubernetes 需要的只是 CRI 中定義的那些接口。
圖 4:Docker & CRI
Docker 的官方文檔加起來(lái)可能有一本書(shū)的厚度,相信沒(méi)有任何開(kāi)發(fā)者可以熟練運(yùn)用 Docker 提供的全部功能。
而作為開(kāi)發(fā)者工具,雖然 Docker 中包含 CRI 需要的所有功能,但是都需要實(shí)現(xiàn)一層包裝以兼容 CRI。
除此之外,社區(qū)提出的很多新功能都沒(méi)有辦法在 Dockershim 中實(shí)現(xiàn),例如 cgroups v2 以及用戶命名空間。
Kubernetes 作為比較松散的開(kāi)源社區(qū),每個(gè)成員尤其是各個(gè) SIG 的成員都只會(huì)在開(kāi)源社區(qū)上花費(fèi)有限的時(shí)間。
而維護(hù) Kubelet 的 sig-node 又尤其繁忙,很多新的功能都因?yàn)榫S護(hù)者沒(méi)有足夠的精力而被擱置。
所以既然 Docker 社區(qū)看起來(lái)沒(méi)有打算支持 Kubernetes 的 CRI 接口,維護(hù) Dockershim 又需要花費(fèi)很多精力,那么我們就能理解為什么 Kubernetes 會(huì)移除 Dockershim 了。
總結(jié)
今天的 Kubernetes 已經(jīng)是非常成熟的項(xiàng)目,它的關(guān)注點(diǎn)也逐漸從提供更完善的功能轉(zhuǎn)變到提供更好的擴(kuò)展性,這樣才能滿足不同場(chǎng)景和不同公司定制化的業(yè)務(wù)需求。
Kubernetes 在過(guò)去因?yàn)?Docker 的熱門(mén)而選擇 Docker,而在今天又因?yàn)楦甙旱木S護(hù)成本而放棄 Docker,我們能夠從這個(gè)過(guò)程中體會(huì)到容器領(lǐng)域的發(fā)展和進(jìn)步。
移除 Docker 的種子其實(shí)從 CRI 發(fā)布時(shí)就種下了,Dockershim 一直都是 Kubernetes 為了兼容 Docker 獲得市場(chǎng)采取的臨時(shí)決定。
對(duì)于今天已經(jīng)統(tǒng)治市場(chǎng)的 Kubernetes 來(lái)說(shuō),Docker 的支持顯得非常雞肋,移除代碼也就順理成章了。
我們?cè)谶@里重新回顧一下 Kubernetes 在倉(cāng)庫(kù)中移除 Docker 支持的兩個(gè)原因:
Kubernetes 在早期版本中引入 CRI 擺脫依賴某個(gè)具體的容器運(yùn)行時(shí)依賴,屏蔽底層的諸多實(shí)現(xiàn)細(xì)節(jié),讓 Kubernetes 能夠更關(guān)注容器的編排。
Docker 本身不兼容 CRI 接口,而且官方并沒(méi)有實(shí)現(xiàn) CRI 的打算,同時(shí)也不支持容器的一些新需求,所以 Dockershim 的維護(hù)成為了社區(qū)的想要擺脫負(fù)擔(dān)。
到最后,我們還是來(lái)看一些比較開(kāi)放的相關(guān)問(wèn)題,有興趣的讀者可以仔細(xì)思考一下下面的問(wèn)題:
Kubernetes 中還有哪些模塊提供良好的擴(kuò)展性?
除了文中提到的 CRI-O、Containerd,還有哪些支持 CRI 的容器運(yùn)行時(shí)?
作者:Draveness
編輯:陶家龍
出處:轉(zhuǎn)載自公眾號(hào)真沒(méi)什么邏輯(ID:draveness)