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

在 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,以及与容器交互的调用(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 进行不同的解释。对于基于虚拟机管理程序的运行时,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 容器)不需要对底层容器运行时进行任何更改,只要 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 服务器,后者直接打开与运行时提供的服务器的流连接,并将其连接到客户端。

此博文中未涵盖 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 集成,您只需启动 Kubernetes API 服务器,并使用 --feature-gates=StreamingProxyRedirects=true 启用新的流重定向功能,然后启动 kubelet 并使用 --experimental-cri=true。

除了几个缺失的功能之外,新的集成已经通过了主要的端到端测试。我们计划尽快扩大测试覆盖范围,并希望社区报告任何问题,以帮助进行过渡。

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.

...

_请注意,kubectl attach/exec/port-forward 尚不能在启用了 CRI 的 minikube 中工作,但这个问题将在较新版本的 minikube 中解决。_

社区

CRI 由 Kubernetes SIG-Node 社区积极开发和维护。我们很乐意听到您的反馈。加入社区