- Kubernetes權威指南:從Docker到Kubernetes實踐全接觸(第4版)
- 龔正等編著
- 2071字
- 2019-09-23 11:04:35
2.9 CRI(容器運行時接口)詳解
歸根結底,Kubernetes Node(kubelet)的主要功能就是啟動和停止容器的組件,我們稱之為容器運行時(Container Runtime),其中最知名的就是Docker了。為了更具擴展性,Kubernetes從1.5版本開始就加入了容器運行時插件API,即Container Runtime Interface,簡稱CRI。
2.9.1 CRI概述
每個容器運行時都有特點,因此不少用戶希望Kubernetes能夠支持更多的容器運行時。Kubernetes從1.5版本開始引入了CRI接口規范,通過插件接口模式,Kubernetes無須重新編譯就可以使用更多的容器運行時。CRI包含Protocol Buffers、gRPC API、運行庫支持及開發中的標準規范和工具。Docker的CRI實現在Kubernetes 1.6中被更新為Beta版本,并在kubelet啟動時默認啟動。
可替代的容器運行時支持是Kubernetes中的新概念。在Kubernetes 1.3發布時,rktnetes項目同時發布,讓rkt容器引擎成為除Docker外的又一選擇。然而,不管是Docker還是rkt,都用到了kubelet的內部接口,同kubelet源碼糾纏不清。這種程度的集成需要對kubelet的內部機制有非常深入的了解,還會給社區帶來管理壓力,這就給新生代容器運行時造成了難于跨越的集成壁壘。CRI接口規范試圖用定義清晰的抽象層清除這一壁壘,讓開發者能夠專注于容器運行時本身。在通向插件式容器支持及建設健康生態環境的路上,這是一小步,也是很重要的一步。
2.9.2 CRI的主要組件
kubelet使用gRPC框架通過UNIX Socket與容器運行時(或CRI代理)進行通信。在這個過程中kubelet是客戶端,CRI代理(shim)是服務端,如圖2.3所示。

