Kubernetes CRI

在 Kubernetes 最底层负责容器启停的软件,我们称之为“容器运行时”。最广为人知的容器运行时是 Docker,但实际上这个领域百花齐放。为了使 Kubernetes 具备更强的扩展能力,我们为容器运行时搞出了新的插件 API,叫做 CRI。

 

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

每种容器运行时都有它的特点,很多用户希望 Kubernetes 支持更多的运行时。在 Kubernetes 1.5 发布之际,我们很自豪引入了容器运行时接口——这是一种能让 kubelet 无需重新编译就可以广泛使用各种容器运行时的插件接口。CRI y由 protocol buffers 和 gRPC API 还有 streaming 库构成,正在火热开发中。CRI 在 Kubernetes 1.5 中发布 Alpha 版。

支持更换容器运行时在 Kubernetes 中已经不是什么新概念了。在 1.3 发布时,我们宣布 rktnetes 项目来将 rkt 容器运行时作为 Docker 容器运行时以外的选项。但是 Docker 与 rkt 深度整合在了 kubelet 源码中,这种集成需要深度理解 Kubernetes 内部,并给 Kubernetes 社区带造成了大量的维护开销,这些因素对新的容器运行时形成了高墙。通过提供明确定义的抽象层,我们清除了障碍并使开发人员专注于构建他们自己的容器运行时。这是朝着真正实现可插拔的容器运行时和构建更健康的生态系统迈出的一小步,但却很重要。

 

CRI 概述

kubelet 和容器运行时(或者是运行时的 CRI shim)通过 Unix 套接字走 gRPC 通讯。

 

protocol buffers API 包括两种 gRPC 服务,镜像服务(ImageService)和运行时服务(RuntimeService)。镜像服务提供了从镜像仓库拉取镜像、检查、删除镜像的接口;运行时服务提供了管理容器和 Pod 生命周期以及与容器交互(exec/attach/port-forward)的接口。一个同时管理镜像和容器的单运行时(例如 Docker 和 rkt)可以用一个套接字同时提供这两种服务。可以通过 --container-runtime-endpoint 和 --image-service-endpoint 参数在 kubelet 中设置此套接字,默认是 unix:///var/run/dockershim.sock 即默认使用本地的 Docker 作为容器运行时。

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 中,这个环境被称为 Pod 沙盒。我们有意为容器运行时预留一些空间,让它们根据内部操作方式对 Pod 沙盒进行不同的实现。对于基于 hypervisor 的运行时,Pod 沙盒可能代表虚拟机;对于其他的,比如 Docker,它又可能是 Linux 命名空间。Pod 沙盒必须遵循 Pod 资源规范,在 v1alpha1 版 API 中,这是通过在 由 kubelet 创建并传递给运行时的 pod 级 cgroup 内的启动所有进程来实现的。

在启动 Pod 前,kubelet 调用 RuntimeService.RunPodSandbox 来创建环境。这包括了为 Pod 设置网络(分配 IP)。当 Pod 沙盒激活,单个容器可以独立地创建/启动/停止/删除。要删掉 Pod,kubelet 会先停止并删除容器。

kubelet 负责通过 RPC 接口管理容器生命周期,执行容器生命周期钩子函数和 liveness/readiness 检查,同时遵循 Pod 的重启策略。

 

为什么必须要有一个以容器为中心的接口?

Kubernetes 使用声明式 API 来描述 Pod 资源。我们考虑过的一种可能的设计是 CRI 在其抽象中重用声明的 Pod 对象,让容器运行时自由地实现自己的控制逻辑来达到期望的状态。这将极大地简化 API 并使得 CRI 能够与更多的运行时协作。我们在设计初期就讨论了这种方法,出于几个原因决定不采用它。首先有许多 Pod 级的特性与特定的机制(崩溃回退逻辑),重新实现对所有运行时来说都是很大的负担。其次更重要的是,Pod 规范仍在高速发展中。诸多新功能(init container)不需要动底层运行时,只要 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)来支持这些功能。使用主机上的工具并不是一个好的解决方案因为大多数工具都基于 Linux 命名空间。在 CRI 中,我们明确在 API 中定义了这些调用来允许运行时自行实现。

另一个潜在的问题是所有容器的流式请求都经过 kubelet 有可能会成为节点上网络流量的瓶颈。在设计 CRI 时,我们听取了这个反馈使运行时消除中间人。容器运行时可以根据请求启动一个单独的流式服务器并将地址返回给 kubelet,kubelet 然后将这个信息再返回给 Kubernetes 的 apiserver,它会打开直接与运行时提供的服务器相连的流连接,并通过它跟客户端连通。

 

容器运行时实例

CRI 维护者 主要特性 容器引擎
dockershim Kubernetes 内置实现、特性最新 Docker
cri-o cri-o OCI 标准不需要 Docker OCI(runc、kata、gVisor…)
cri-containerd Containerd 基于 containerd 不需要 Docker OCI(runc、kata、gVisor…)
frakti Kubernetes 虚拟化容器 hyperd、Docker
rktlet Kubernetes 支持 rkt rkt
PouchContainer Alibaba 富容器 OCI(runc、kata…)
Virtlet Mirantis 虚拟机和 qcow2 镜像 libvirt(kvm)

CRI 还有很多其他方面的内容没有在本文中提及。请参阅设计文档和提案,了解所有细节。

 

发表评论