CRI

CRI

简介

Kubernetes 运行时是支持容器运行时接口(CRI) 的高级容器运行时,CRI 在 Kubernetes 1.5 中引入,并且充当 kubelet 和容器运行时之间的桥梁,并且希望像 Docker、containerd 这样的高级容器运行时实现 CRI 接口。

预期容器运行时支持镜像管理并且支持 Kubernetes Pod,并管理各个容器,所以从要求中也能看出来 Kubernetes 运行时要求的必须是 high-level 运行时,low-level 运行时缺少必要的功能。

为了了解更多关于 CRI 的知识,有必要了解 Kubernetes 的架构,Kubelet 是一个位于 Kubernetes 集群中每个工作节点上的代理,Kubelet 负责管理节点的容器工作负载,在实际运行中,kubelet 使用 CRI 在同一节点上运行的容器运行时进行通信,有个 CRI 这个抽象层,我们可以随时将实际的容器运行时切换到其他实现了 CRI 接口的容器运行时,而不必局限某一种容器运行时,也不必内置于 kubelet 中。

kubernetes 中关于 CRI 架构图

image

CRI 运行时例子

containerd

在前面的文章中提到过 containerd,它是一个 high-level 运行时,containerd 可能是现在最流行的容器运行时,他将 CRI 作为一个插件实现并且默认开启,它默认使用 unix 套接字进行监听,你可以像如下这样使用 crictl 连接 containerd:

1
2
3
cat <<EOF | sudo tee /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
EOF

这是一个有趣的高级运行时,因为它从 1.2 开始通过 “runtime handler” 支持多个 low-level 运行时,runtime handler 通过 CRI 规范进行交互,基于该 runtime handler 的 containerd 将通过 shim 来启动容器,它可以使用除了 runc 以外的其他 low-level 运行时来运行容器。比如像 gVisor、Kata Containers 或者 Nabla Containers。runtime handler 在 Kubernetes 1.12 中公开了一个 alpha 特性的 API 对象 RuntimeClass,更多的 containerd‘s shim 概念点击 这里

Docker

Docker 也是第一个实现 CRI 的,在 kubelet 和 Docker 之间通过 shim 来实现 CRI。同时 Docker 也将很多的功能剥离到 containerd 中实现了,现在也是通过 containerd 来支持 CRI 了,安装 Docker 的同时会同时安装 containerd,CRI 可以直接和 containerd 交互,因此 Docker 其实已经不再需要支持 CRI 了,因为默认集成了 containerd,所以我们可以直接安装 Docker 来间接安装 containerd。

cri-o

cri-o 是一个轻量级的容器运行时,它是 Kubernetes 特定的高级容器运行时,它支持 OCI compatible images 管理和从 OCI compatible images registry 中拉取镜像,它支持 runc 和 Clear Containers 作为 low-level 运行时。理论上支持其他 OCI 兼容的 low-level 运行时,但是依赖于 runc OCI command line interface 的兼容性,因此在实践中不如 containerd 的 shim API 灵活。

CRI 规范

CRI 是 protocol buffersgRPC API。该规范是在kubelet 下 Kubernetes 仓库中的 protobuf file 中定义的。

CRI 中定义了几种远程调用(RPC)和消息类型。下面这几种 RPC 方法:

  • ImageService.PullImage:拉取镜像
  • RuntimeService.RunPodSandbox:创建 pod
  • RuntimeService.CreateContainer:创建容器
  • RuntimeService.StartContainer:启动容器
  • RuntimeService.StopContainer:停止容器

例如,新建的 Kubernetes Pod 使用 CRI 的典型交互方式像下面这样(以我自己的伪 gRPC 方式,每个 RPC 都会得到一个更大的请求对象为了简单我对其进行了简化),RunPodSandbox 和 CreateContainer RPC 在他们的响应中返回 ID,并且将这个 ID 在后续请求中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ImageService.PullImage({image: "image1"})
ImageService.PullImage({image: "image2"})
podID = RuntimeService.RunPodSandbox({name: "mypod"})
id1 = RuntimeService.CreateContainer({
pod: podID,
name: "container1",
image: "image1",
})
id2 = RuntimeService.CreateContainer({
pod: podID,
name: "container2",
image: "image2",
})
RuntimeService.StartContainer({id: id1})
RuntimeService.StartContainer({id: id2})

我们可以直接使用 crictl 工具直接和 CRI 运行时进行交互,crictl 可以让我们直接从命令行使用 gRPC 发送消息到 CRI 运行时。我们可以使用 crictl 来调试和测试 CRI 运行时,而不用启动成熟的 kubelet 或者 Kubernetes 集群。可以通过 github 上的 cri-tools 版本页面下载 crictl 二进制文件来获得它。

可以通过配置 /etc/crictl.yaml 文件来配置 crictl。在这里你可以指定运行时的 gRPC 端点为 Unix 套接字文件 (unix:///path/to/file) 或者一个 TCP 端点(tcp://:),我们使用 containerd 来做如下一个示例:

1
2
3
cat <<EOF | sudo tee /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
EOF

或者你可以在每个命令指定运行时的端点,比如下面这样:

1
crictl --runtime-endpoint unix:///run/containerd/containerd.sock …

我们使用 crictl 创建一个只包含一个 container 的 Pod,首先我们应该先使用 crictl 拉取一个镜像,如果本地没有存储镜像就不能够启动。

1
sudo crictl pull nginx

接下来创建一个 Pod 创建请求,可以将其作为 json 文件进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
cat <<EOF | tee sandbox.json
{
"metadata": {
"name": "nginx-sandbox",
"namespace": "default",
"attempt": 1,
"uid": "hdishd83djaidwnduwk28bcsb"
},
"linux": {
},
"log_directory": "/tmp"
}
EOF

接下来创建 pod sandbox,我们将把 sandbox 的 id 存储为变量 SANDBOX_ID

1
SANDBOX_ID=$(sudo crictl runp --runtime runsc sandbox.json)

接下来我们可以使用 JSON 文件创建容器创建请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
cat <<EOF | tee container.json
{
"metadata": {
"name": "nginx"
},
"image":{
"image": "nginx"
},
"log_path":"nginx.0.log",
"linux": {
}
}
EOF

然后我们可以在前面创建的 Pod 中创建启动容器。

1
2
3
4
{
CONTAINER_ID=$(sudo crictl create ${SANDBOX_ID} container.json sandbox.json)
sudo crictl start ${CONTAINER_ID}
}

也可以查看正在运行的 pod 或者 container

1
2
3
sudo crictl inspectp ${SANDBOX_ID}

sudo crictl inspect ${CONTAINER_ID}

使用如下的命令可以删除 container 和 pod

1
2
3
4
5
6
7
8
9
{
sudo crictl stop ${CONTAINER_ID}
sudo crictl rm ${CONTAINER_ID}
}

{
sudo crictl stopp ${SANDBOX_ID}
sudo crictl rmp ${SANDBOX_ID}
}

相关链接

Kubernetes Container Runtimes & CRI