近日 K8s 官方稱最早將在 1.23版本棄用 docker 作為容器執行時,並在部落格中強調可以使用如 containerd 等 CRI 執行時來代替 docker。本文會做詳細解讀,並介紹 docker 與 containerd 的關係,以及為什麼 containerd 是更好的選擇。這裡先回答下TKE使用者關心的問題:我們的叢集該怎麼辦?
TKE叢集該怎麼辦TKE早在 2019年5月就已經支援選擇 containerd 作為容器執行時。如果新建叢集,推薦選擇 containerd 作為容器執行時已有叢集在升級到 K8s 1.23(假定 TKE 第一個不支援 dockershim 的 K8s版本,也可能是 1.24)之前,仍然可以繼續使用 docker 作為容器執行時已有叢集透過 TKE 叢集升級功能升級到 1.23時, TKE會提供切換執行時為 containerd 的選項。當然,這種情況下沒辦法做到 Pod 不受影響,只能採用重灌節點的方式來升級已有叢集也可以將執行時切換為 containerd ,新增節點會使用 containerd , 存量節點不受影響仍然使用 docker (注意: 這會造成同一叢集中 docker 節點與 containerd 節點共存,如果有使用 Docker in Docker, 或者其他依賴節點上 docker daemon 與 docker.sock 的業務,需要提前採取措施來避免產生問題,例如透過按節點標籤排程,保證這類業務排程到 docker 節點;或者採用如前文所述在 containerd 叢集執行 Docker in Docker 的方案)當然,在未來 docker 也有可能在內部實現 CRI 或者新增一個 dockershim 程序,如果 docker 做了相應適配,TKE 這邊在未來也會進行支援。解讀 K8s 棄用 dockershimDocker support in the kubelet is now deprecated and will be removed in a future release. The kubelet uses a module called “dockershim” which implements CRI support for Docker and it has seen maintenance issues in the Kubernetes community. We encourage you to evaluate moving to a container runtime that is a full-fledged implementation of CRI (v1alpha1 or v1 compliant) as they become available. ([#94624], [@dims]) [SIG Node]
K8s 在 1.20的 change log 中提到 K8s 將於 1.20版本開始逐步放棄對 Docker 的支援。在 K8s 的官方部落格中也提到具體的宣告和一些 FAQ。
在部落格中提到 K8s 將在 1.20版本中新增不推薦使用 docker 的資訊,且最早將於 1.23版本中把 dockershim 從 kubelet 中移除,屆時使用者將無法使用 docker 作為 K8s 叢集的執行時,不過透過 docker 構建的映象在沒有 docker 的 K8s 叢集中依然可以使用。
“寄生”在 kubelet 中的 dockershim本次改動主要內容是準備刪除 kubelet 中的 dockershim,當然這種做法也是符合預期的。在早期 rkt 和 docker 爭霸時,kubelet 中需要維護兩坨程式碼分別來適配 docker 和 rkt ,這使得 kubelet 每次釋出新功能都需要考慮對執行時元件的適配問題,嚴重拖慢了新版本釋出速度。另外虛擬化已經是一個普遍的需求,如果出現了型別的執行時,SIG-Node 小組可能還需要把和新執行時適配的程式碼新增到 kubelet 中。這種做法並不是長久之計,於是在 2016 年,SIG-Node提出了容器操作介面 CRI(Container Runtime Interface)。 CRI 是對容器操作的一組抽象,只要每種容器執行時都實現這組介面,kubelet 就能透過這組介面來適配所有的執行時。但 Docker 當時並沒有(也不打算)實現這組介面, kubelet 只能在內部維護一個稱之為“dockershim”元件,這個元件充當了 docker 的 CRI 轉接器,kubelet 在建立容器時透過 CRI 介面呼叫 dockershim ,而 dockershim 在透過 http 請求把請求交給 docker 。於是 kubelet 的架構變成下圖這樣:
在使用實現了 CRI 介面的元件作為容器執行時的情況下,kubelet 建立容器的呼叫鏈如圖中紅色箭頭所示,kubelet 中的 ContainerManager 可以直接透過 CRI 呼叫到容器執行時,這過程中只需要一次 grpc 請求;而在使用 docker 時,ContainerManager 會走圖中藍色的呼叫鏈, CRI 的請求透過 unix:///var/run/dockershim.sock 流向 dockershim,dockershim 做轉換後把請求轉發給 docker,至於為什麼 docker 後面還有個 containerd 稍後會講到。在 kubelet 中實現 docker 的轉接器本來就是一種不優雅的實現,這種做法讓呼叫鏈變長且不穩定性,還給 kubelet 的維護添加了額外工作,把這部分內容從 kubelet 刪掉就是時間問題了。
棄用 Docker 後會有什麼不同?If you’re an end-user of Kubernetes, not a whole lot will be changing for you. This doesn’t mean the death of Docker, and it doesn’t mean you can’t, or shouldn’t, use Docker as a development tool anymore. Docker is still a useful tool for building containers, and the images that result from running docker build can still run in your Kubernetes cluster.
訊息一出,大家最關心的事情應該就是棄用 docker 後到底會產生什麼影響?
官方的答覆是:Don’t Panic!隨後又重點解釋了幾個大家最關心的問題,我們來分析下官方提到的這些方面:
正常的 K8s 使用者不會有任何影響是的,生產環境中高版本的叢集只需要把執行時從 docker 切換到其他的 runtime(如 containerd)即可。containerd 是 docker 中的一個底層元件,主要負責維護容器的生命週期,跟隨 docker 經歷了長期考驗。同時 2019年初就從 CNCF 畢業,可以單獨作為容器執行時用在叢集中。TKE 也早在 2019 年就已經提供了 containerd 作為執行時選項,因此把 runtime 從 docker 轉換到 containerd 是一個基本無痛的過程。CRI-O 是另一個常被提及的執行時元件,由 redhat 提供,比 containerd 更加輕量級,不過和 docker 的區別較大,可能轉換時會有一些不同之處。開發環境中透過docker build構建出來的映象依然可以在叢集中使用映象一直是容器生態的一大優勢,雖然人們總是把映象稱之為“docker映象”,但映象早就成為了一種規範了。具體規範可以參考[image-spec]。在任何地方只要構建出符合 Image Spec 的映象,就可以拿到其他符合 Image Spec 的容器執行時上執行。在 Pod 中使用 DinD(Docker in Docker)的使用者會受到影響有些使用者會把 docker 的 socket (/run/docker.sock)掛載到 Pod 中,並在 Pod 中呼叫 docker 的 api 構建映象或建立編譯容器等,官方在這裡的建議是使用 Kaniko、Img 或 Buildah。我們可以透過把 docker daemon 作為 DaemonSet 或者給想要使用 docker 的 Pod 新增一個 docker daemon 的 sidecar 的方式在任意執行時中使用 DinD 的方案。TKE 也專門為在 containerd 叢集中使用 DinD 提供了方案。containerd 的今生前世所以 containerd 到底是個啥?和 docker 又是什麼關係?可能有些同學看到部落格後會發出這樣的疑問,接下來就給同學們講解下 containerd 和 docker 的淵源。
docker 與 containerd2016年,docker 把負責容器生命週期的模組拆分出來,並將其捐贈給了社群,也就是現在的 containerd。docker 拆分後結構如下圖所示(當然 docker 公司還在 docker 中添加了部分編排的程式碼)。
在我們呼叫 docker 命令建立容器後,docker daemon 會透過 Image 模組下載映象並儲存到 Graph Driver 模組中,之後透過 client 呼叫containerd 建立並執行容器。我們在使用 docker 建立容器時可能需要使用--volume給容器新增持久化儲存;還有可能透過--network連線我們用 docker 命令建立的幾個容器,當然,這些功能是 docker 中的 Storage 模組和 Networking 模組提供給我們的。但 K8s 提供了更強的卷掛載能力和叢集級別的網路能力,在叢集中 kubelet 只會使用到 docker 提供的映象下載和容器管理功能,而編排、網路、儲存等功能都不會用到。下圖中可以看出當前的模式下各模組的呼叫鏈,同時圖中被紅框標註出的幾個模組就是 kubelet 建立 Pod 時所依賴的幾個執行時的模組。
containerd 被捐贈給CNCF社群後,社群給其添加了映象管理模組和 CRI 模組,這樣 containerd 不只可以管理容器的生命週期,還可以直接作為 K8s 的執行時使用。於是 containerd 在 2019年2月從 CNCF 社群畢業,正式進入生產環境。下圖中能看出以 containerd 作為容器執行時,可以給 kubelet 帶來建立 Pod 所需的全部功能,同時還得到了更純粹的功能模組以及更短的呼叫鏈。
在 Kubernetes 叢集中使用 containerd當然現在有諸多的 CRI 實現者,比較主要的除了 containerd 還有 CRI-O。CRI-O 是主要由 Red Hat 員工開發的 CRI 執行時,完全和 docker 沒有關係,因此從 docker 遷移過來可能會比較困難。無疑 containerd 才是 docker 被拋棄後的 CRI 執行時的最佳人選,對於開發同學來說整個遷移過程應該是無感知的,不過對於部分運維同學可能會比較在意部署和執行中細節上的差異。接下來我們重點介紹下在 K8s 中使用 containerd 和 docker 的幾處區別。
容器日誌對比項對比項
Docker
Containerd
儲存路徑
如果 docker 作為 K8s 容器執行時,容器日誌的落盤將由 docker 來完成,儲存在類似/var/lib/docker/containers/KaTeX parse error: Expected 'EOF', got '目' at position 13: CONTAINERID 目̲錄下。kubelet 會在 /…CONTAINERID 該目錄下的容器日誌檔案。
如果 Containerd 作為 K8s 容器執行時, 容器日誌的落盤由 kubelet 來完成,儲存至 /var/log/pods/$CONTAINER_NAME 目錄下,同時在 /var/log/containers 目錄下建立軟連結,指向日誌檔案。
配置引數
在 docker 配置檔案中指定:“log-driver”: “json-file”,“log-opts”: {“max-size”: “100m”,“max-file”: “5”}
方法一:在 kubelet 引數中指定:–container-log-max-files=5 --container-log-max-size=“100Mi” 方法二:在 KubeletConfiguration 中指定: “containerLogMaxSize”: “100Mi”, “containerLogMaxFiles”: 5,
把容器日誌儲存到資料盤
把資料盤掛載到 “data-root”(預設是 /var/lib/docker)即可。
建立一個軟連結 /var/log/pods 指向資料盤掛載點下的某個目錄。在 TKE 中選擇“將容器和映象儲存在資料盤”,會自動建立軟連結 /var/log/pods。
cni 配置方式的區別 在使用 docker 時,kubelet 中的 dockershim 負責呼叫 cni 外掛,而 containerd 的場景中 containerd 中內建的 containerd-cri 外掛負責呼叫 cni,因此關於 cni 的配置檔案需要放在 containerd 的配置檔案中(/etc/containerd/containerd.toml):[plugins.cri.cni]bin_dir = "/opt/cni/bin"conf_dir = “/etc/cni/net.d”stream 服務的區別說明:Kubectl exec/logs 等命令需要在 apiserver 跟容器執行時之間建立流轉發通道。如何在 containerd 中使用並配置 Stream 服務?Docker API 本身提供 stream 服務,kubelet 內部的 docker-shim 會透過 docker API 做流轉發。而containerd 的 stream 服務需要單獨配置:[plugins.cri]stream_server_address = "127.0.0.1"stream_server_port = "0"enable_tls_streaming = false [plugins.cri] stream_server_address = “127.0.0.1” stream_server_port = “0” enable_tls_streaming = falseK8s 1.11 前後版本配置區別是什麼?containerd 的 stream 服務在 K8s 不同版本執行時場景下配置不同。
在 K8s 1.11 之前: kubelet 不會做 stream proxy,只會做重定向。即 kubelet 會將 containerd 暴露的 stream server 地址傳送給 apiserver,並讓 apiserver 直接訪問 containerd 的 stream 服務。此時,您需要給 stream 服務轉發器認證,用於安全防護。在 K8s 1.11 之後: K8s1.11 引入了 [kubelet stream proxy], 使用 containerd stream 服務只需要監聽本地地址即可。在 TKE 叢集中使用 containerd從 2019年5月份開始,TKE就開始支援把 containerd 作為容器執行時選項之一。隨著TKE逐步在 containerd 叢集中支援日誌收集服務和 GPU 能力,2020年 9月份 containerd 在 TKE 也摘掉了 Beta 版本的標籤,可以正式用於生產環境中了。在長期使用中,我們也發現了一些 containerd 的問題並且及時進行了修復
想要在TKE叢集中使用 containerd 作為執行時有三種方式:
在建立叢集時,選擇 1.12.4 及以上版本的 K8s 後,選擇 containerd 為執行時元件即可在已有 docker 叢集中,透過建立執行時為 containerd 的節點池來建立一部分 containerd 節點(新建節點池 > 更多設定 > 執行時元件)在已有 docker 叢集中,修改叢集或者節點池的"執行時元件"屬性為"containerd"注意: 後兩種方式會造成同一叢集中 docker 節點與 containerd 節點共存,如果有使用 Docker in Docker, 或者其他依賴節點上 docker daemon 與 docker.sock 的業務,需要提前採取措施來避免產生問題,例如透過按節點標籤排程,保證這類業務排程到 docker 節點;或者採用如前文所述在 containerd 叢集執行 Docker in Docker 的方案。