K8s 終將廢棄 docker,TKE 早已支援 containerd

騰訊雲原生發表於2020-12-14

近日 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 棄用 dockershim

Docker 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中使用DinD

containerd 的今生前世

所以 containerd 到底是個啥?和 docker 又是什麼關係?可能有些同學看到部落格後會發出這樣的疑問,接下來就給同學們講解下 containerd 和 docker 的淵源。

docker 與 containerd

2016年,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 所需的全部功能,同時還得到了更純粹的功能模組以及更短的呼叫鏈。

從上面的對比可以看出從 containerd 被捐贈給社群開始,就一直以成為簡單、穩定且可靠的容器執行時為目標;而 docker 則是希望能成為一個完整的產品。官方文件中也提到了這一點,docker 為了給使用者更好的互動和使用體驗以及更多的功能,提供了很多開發人員所需要的特性,同時為了給 swarm 做基礎,提供了網路和卷的功能。而這些功能其實都是是 K8s 用不上的;containerd 則相反,僅提供了 kubelet 建立 Pod 所需要的基礎功能,當然這換來的就是更高的魯棒性以及更好的效能。在一定程度上講,即使在 kubelet 1.23 版本之後 docker 提供了 CRI 介面,containerd 仍然是更好的選擇。

在 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/$CONTAINERID 目錄下。kubelet 會在 /var/log/pods 和 /var/log/containers 下面建立軟連結,指向 /var/lib/docker/containers/$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 = false
    

K8s 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. 在建立叢集時,選擇 1.12.4 及以上版本的 K8s 後,選擇 containerd 為執行時元件即可

  2. 在已有 docker 叢集中,通過建立執行時為 containerd 的節點池來建立一部分 containerd 節點(新建節點池 > 更多設定 > 執行時元件)

  3. 在已有 docker 叢集中,修改叢集或者節點池的"執行時元件"屬性為"containerd"

注意: 後兩種方式會造成同一叢集中 docker 節點與 containerd 節點共存,如果有使用 Docker in Docker, 或者其他依賴節點上 docker daemon 與 docker.sock 的業務,需要提前採取措施來避免產生問題,例如通過按節點標籤排程,保證這類業務排程到 docker 節點;或者採用如前文所述在 containerd 叢集執行 Docker in Docker 的方案。

現階段關於 containerd 和 docker 選擇問題可以檢視這裡

參考

[1] Don't Panic: Kubernetes and Docker

[2] Dockershim FAQ

[3] Dockershim Removal Kubernetes Enhancement Proposal

[4] kubernetes CHANGELOG-1.20

【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多幹貨!!

相關文章