这篇文章已发布一年多。较旧的文章可能包含过时内容。请检查页面中的信息自发布以来是否仍正确。

引入 Kubernetes 的容器运行时接口 (CRI)

编者注:本文是关于 Kubernetes 1.5 新特性的深度系列文章之一。

在 Kubernetes 节点的最低层,是用于启动和停止容器等的软件。我们称之为“容器运行时(Container Runtime)”。最广为人知的容器运行时是 Docker,但它并非唯一。事实上,容器运行时领域一直在快速演进。作为使 Kubernetes 更具可扩展性的努力的一部分,我们一直在研究 Kubernetes 中针对容器运行时的新插件 API,称为“CRI”。

什么是 CRI?Kubernetes 为什么需要它?

每种容器运行时都有其自身的优势,许多用户要求 Kubernetes 支持更多运行时。在 Kubernetes 1.5 版本中,我们自豪地推出容器运行时接口 (CRI)——一种插件接口,它使得 kubelet 能够使用各种容器运行时,而无需重新编译。CRI 包含protocol buffersgRPC 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 作为服务器。

protocol buffers API 包含两个 gRPC 服务:ImageService 和 RuntimeService。ImageService 提供从仓库拉取镜像、检查和移除镜像的 RPC 调用。RuntimeService 包含管理 Pod 和容器生命周期的 RPC 调用,以及与容器交互(exec/attach/port-forward)的调用。管理镜像和容器的单体容器运行时(例如 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 有一个带有 Pod 资源的声明式 API。我们曾考虑过的一种可能的设计是让 CRI 在其抽象中重用声明式 Pod 对象,赋予容器运行时实现和执行自己的控制逻辑以达到期望状态的自由。这将极大地简化 API,并使 CRI 能够与更广泛的运行时配合工作。我们在设计早期阶段讨论了这种方法,但出于几个原因最终决定放弃。首先,kubelet 中有许多 Pod 级别的特性和特定机制(例如,崩溃循环回退逻辑),让所有运行时重新实现将是巨大的负担。其次,更重要的是,Pod 规范当时(现在也是)仍在快速演进。许多新特性(例如,init containers)不需要对底层容器运行时进行任何更改,只要 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 提供允许用户与 Pod 及其中的容器交互的功能(例如 kubectl exec/attach/port-forward)。目前的 Kubelet 通过调用容器运行时的原生方法或使用节点上可用的工具(例如 nsenter 和 socat)来支持这些功能。使用节点上的工具不是一个可移植的解决方案,因为大多数工具都假定 Pod 使用 Linux 命名空间隔离。在 CRI 中,我们在 API 中明确定义了这些调用,以允许运行时特定的实现。

当前 kubelet 实现的另一个潜在问题是,kubelet 处理所有流式请求的连接,因此可能成为节点网络流量的瓶颈。在设计 CRI 时,我们采纳了这些反馈,允许运行时消除中间环节。容器运行时可以在接收到请求后启动一个独立的流式服务器(并且可能将资源使用计入 Pod!),然后将该服务器的位置返回给 kubelet。Kubelet 随后将此信息返回给 Kubernetes API Server,API Server 会直接向运行时提供的服务器打开一个流式连接,并将其连接到客户端。

CRI 还有许多其他方面未在本文中介绍。请参阅设计文档和提案列表以了解所有详细信息。

当前状态

尽管 CRI 仍处于早期阶段,但已经有一些项目正在开发中,旨在 使用 CRI 集成容器运行时。以下是一些示例:

如果您有兴趣尝试这些替代运行时,可以关注各个仓库以获取最新进展和说明。

对于有兴趣集成新容器运行时的开发者,请参阅开发者指南以了解已知限制和 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 Server 以启用新的流式重定向功能,然后使用 --experimental-cri=true 启动 kubelet 即可。

除了少量缺失的功能外,新的集成已持续通过主要的端到端测试。我们计划尽快扩大测试覆盖范围,并鼓励社区报告任何问题,以帮助过渡。

使用 Minikube 体验 CRI

如果您想尝试新的集成,但还没有时间在云中启动新的测试集群,minikube 是一个快速启动本地集群的好工具。在开始之前,请按照说明下载并安装 minikube。

  1. 检查可用的 Kubernetes 版本并选择最新的 1.5.x 版本。我们将使用 v1.5.0-beta.1 作为示例。
$ minikube get-k8s-versions
  1. 使用内置的 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` 设置 minikube 用于启动节点的 iso 镜像。示例中使用的镜像是

  1. 检查 minikube 日志以确认 CRI 已启用。
$ minikube logs | grep EnableCRI

I1209 01:48:51.150789    3226 localkube.go:116] Setting EnableCRI to true on kubelet.
  1. 创建一个 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 社区积极开发和维护。我们非常乐意听取你的反馈。要加入社区: