1、为了什么需要 CRI ?
在 k8s v1.5 之前,Docker 作为第一代的容器运行时, kubelet 通过内嵌其中的 DockerShim 操作 Docker API 来操作容器。在 Kubernetes 1.5 中引入了 CRI,可以解耦了kubelet与容器运行时,该插件接口让 Kubernetes 无需重新编译就可以支持更多的容器运行时。
kubelet 将通过 CRI
接口来跟第三方容器运行时进行通信(如Rkt或Hyper等),来操作容器与镜像。CRI
包含 Protocol Buffers、gRPC API、以及运行库支持,还有尚在开发的标准规范和工具。
2、CRI 概览
实现 CRI 接口的容器运行时称为 CRI shim
, 它作为 gRPC 服务端,监听在本地的 Unix socket 上;而 Kubelet 作为 gRPC 客户端来调用 CRI 接口,进行 Pod 、容器、镜像的生命周期管理。
Protocol Buffers API 包含两个 gRPC 服务 ImageService
和 RuntimeService
。
ImageService
提供从仓库拉取镜像、查看和移除镜像等功能;RuntimeService
提供对 Pod 和容器的生命周期管理、和容器的交互,创建和启动容器、删除容器等功能;
另外安装时,需要在每台宿主机上单独安装一个负责响应 CRI 的组件称作 CRI shim
。顾名思义,CRI shim 的工作,就是扮演 kubelet 与容器项目之间的“垫片”(shim)。所以它的作用非常单一,那就是实现 CRI 规定的每个接口,然后把具体的 CRI 请求“翻译”成对后端容器项目的请求或者操作。
3、Pod 和容器的生命周期管理
service RuntimeService {// Sandbox operations.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) {}// Container operations.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) {}...
}
Pod 由一组应用程序容器组成,位于具有资源约束的隔离环境中。在CRI中,这种环境称为PodSandbox
。我们有意为容器运行时留出一些空间,以便根据它们在内部的运行方式以不同的方式解释 PodSandbox
。对于基于虚拟机管理程序的运行时,PodSandbox 可能代表虚拟机。对于其他人,例如Docker,它可能是Linux命名空间。PodSandbox 必须遵守 Pod 资源规范。在 v1alpha1 API 中,这是通过启动 kubelet 创建并传递给运行时的 pod 级 cgroup 中的所有进程来实现的。
在启动 pod 之前,kubelet 会调用 RuntimeService.RunPodSandbox
来创建环境。这包括为 Pod 设置网络(例如,分配 IP)。一旦 PodSandbox
处于活动状态,就可以独立创建/启动/停止/删除单个容器。要删除 pod,kubelet 会在停止和移除 PodSandbox 之前停止并移除容器。
Kubelet 的职责在于通过 RPC 管理容器的生命周期,实现容器生命周期的钩子,以及存活和健康监测,执行 Pod 的重启策略等。
4、Exec/attach/port-forward 请求
service RuntimeService {...// ExecSync runs a command in a container synchronously.rpc ExecSync(ExecSyncRequest) returns (ExecSyncResponse) {}// Exec prepares a streaming endpoint to execute a command in the container.rpc Exec(ExecRequest) returns (ExecResponse) {}// Attach prepares a streaming endpoint to attach to a running container.rpc Attach(AttachRequest) returns (AttachResponse) {}// PortForward prepares a streaming endpoint to forward ports from a PodSandbox.rpc PortForward(PortForwardRequest) returns (PortForwardResponse) {}...
}
Kubernetes 为用户提供了和 Pod 以及其中的容器进行交互的能力(kubectl exec/attach/port-forward
)。Kubelet 目前支持两种方式来支持这些功能:调用容器的本地方法,或者使用 Node 上的工具(例如 nsenter
以及 socat
)。因为多数工具假设 Pod 利用 Linux namespace 做了隔离,因此使用 Node 上的工具并不是一个可移植的方案。在 CRI 中,我们显式的定义这些调用,让运行时可以做特定实现。
当下还有一个潜在问题是,Kubelet 处理所有的请求连接,所以他有成为 Node 通信瓶颈的可能。在设计 CRI 的时候,我们采纳了一些反馈,让运行时能够排除中间人。容器运行时可以启动一个单独的流服务器处理请求(还能为 Pod 的资源使用进行记录),并把服务器地址返回给 Kubelet。这样 Kubelet 就能反馈信息给 API Server,使之可以直接连接到容器运行时的服务,并连接到客户端。