在寫“K8s”系列文章的過程中,很多讀者留言詢問 K8s 棄用 Docker 的事,擔心現在學習 Docker 是否還值得,是不是該切換到 containerd 或其他運行時。
這些懷疑有一定的道理。兩年前,K8s 發布“棄用 Docker”的消息時,確實在社區引起了“軒然大波”,影響甚至蔓延到了社區之外,K8s 不得不寫了好幾篇博客來重復解釋原因。
兩年過去了,雖然 K8s 1.24 已經實現了“棄用 Docker”的目標,但很多人似乎對這一點還不是很清楚。所以本篇文章就來聊聊這個話題。

CRI(容器運行時接口)
要理解 K8s 為何“棄用 Docker”,我們得回顧一下 K8s 的發展史。
2014 年,Docker 正處于鼎盛時期,而 K8s 剛剛誕生。雖然它得到了 google 和 Borg 的支持,但它還是比較新的。
因此,K8s 首先選擇支持 Docker 。
快進到 2016 年,CNCF 成立一年,K8s 也發布了 1.0 版本,可以正式用于生產環境。這些都表明 K8s 已經長大了。
于是宣布加入 CNCF,成為第一個 CNCF 托管項目。它想利用基金會的力量聯合其他廠商來“打倒”Docker。
在 2016 年底的 1.5 版本中,K8s 引入了新的接口標準:CRI:Container Runtime Interface 容器運行時接口。
CRI 使用ProtoBufferandgPRC來指定kubelet應該如何調用容器運行時來管理容器和鏡像,但這是一組與以前的 Docker 調用完全不兼容的新接口。
顯然它不想再和 Docker 綁定,在底層允許訪問其他容器技術(如 rkt、kata 等),可以隨時“踢開” Docker。
但此時 Docker 已經非常成熟,市場的慣性也非常強。各大云廠商不可能一下子全部替換掉 Docker。
因此,K8s 只能同時提供一種“折中”的方案,在kubelet和 Docker 之間增加一個“適配器”,將 Docker 的接口轉換為 CRI 兼容的接口:
因為這個“適配器”夾在kubeletDocker 和 Docker 之間,所以形象地稱為“shim”,意思是“墊片”。
有了 CRI 和 shim,雖然 K8s 仍然使用 Docker 作為底層運行時,但它也具備了與 Docker 解耦的條件,從而拉開了“棄用 Docker”大戲的帷幕。
Containerd
面對挑戰,Docker 采取了“斷臂求生”的策略,推動自身重構,將原有單一架構的 Docker Engine 拆分成多個模塊,其中 Docker daemon 部分捐贈給 CNCF,containerd 形成。
作為 CNCF 的托管項目,containerd 必須符合 CRI 標準。但是由于很多原因,Docker 只是 containerd 在 Docker Engine 中調用,對外的接口保持不變,也就是說不兼容 CRI。
由于 Docker 的“固執”,此時 K8s 中有兩條調用鏈:
使用 CRI 接口調用 dockershim,然后 dockershim 調用 Docker,Docker 再去 containerd 操作容器。
使用 CRI 接口直接調用 containerd 操作容器。

