作者 | 楊明越
Kubernetes 在其最新的 Changelog 中宣佈,自 Kubernetes 1.20 之後將棄用 Docker 作為容器執行時。那麼這到底是怎麼回事?開發者和企業會受到什麼樣到影響?
近幾年,Kubernetes 已經成為自有機房、雲上廣泛使用的容器編排方案,最廣泛的使用方式是 Kubernetes+Docker。從 DevOps 人員的角度,一面用 kubctl 命令、k8s API 來操作叢集,一面在單機用 Docker 命令來管理映象、執行映象。
單獨用 Docker 的情況,在一些公司的場景裡面也是有的。一種場景是“只分不合”,把一臺機器用 Docker 做資源隔離,但是不需要將多容器“編排”。單獨用 Kubernetes,下層不是 Docker 的情況,並不算很多。
Kubernetes 和 Docker 的關係,簡單來說,有互補,也有競爭。在一般的認知中,Kubernetes 和 Docker 是互補關係:
Dockers 屬於下層——容器引擎;Kubernetes 屬於上層——編排排程層。Docker 源於 Linux Container,可以將一臺機器的資源分成 N 份容器,做到資源的隔離,並將可執行的程式定義為標準的 docker image;Kubernetes 則可以把不同機器的每份容器進行編排、排程,組成分散式系統。
Kubernetes 和 Docker 並不完全是“涇渭分明”的互補關係,它之間有重疊部分,也可以說成是競爭,主要在於幾個點:
系統三大移植資源是計算、儲存、網路,從 Kubernetes 角度 Docker 屬於“Runtime(執行時)”,也就是計算資源;但是 Docker 技術體系裡面,本身也包括儲存層、網路層。上下層職責的重疊,也可以看作競爭。Docker 原本有個原生的排程引擎——Swarm,幾年前在排程編排領域,還是 Kubernetes、Mesos、Swarm 三者並存,Kubernetes 最終勝出,但 Docker 仍有“繼續向上做一層的意願”。Kubernetes 在如何使用 Docker 方面,存在爭議和變數。kubernetes 1.20 ChangeLog 中所謂要廢棄 Docker 的傳言,也是無風不起浪。換句話說,即便 Kubernetes 一直用 Docker,也不是用 Docker 的全部,多少是不一樣的。
而且,“棄用 Docker”這個詞本身有多重的含義,Docker 並非一個單層軟體,Kubernetes 1.20 啟用 dockershim 並不代表棄用了 Docker 的全部,仍有 containerd 可以對接 docker。
1Kubernetes 有 CRI、OCI 兩個容器標準
在目前廣泛使用 kubernetes 與 Runtime 的橋接方案,CRI(Container Runtime Interface)與 OCI(Open Container Initiative)是“套娃“關係。Kubernetes 的 kubelet 呼叫 CRI,OCI 的實現者然後再呼叫 OCI。
下圖也說明了 CRI 與 OCI 的關係:
從 Kubernetes 的角度,CRI 是與 CNI(網路)、CSI(儲存)相同層級的介面。
OCI 是個自下而上的標準,也就是從實現抽象出介面,它是 Linux Foundation 主導的。Docker 實現的核心 RunC,也就是 OCI 的典型實現、標準實現。
CRI 是個自上而下的標準,源於 Kubernetes 對移植層(執行時)的要求。
容器引擎層自下而上定義 OCI,容器編排層自上而下定義 CRI,這也讓它們出現了“套娃“執行情況。
在 Kubernetes 的 dockershim、cri-containerd、cri-o 三種實現中,RedHat 推崇的 cri-o 已經比較主流,它雖然仍是“套娃“,但已經比較精簡。
下面是從 kubernetes 叢集執行的全景圖看 cri-o 的位置:
2Docker 本源於 Linux Container
Docker 作為容器引擎,其實現的基礎是 Linux Container——從核心到使用者空間的機制。
Linux Container 可以分成兩個部分,核心裡面的 cgroup,使用者空間的 lxc。Docker 最初實現的時候,也是完全基於 Linux Container 的,基於 lxc 做更上層的東西。
這張很多人認為“與事實不符“的圖,其實代表了過去:
在 Docker 的發展過程中,最終啟用了 C 語言寫成 lxc,換成了 go 語言寫成的 libcontainer。
下面的圖也不是很新,但它更能表示 Docker 後續典型的架構,這裡面已經沒有了 lxc。
然而,萬變不離其宗,Docker 實現的本源,還是 Linux Container。即便不用 lxc,當仍要用核心的 cgroup,並且模式也是類似的。
3Kubernetes 最終如何橋接容器
下面是 Docker 的簡圖:
從軟體模組的角度,圖中的 docker Engine、cri-containd、containd-shim、runC 都屬於 Docker 體系的軟體。
下圖中的紫、橙、紅三種顏色,代表了 dockershim、cri-containerd、cri-o 三種 CRI 的典型方式——流程在逐漸縮短,這也是 CRI 實現的一個演進過程。
如果是 kubelet 的 dockershim 模式(紫色),流程是這樣的:
kubelet 從 CRI 的 gRPC 呼叫 dockershim,二者在同一個程序dockershim 呼叫 docker 守護程序docker 守護程序呼叫 containerd;containerd 呼叫 containerd-shim(有時名為 docker-containerd-shim 守護程序)完成建立容器等操作containerd-shim 訪問 OCI 的實現 runC(命令列可執行程式)如果是 kubelet 的 cri-containerd 模式(橙色),流程是這樣的:
kubelet 從 CRI 的 gRPC 呼叫 cri-containerd;cri-containerd 呼叫 containerd;containerd 呼叫 containerd-shim(同上)containerd-shim 呼叫 RucnC (同上);在很多人的印象中,如果不用 docker 守護程序,就相當於“棄用 docker“,這其實也就是從 dockershim 到 containerd 的變化。從另一個角度來說,containerd 這個守護程序,也是 docker 組織做的。
如果是 kubelet 的 cri-o 模式(紅色),則更加簡練:
kubelet 從 CRI 的 gRPC 呼叫 cri-o;cri-o 呼叫 OCI 的實現 runC如果以 kubelet 呼叫 CRI 為起點,OCI 的 runC 呼叫為終點,三種模式經歷的可執行程式分別是:
dockershim 模式:dockershim(*)->dockd->containerd->containerd-shimcri-containerd 模式:cri-containerd(*)-> containerd->containerd-shimcri-o 模式:cri-odockershim 模式有 3 個可執行程式,dockershim 一般與 kubelet 同進程;cri-containerd 模式有 2-3 個可執行程式,cri-containerd 可與 containerd 同進程;cri-o 模式只有 1 個可執行程式。
顯然在這種 Red Hat 推崇的 cri-o 模式下,Docker 體系的 containerd 也用不著了,只剩 runC 這個命令列的程式。runC 也是用 go 寫成的,裡面有呼叫 libcontainer。
當 Docker 萎縮到這個地步,其實也只剩 Linux 核心裡面 cgroup、namespace 功能的封裝了。
總結來說,由於老技術實現的慣性,在生產環境大量使用的經典 Kubernetes+ Docker 方案依然執行,且運維已經成熟,不會很快升級。對於開發人員、企業,對於 K8S API 的使用頻率、變數,遠遠大於 Docker API,至於 Kubernetes 和 Docker 的橋接,更不用關心。因此,即便“徹底棄用 Docker”,對開發者與企業的影響也非常有限。