寫這篇文章是來填 很久之前挖下的坑[1]。
本文涉及組件的源碼版本如下:
- Kube.NETes 1.24
- CRI 0.25.0
- ContAInerd 1.6
容器運行時(Container Runtime)是負責管理和執行容器的組件。它負責將容器鏡像轉化為在主機上運行的實際容器進程,提供鏡像管理、容器的生命周期管理、資源隔離、文件系統、網絡配置等功能。
圖片
常見容器運行時有下面這幾種,這些容器運行時都提供了不同程度的功能和性能。但他們都遵循容器運行時接口(CRI),以便能夠與 Kubernetes 或其他容器編排系統集成,實現容器的調度和管理。
- containerd[2]
- CRI-O[3]
- Docker Engine[4]
- Mirantis Container Runtime[5]
有了 CRI,我們也可以“隨意”地在幾種容器運行時之間進行切換,而無需重新編譯 Kubernetes。簡單來講,CRI 定義了所有對容器的操作,作為容器編排系統與容器運行時間的標準接口存在。
CRI 的前生今世
圖片
CRI 的首次引入是在 Kubernets 1.5[6],初始版本是 v1alpha1。在這之前,Kubernetes 需要在 kubelet 源碼中維護對各個容器運行時的支持。
有了 CRI 之后,在 kubelet 中僅需支持 CRI 即可,然后通過一個中間層 CRI shim(grpc 服務器)與容器運行時進行交互。因為此時各家容器運行時實現還未支持 CRI。
在去年發布的 Kubernetes 1.24 中,正式移除了 Dockershim[7],與容易運行時的交互得到了簡化。
Kubernetes 目前支持 CRI 的 v1alpha2 和 v1。其中 v1 版本是在 Kubernetes 1.23 版本中引入的。
每次 kubelet 啟動時,首先會嘗試使用 v1 的 API 與容器運行時進行連接。如果失敗,才會嘗試使用 v1alpha2。
kubelet 與 CRI
在之前做過的 kubelet 源碼分析[8] 會持續監控來自 文件、apiserver、http 的變更,來更新 pod 的狀態。寫那篇文章的時候,分析到這里就結束了。因為這之后的工作就交給 容器運行時[9] 來完成 sandbox 和各種容器的創建和運行,見 `kubeGenericRuntimeManager#SyncPod()`[10]。
kubelet 啟動時便會 初始化 CRI 客戶端[11],與容器運行時建立連接并確認 CRI 的版本。
創建 pod 的過程中,都會通過 CRI 與容器運行時進行交互:
- 創建 sandbox
- 創建容器
- 拉取鏡像
參考源碼
- pkg/kubelet/kuberuntime/kuberuntime_sandbox.go#L39[12]
- pkg/kubelet/kuberuntime/kuberuntime_container.go#L176[13]
- pkg/kubelet/images/image_manager.go#L89[14]
接下來我們以 Containerd 為例,看下如何處理 kubelet 的請求。
Containerd 與 CRI
Containerd 的 `criService`[15] 實現了 CRI 接口 `RuntimeService`[16] 和 `ImageService `[17] 的 RuntimeServiceServer 和 ImageServiceServer。
cirService 會進一步包裝成 `instrumentedService`[18],保證所有的操作都是在 k8s.io命名空間下執行的
RuntimeServiceServer
ImageServiceServer
ImageServiceServer[20]
type ImageServiceServer interface {
// ListImages lists existing images. ListImages(context.Context, *ListImagesRequest) (*ListImagesResponse, error)
// ImageStatus returns the status of the image. If the image is not // present, returns a response with ImageStatusResponse.Image set to // nil. ImageStatus(context.Context, *ImageStatusRequest) (*ImageStatusResponse, error)
// PullImage pulls an image with authentication config. PullImage(context.Context, *PullImageRequest) (*PullImageResponse, error)
// RemoveImage removes the image. // This call is idempotent, and must not return an error if the image has // already been removed. RemoveImage(context.Context, *RemoveImageRequest) (*RemoveImageResponse, error)
// ImageFSInfo returns information of the filesystem that is used to store images.
ImageFsInfo(context.Context, *ImageFsInfoRequest) (*ImageFsInfoResponse, error)
}
下面以創建 sandbox 為例看一下 Containerd 的源碼。
Containerd 源碼分析
創建 sandbox 容器的請求通過 CRI 的 UDS(Unix domain socket)[21] 接口 /runtime.v1.RuntimeService/RunPodSandbox,進入到 criService 的處理流程中。在 criService#RunPodSandbox(),負責創建和運行 sandbox 容器,并保證容器狀態正常。
- 下載 sandobx 容器鏡像
- 初始化容器元數據
- 初始化 pod 網絡命名空間,詳細內容可參考之前的文章 源碼解析:從 kubelet、容器運行時看 CNI 的使用[22]
- 更新容器元數據
- 寫入文件系統
參考源碼
- pkg/cri/server/sandbox_run.go#L61[23]
- services/tasks/local.go#L156[24]
總結
CRI 提供了一種標準化的接口,用于與底層容器運行時進行交互。這對與發展和狀大 Kubernetes 生態系統非常重要:
- Kubernetes 控制平面與容器管理的具體實現解耦,可以獨立升級或者切換容器運行時,方便擴展和優化。
- Kubernetes 作為一個跨云、跨平臺和多環境的容器編排系統,在不同的環境和場景下使用不同的容器平臺。CRI 的出現,保證平臺的多樣性和靈活性。
參考資料
[1] 很久之前挖下的坑: https://atbug.com/how-kubelete-container-runtime-work-with-cni/#創建-pod
[2] containerd: https://kubernetes.io/docs/setup/production-environment/container-runtimes/#containerd
[3] CRI-O: https://kubernetes.io/docs/setup/production-environment/container-runtimes/#cri-o
[4] Docker Engine: https://kubernetes.io/docs/setup/production-environment/container-runtimes/#docker
[5] Mirantis Container Runtime: https://kubernetes.io/docs/setup/production-environment/container-runtimes/#mcr
[6] Kubernets 1.5: https://kubernetes.io/blog/2016/12/container-runtime-interface-cri-in-kubernetes/
[7] 正式移除了 Dockershim: https://kubernetes.io/blog/2022/05/03/dockershim-historical-context/
[8] kubelet 源碼分析: https://mp.weixin.qq.com/s/O7k3MlgyonNtOUxNPrN8lg
[9] 容器運行時: https://kubernetes.io/docs/setup/production-environment/container-runtimes/
[10] kubeGenericRuntimeManager#SyncPod(): https://Github.com/kubernetes/kubernetes/blob/023d6fb8f4a7d130bf5c8e725ca310df9e663cd0/pkg/kubelet/kuberuntime/kuberuntime_manager.go#L711
[11] 初始化 CRI 客戶端: https://github.com/kubernetes/kubernetes/blob/14fcab83adf319b8ef8e82e1054412309c46f535/pkg/kubelet/kubelet.go#L285
[12] pkg/kubelet/kuberuntime/kuberuntime_sandbox.go#L39: https://github.com/kubernetes/kubernetes/blob/ea929715339da4553589df61c8638bac3bcae618/pkg/kubelet/kuberuntime/kuberuntime_sandbox.go#L39
[13] pkg/kubelet/kuberuntime/kuberuntime_container.go#L176: https://github.com/kubernetes/kubernetes/blob/3946d99904fe37ea04b231a8d101085b9b80b221/pkg/kubelet/kuberuntime/kuberuntime_container.go#L176
[14] pkg/kubelet/images/image_manager.go#L89: https://github.com/kubernetes/kubernetes/blob/de37b9d293613aac194cf522561d19ee1829e87b/pkg/kubelet/images/image_manager.go#L89
[15] criService: https://github.com/containerd/containerd/blob/1764ea9a2815ddbd0cde777b557f97171b84cd02/pkg/cri/server/service.go#L77
[16] RuntimeService: https://github.com/kubernetes/cri-api/blob/master/pkg/apis/runtime/v1/api.proto#L34
[17] ImageService : https://github.com/kubernetes/cri-api/blob/master/pkg/apis/runtime/v1/api.proto#L128
[18] instrumentedService: https://github.com/containerd/containerd/blob/d3c7e31c8a8f7dc3f0ef0d189fda5a7caca42ce2/pkg/cri/server/instrumented_service.go#L32
[19] RuntimeServiceServer: https://github.com/kubernetes/cri-api/blob/v0.25.0/pkg/apis/runtime/v1/api.pb.go#L9301
[20] ImageServiceServer: https://github.com/kubernetes/cri-api/blob/v0.25.0/pkg/apis/runtime/v1/api.pb.go#L10131C9-L10131C9
[21] UDS(Unix domain socket): https://en.wikipedia.org/wiki/Unix_domain_socket
[22] 源碼解析:從 kubelet、容器運行時看 CNI 的使用: https://atbug.com/how-kubelete-container-runtime-work-with-cni/#創建-sandbox-容器
[23] pkg/cri/server/sandbox_run.go#L61: https://github.com/containerd/containerd/blob/f2376e659ffa55e4ff2578baf4e4c7aab54042e4/pkg/cri/server/sandbox_run.go#L61
[24] services/tasks/local.go#L156: https://github.com/containerd/containerd/blob/bbe46b8c43fc2febe316775bc2d4b9d697bbf05c/services/tasks/local.go#L156