顯然,因為 containerd 是用來管理容器的,所以這兩個調用鏈的最終效果是完全一樣的,但是第二種方法去掉了 dockershim 和 Docker Engine 這兩個環節,更加簡潔明了,性能也更好。
2018 年 Kube.NETes 1.10 發布時,containerd 也更新到 1.1 版本,正式與 Kubernetes 集成,并發表[博文](https://kubernetes.io/blog/2018/05/24/kubernetes-containerd-integration- gos-ga/ "博文")顯示一些性能測試數據:

從這些數據可以看出,相比當時的 Docker 18.03,containerd1.1Pod 啟動延遲降低了 20% 左右,CPU 使用率降低了 68%,內存使用率降低了 12%,這樣可觀的性能提升對云廠商來說是非常有誘惑力的。
棄用Docker
2020 年,K8s 1.20 終于正式向 Docker “宣戰”:kubelet將棄用 Docker 支持,并將在未來的版本中完全移除。
但由于 Docker 幾乎已經成為容器技術的代名詞,而且 K8s 已經使用 Docker 多年,該公告在傳播時很快“變味了”,“kubelet 將棄用 Docker 支持”被簡化為更吸人眼球的東西 “K8s 將棄用”Docker”。
這自然引起了 IT 界的恐慌,“不明真相的群眾”紛紛表示震驚:
用了這么久的 Docker 突然不能用了。
為什么 K8s 會這樣對待 Docker?
之前對 Docker 的投資會歸零嗎?現有的大量鏡像怎么辦?
其實,如果你了解了上面提到的這兩個項目CRI,containerd你就會知道,K8s 的這一舉動并不奇怪,一切都是“自然”的:其實只是“棄用 dockershim ”,也就是dockershim搬出kubelet,并不是“棄用 Docker”的軟件產品。
因此,“棄用 Docker”對 K8s 和 Docker 的影響不大,因為它們都已經將底層改為開源containerd,原有的 Docker 鏡像和容器仍然可以正常運行。唯一的變化是K8s繞過了Docker,直接調用Docker內部的containerd。

然而,還是會有一些影響。如果K8s直接使用containerd來操作容器,那么它就是一個獨立于Docker的工作環境,兩者都無法訪問對方管理的容器和鏡像。換句話說,使用docker ps命令將不會看到K8s中運行的容器。
這對一些人來說可能需要花一點時間來適應并使用新工具crictl,但用于查看容器和鏡像的子命令仍然是相同的,例如ps,images等,不難適應(如果你一直在用kubectl管理K8s,這個沒有影響)。
K8s 原本計劃用一年時間完成“棄用 Docker”的工作,但它確實低估了 Docker 的基礎。1.23版本還是沒能移除dockershim,只好延期半年。最后,1.24版本從kubelet中刪除了dockershim的代碼。
從此,Kubernetes 與 Docker 徹底“分道揚鑣”。
Docker 的未來
那么,Docker 的未來會怎樣呢?云原生時代就沒有它的立足之地嗎?這個問題的答案顯然是否定的。
作為容器技術的奠基人,沒有人可以質疑 Docker 的歷史地位。雖然 K8s 默認不再綁定 Docker,但 Docker 仍然可以以其他形式的 K8s 共存。
首先,由于容器鏡像格式已經標準化(OCI規范,Open Container Initiative),Docker鏡像在K8s中仍然可以正常使用,不需要改變原有的開發測試和CI/CD流程。我們仍然可以拉取 Docker Hub,或者編寫一個 Dockerfile 來打包應用程序。
其次,Docker是一個完整的軟件產品線,不僅僅是containerd,它還包括鏡像構建、分發、測試等很多服務,甚至連K8s都內置于Docker Desktop中。
就容器開發的便利性而言,Docker暫時還難以被取代。大多數云原生開發人員可以繼續在這個熟悉的環境中工作,使用Docker來開發在K8s中運行的應用程序。
同樣,雖然 K8s 不再包含dockershim,Docker 已經接管了這部分代碼并構建了一個名為cri-dockerd的項目,該項目也同樣工作,將 Docker Engine 適配為 CRI 接口,這樣就kubelet可以通過它再次操作Docker,就好像它從來沒有發生過一樣。
總的來說,Docker雖然在容器編排大戰中敗下陣來,被K8s擠到了墻角,但依然具有很強的生命力。多年積累的眾多忠實用戶和大量應用形象是其最大的資本和后盾。足以支持它在另一條不與 K8s 正面交鋒的道路上。
對于初學者來說,Docker簡單易用,工具鏈完整,界面友好,市面上很難找到與之相媲美的軟件。應該說是入門級學習容器技術和云原生的上上選擇。






