官术网_书友最值得收藏!

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/的說明。

主站蜘蛛池模板: 衡阳县| 太白县| 大余县| 吴忠市| 绍兴市| 油尖旺区| 岳阳县| 余江县| 普安县| 宁波市| 巩义市| 东乡县| 利津县| 皋兰县| 兴安盟| 始兴县| 乐陵市| 敦化市| 济南市| 孙吴县| 同心县| 仙桃市| 桐城市| 青龙| 吉水县| 甘谷县| 吉安县| 南充市| 五家渠市| 广西| 翁源县| 镇远县| 阿克| 龙井市| 五莲县| 乌苏市| 扎鲁特旗| 梁平县| 岢岚县| 来凤县| 沐川县|