本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
在 Kubernetes 中引入容器运行时接口(CRI)
编者按:这篇博文是关于 Kubernetes 1.5 新功能系列深度文章的一部分。
在 Kubernetes 节点的最低层是负责启动和停止容器等任务的软件。我们称之为“容器运行时”。最广为人知的容器运行时是 Docker,但它并非唯一。事实上,容器运行时领域一直在快速发展。作为使 Kubernetes 更具可扩展性的一部分努力,我们一直在为 Kubernetes 中的容器运行时开发新的插件 API,称为“CRI”。
CRI 是什么?Kubernetes 为什么需要它?
每个容器运行时都有其自身的优势,许多用户都要求 Kubernetes 支持更多的运行时。在 Kubernetes 1.5 版本中,我们很荣幸推出容器运行时接口 (CRI)——一个插件接口,它使 kubelet 能够使用各种容器运行时,而无需重新编译。CRI 由协议缓冲区和gRPC API以及库组成,更多规范和工具正在积极开发中。CRI 在 Kubernetes 1.5 中以 Alpha 版发布。
支持可互换的容器运行时在 Kubernetes 中并不是一个新概念。在 1.3 版本中,我们宣布了 rktnetes 项目,以支持 rkt 容器引擎作为 Docker 容器运行时的替代方案。然而,Docker 和 rkt 都通过内部和不稳定的接口直接且深度集成到 kubelet 源代码中。这种集成过程需要深入理解 Kubelet 内部结构,并给 Kubernetes 社区带来巨大的维护开销。这些因素为新兴容器运行时设置了很高的门槛。通过提供清晰定义的抽象层,我们消除了这些障碍,让开发者能够专注于构建他们的容器运行时。这是实现可插拔容器运行时和构建更健康生态系统的一小步,但却是重要的一步。
CRI 概述
Kubelet 使用 gRPC 框架通过 Unix 套接字与容器运行时(或运行时 CRI shim)通信,其中 kubelet 充当客户端,CRI shim 充当服务器。
协议缓冲区的API包含两个 gRPC 服务:ImageService 和 RuntimeService。ImageService 提供用于从仓库拉取、检查和删除镜像的 RPC。RuntimeService 包含用于管理 Pod 和容器生命周期的 RPC,以及与容器交互(执行/附加/端口转发)的调用。管理镜像和容器的单一容器运行时(例如 Docker 和 rkt)可以通过单个套接字同时提供这两个服务。套接字可以通过 kubelet 的 `--container-runtime-endpoint` 和 `--image-service-endpoint` 标志进行设置。
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 进行不同的解释。对于基于 hypervisor 的运行时,PodSandbox 可能代表一个虚拟机。对于其他运行时,例如 Docker,它可能代表 Linux 命名空间。PodSandbox 必须遵守 Pod 资源规范。在 v1alpha1 API 中,这是通过在 kubelet 创建并传递给运行时的 Pod 级别 cgroup 中启动所有进程来实现的。
在启动 Pod 之前,kubelet 调用 RuntimeService.RunPodSandbox 来创建环境。这包括为 Pod 设置网络(例如,分配 IP)。一旦 PodSandbox 处于活动状态,各个容器可以独立地创建/启动/停止/移除。要删除 Pod,kubelet 将在停止并移除 PodSandbox 之前停止并移除容器。
Kubelet 负责通过 RPC 管理容器的生命周期,执行容器生命周期钩子和存活/就绪检查,同时遵守 Pod 的重启策略。
为什么是命令式、以容器为中心的接口?
Kubernetes 具有声明式 API 和一个 `Pod` 资源。我们考虑过的一种可能设计是 CRI 在其抽象中重用声明式 `Pod` 对象,让容器运行时可以自由地实现和执行自己的控制逻辑以达到期望状态。这会大大简化 API,并允许 CRI 适用于更广泛的运行时。我们在设计阶段早期讨论过这种方法,但出于几个原因决定反对。首先,kubelet 中有许多 Pod 级别的特性和特定机制(例如,崩溃循环回退逻辑),所有运行时重新实现它们将是一个巨大的负担。其次,更重要的是,Pod 规范(并且仍然)在快速发展。许多新特性(例如,初始化容器)不需要对底层容器运行时进行任何更改,只要 kubelet 直接管理容器即可。CRI 采用命令式容器级别接口,以便运行时可以共享这些常见特性,以提高开发速度。这并不意味着我们偏离了“水平触发”的理念——kubelet 负责确保实际状态被驱动到声明状态。
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 提供功能(例如 kubectl exec/attach/port-forward),供用户与 Pod 及其中的容器进行交互。Kubelet 今天通过调用容器运行时的原生方法或使用节点上可用的工具(例如 nsenter 和 socat)来支持这些功能。使用节点上的工具不是一个可移植的解决方案,因为大多数工具都假设 Pod 使用 Linux 命名空间进行隔离。在 CRI 中,我们明确在 API 中定义这些调用,以允许运行时特定的实现。
Kubelet 当前实现中另一个潜在问题是,kubelet 处理所有流请求的连接,因此它可能成为节点上网络流量的瓶颈。在设计 CRI 时,我们采纳了这一反馈,允许运行时消除中间人。容器运行时可以根据请求启动一个单独的流服务器(并且可能将资源使用量计入 Pod!),并将服务器的位置返回给 kubelet。然后 kubelet 将此信息返回给 Kubernetes API 服务器,Kubernetes API 服务器打开一个直接连接到运行时提供的服务器的流连接,并将其连接到客户端。
本博客文章未涵盖 CRI 的许多其他方面。请参阅设计文档和提案列表以获取所有详细信息。
当前状态
尽管 CRI 仍处于早期阶段,但已经有几个项目正在开发中,以使用 CRI 集成容器运行时。以下是一些示例:
- cri-o: OCI 兼容运行时。
- rktlet: rkt 容器运行时。
- frakti: 基于 Hypervisor 的容器运行时。
- docker CRI shim.
如果您有兴趣尝试这些替代运行时,可以关注各个存储库以获取最新进展和说明。
对于有兴趣集成新容器运行时的开发者,请参阅开发者指南以了解 API 的已知限制和问题。我们正在积极吸收早期开发者的反馈以改进 API。开发者应该预料到 API 会偶尔发生破坏性更改(毕竟它是 Alpha 版)。
尝试新的 CRI-Docker 集成
Kubelet 尚未默认使用 CRI,但我们正在积极努力实现这一目标。第一步是使用 CRI 将 Docker 重新集成到 kubelet 中。在 1.5 版本中,我们扩展了 kubelet 以支持 CRI,并为 Docker 添加了一个内置的 CRI shim。这允许 kubelet 代表 Docker 启动 gRPC 服务器。要尝试新的 kubelet-CRI-Docker 集成,您只需通过 `--feature-gates=StreamingProxyRedirects=true` 启动 Kubernetes API 服务器以启用新的流重定向功能,然后通过 `--experimental-cri=true` 启动 kubelet。
除了少数缺失的功能之外,新的集成已持续通过主要的端到端测试。我们计划很快扩大测试覆盖范围,并鼓励社区报告任何问题以帮助过渡。
CRI 与 Minikube
如果您想尝试新的集成,但还没有时间在云中搭建一个新的测试集群,minikube 是一个快速搭建本地集群的绝佳工具。在开始之前,请按照说明下载并安装 minikube。
- 检查可用的 Kubernetes 版本并选择最新的 1.5.x 版本。我们将以 v1.5.0-beta.1 为例。
$ minikube get-k8s-versions
- 启动一个包含内置 docker CRI 集成的 minikube 集群。
$ minikube start --kubernetes-version=v1.5.0-beta.1 --extra-config=kubelet.EnableCRI=true --network-plugin=kubenet --extra-config=kubelet.PodCIDR=10.180.1.0/24 --iso-url=http://storage.googleapis.com/minikube/iso/buildroot/minikube-v0.0.6.iso
`--extra-config=kubelet.EnableCRI=true` 开启 kubelet 中的 CRI 实现。`--network-plugin=kubenet` 和 `--extra-config=kubelet.PodCIDR=10.180.1.0/24` 将网络插件设置为 kubenet 并确保为节点分配一个 PodCIDR。或者,您可以使用不依赖 PodCIDR 的 cni 插件。 `--iso-url` 设置一个 ISO 镜像,供 minikube 启动节点使用。示例中使用的镜像
- 检查 minikube 日志以确认 CRI 已启用。
$ minikube logs | grep EnableCRI
I1209 01:48:51.150789 3226 localkube.go:116] Setting EnableCRI to true on kubelet.
- 创建 Pod 并检查其状态。您应该会看到一个“SandboxReceived”事件,证明 Kubelet 正在使用 CRI!
$ kubectl run foo --image=gcr.io/google\_containers/pause-amd64:3.0
deployment "foo" created
$ kubectl describe pod foo
...
... From Type Reason Message
... ----------------- ----- --------------- -----------------------------
...{default-scheduler } Normal Scheduled Successfully assigned foo-141968229-v1op9 to minikube
...{kubelet minikube} Normal SandboxReceived Pod sandbox received, it will be created.
...
_请注意,在 minikube 中启用 CRI 后,kubectl attach/exec/port-forward 尚无法工作,但这将在新版本的 minikube 中解决。_
社区
CRI 由 Kubernetes SIG-Node 社区积极开发和维护。我们很乐意听取您的反馈。加入社区:
- 在 GitHub 上发布问题或功能请求
- 加入 Slack 上的 #sig-node 频道
- 订阅 SIG-Node 邮件列表
- 在 Twitter 上关注我们 @Kubernetesio 获取最新更新