圖2.3 CRI的主要組件
Protocol Buffers API包含兩個gRPC服務:ImageService和RuntimeService。
ImageService提供了從倉庫拉取鏡像、查看和移除鏡像的功能。
RuntimeService負責Pod和容器的生命周期管理,以及與容器的交互(exec/attach/port-forward)。rkt和Docker這樣的容器運行時可以使用一個Socket同時提供兩個服務,在kubelet中可以用--container-runtime-endpoint和--image-service-endpoint參數設置這個Socket。
2.9.3 Pod和容器的生命周期管理
Pod由一組應用容器組成,其中包含共有的環境和資源約束。在CRI里,這個環境被稱為PodSandbox。Kubernetes有意為容器運行時留下一些發揮空間,它們可以根據自己的內部實現來解釋PodSandbox。對于Hypervisor類的運行時,PodSandbox會具體化為一個虛擬機。其他例如Docker,會是一個Linux命名空間。在v1alpha1 API中,kubelet會創建Pod級別的cgroup傳遞給容器運行時,并以此運行所有進程來滿足PodSandbox對Pod的資源保障。
在啟動Pod之前,kubelet調用RuntimeService.RunPodSandbox來創建環境。這一過程包括為Pod設置網絡資源(分配IP等操作)。PodSandbox被激活之后,就可以獨立地創建、啟動、停止和刪除不同的容器了。kubelet會在停止和刪除PodSandbox之前首先停止和刪除其中的容器。
kubelet的職責在于通過RPC管理容器的生命周期,實現容器生命周期的鉤子,存活和健康監測,以及執行Pod的重啟策略等。
RuntimeService服務包括對Sandbox和Container操作的方法,下面的偽代碼展示了主要的RPC方法:
service RuntimeService { // 沙箱操作 rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse) {} rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse) {} rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse) {} rpc PodSandboxStatus(PodSandboxStatusRequest) returns (PodSandboxStatusResponse) {} rpc ListPodSandbox(ListPodSandboxRequest) returns (ListPodSandboxResponse) {} // 容器操作 rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {} rpc StartContainer(StartContainerRequest) returns (StartContainerResponse) {} rpc StopContainer(StopContainerRequest) returns (StopContainerResponse) {} rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse) {} rpc ListContainers(ListContainersRequest) returns (ListContainersResponse) {} rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse) {} ...... }
2.9.4 面向容器級別的設計思路
眾所周知,Kubernetes的最小調度單元是Pod,它曾經可能采用的一個CRI設計就是復用Pod對象,使得容器運行時可以自行實現控制邏輯和狀態轉換,這樣一來,就能極大地簡化API,讓CRI能夠更廣泛地適用于多種容器運行時。但是經過深入討論之后,Kubernetes放棄了這一想法。
首先,kubelet有很多Pod級別的功能和機制(例如crash-loop backoff機制),如果交給容器運行時去實現,則會造成很重的負擔;其次且更重要的是,Pod標準還在快速演進中。很多新功能(如初始化容器)都是由kubelet完成管理的,無須交給容器運行時實現。
CRI選擇了在容器級別進行實現,使得容器運行時能夠共享這些通用特性,以獲得更快的開發速度。這并不意味著設計哲學的改變——kubelet要負責、保證容器應用的實際狀態和聲明狀態的一致性。
Kubernetes為用戶提供了與Pod及其中的容器進行交互的功能(kubectl exec/attach/port- forward)。kubelet目前提供了兩種方式來支持這些功能。
(1)調用容器的本地方法。
(2)使用Node上的工具(例如nsenter及socat)。
因為多數工具都假設Pod用Linux namespace做了隔離,因此使用Node上的工具并不是一種容易移植的方案。在CRI中顯式定義了這些調用方法,讓容器運行時進行具體實現。下面的偽代碼顯示了Exec、Attach、PortForward這幾個調用需要實現的RuntimeService方法:
service RuntimeService { ...... // ExecSync在容器中同步執行一個命令。 rpc ExecSync(ExecSyncRequest) returns (ExecSyncResponse) {} // Exec在容器中執行命令 rpc Exec(ExecRequest) returns (ExecResponse) {} // Attach附著在容器上 rpc Attach(AttachRequest) returns (AttachResponse) {} // PortForward從Pod沙箱中進行端口轉發 rpc PortForward(PortForwardRequest) returns (PortForwardResponse) {} ...... }
目前還有一個潛在問題是,kubelet處理所有的請求連接,使其有成為Node通信瓶頸的可能。在設計CRI時,要讓容器運行時能夠跳過中間過程。容器運行時可以啟動一個單獨的流式服務來處理請求(還能對Pod的資源使用情況進行記錄),并將服務地址返回給kubelet。這樣kubelet就能反饋信息給API Server,使之可以直接連接到容器運行時提供的服務,并連接到客戶端。
2.9.5 嘗試使用新的Docker-CRI來創建容器
要嘗試新的Kubelet-CRI-Docker集成,只需為kubelet啟動參數加上--enable-cri=true開關來啟動CRI。這個選項從Kubernetes 1.6開始已經作為kubelet的默認選項了。如果不希望使用CRI,則可以設置--enable-cri=false來關閉這個功能。
查看kubelet的日志,可以看到啟用CRI和創建gRPC Server的日志:
I0603 15:08:28.953332 3442 container_manager_linux.go:250] Creating Container Manager object based on Node Config: {RuntimeCgroupsName: SystemCgroupsName: KubeletCgroupsName: ContainerRuntime:docker CgroupsPerQOS:true CgroupRoot:/ CgroupDriver:cgroupfs ProtectKernelDefaults:false EnableCRI:true NodeAllocatableConfig:{KubeReservedCgroupName: SystemReservedCgroupName: EnforceNodeAllocatable:map[pods:{}] KubeReserved:map[] SystemReserved:map[] HardEvictionThresholds:[{Signal:memory.available Operator:LessThan Value:{Quantity:100Mi Percentage:0} GracePeriod:0s MinReclaim:<nil>}]} ExperimentalQOSReserved:map[]} ...... I0603 15:08:29.060283 3442 kubelet.go:573] Starting the GRPC server for the docker CRI shim.
創建一個Deployment:
$ kubectl run nginx --image=nginx deployment "nginx " created
查看Pod的詳細信息,可以看到將會創建沙箱(Sandbox)的Event:
$ kubectl describe pod nginx ...... Events: ...From Type Reason Message ...----------------- ----- --------------- ----------------------------- ...default-scheduler Normal Scheduled Successfully assigned nginx to k8s-node-1 ...kubelet, k8s-node-1 Normal SandboxReceived Pod sandbox received, it will be created. ......
這表明kubelet使用了CRI接口來創建容器。
2.9.6 CRI的進展
目前已經有多款開源CRI項目可用于Kubernetes:Docker、CRI-O、Containerd、frakti(基于Hypervisor的容器運行時),各CRI運行時的安裝手冊可參考官網https://kubernetes.io/docs/setup/cri/的說明。