k8s CRI 容器运行时接口介绍
CRI (Container Runtime Interface - 容器运行时接口)是 Kubernetes (具体说是kubelet) 定义的一套 gRPC API 标准,用于与容器运行时通信,命令它们启动和管理 Pod/容器。市面上有多重容器运行时,本文尝试厘清他们的关系,这是理解 Kubernetes 节点工作原理的关键。
容器运行时对比表
| 组件 (Component) | 官方网站 / 文档 | GitHub 仓库 | 核心作用 / 定位 |
|---|---|---|---|
| docker | docker.com | github.com/moby/moby | 一个完整的容器平台(引擎),用于构建、分发和运行容器。在 K8s 1.24 之前是默认运行时。 |
| containerd | containerd.io | github.com/containerd/containerd | 一个工业标准的容器运行时。它本身原生实现了 CRI,负责管理容器的整个生命周期。 |
| cri-o | cri-o.io | github.com/cri-o/cri-o | 一个轻量级的CRI 运行时。它的唯一目的就是作为 Kubernetes 的容器运行时,专门为 K8s 而生。 |
| cri-docker | (无独立官网) | github.com/Mirantis/cri-dockerd | 一个适配器(Shim)。它将 K8s 的 CRI 请求翻译成 Docker Engine 的 API 请求。 |
| ctr | (containerd 的一部分) | github.com/containerd/containerd | containerd 的专用命令行工具 (CLI)。用于直接与 containerd 交互,绕过了 CRI。 |
| crictl | (K8s SIGs 的项目) | github.com/kubernetes-sigs/cri-tools | CRI 的标准命令行工具 (CLI)。用于与任何实现了 CRI 的运行时(如 containerd, cri-o)进行交互。 |
为了理解它们的依赖关系,我们从 K8s 的 kubelet 开始,自上而下看。
安装运行时
无论您选择哪个运行时,在 Kubernetes 节点上都应执行以下准备步骤:
- 加载内核模块:
sudo modprobe overlay
sudo modprobe br_netfilter- 使模块持久化:
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF- 设置 sysctl 网络参数:
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF- 应用 sysctl 参数:
sudo sysctl --system安装 containerd
containerd 是最直接、最推荐的 K8s 运行时。
- 安装 containerd:
sudo apt-get update
# containerd 已经包含在 Ubuntu 的默认仓库中
sudo apt-get install containerd -y- 生成默认配置文件 (关键步骤):
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml- 配置 cgroup 驱动 (K8s 必须):
为了让
kubelet和containerd使用相同的 cgroup 驱动,您必须修改配置文件,将containerd的驱动设为systemd。 使用sed命令自动修改 (或手动编辑):
# 查找 [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
# 将 SystemdCgroup = false 改为 SystemdCgroup = true
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml- 重启并启用 containerd:
sudo systemctl restart containerd
sudo systemctl enable containerd安装 CRI-O (K8s 专用)
cri-o 是专为 K8s 设计的轻量级运行时,需要添加其官方仓库。
- 设置 OS 和 CRI-O 版本 (示例):
# 示例使用 Ubuntu 22.04
export OS=Ubuntu_22.04
# 示例使用 1.28 版本的 K8s (CRI-O 版本通常与之对应)
export VERSION=1.28- 添加 GPG 密钥和
apt仓库:
sudo apt-get update
sudo apt-get install -y curl gpg
# 添加密钥
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | sudo gpg --dearmor -o /usr/share/keyrings/libcontainers-archive-keyring.gpg
# 添加仓库
echo "deb [signed-by=/usr/share/keyrings/libcontainers-archive-keyring.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /" | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list- 安装 CRI-O:
sudo apt-get update
sudo apt-get install cri-o cri-o-runc -y- 启动并启用 CRI-O:
cri-o默认已配置为使用systemdcgroup 驱动,通常无需额外配置。
sudo systemctl daemon-reload
sudo systemctl enable crio
sudo systemctl start crio安装 Docker Engine (Docker CE)
这是安装完整的 Docker 平台。如果您选择这条路,K8s 还需要 cri-docker (见选项四)。
- 卸载旧版本 (如果存在):
sudo apt-get remove docker docker-engine docker.io containerd runc- 设置 Docker 的
apt仓库:
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
# 添加 Docker 的官方 GPG 密钥
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# 添加仓库
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null- 安装 Docker Engine:
sudo apt-get update
# 注意:这会安装 docker-ce 并同时安装 containerd.io 作为其依赖
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y- 配置 cgroup 驱动 (K8s 必须):
与
containerd类似,Docker 默认不使用systemdcgroup 驱动。
# 创建或修改 /etc/docker/daemon.json
cat <<EOF | sudo tee /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF- 重启并启用 Docker:
sudo systemctl enable docker
sudo systemctl daemon-reload
sudo systemctl restart docker安装 cri-docker (Docker 适配器)
前提条件:必须已经完成了 “安装 Docker Engine”。
cri-docker 是一个适配器,它没有在标准的 apt 仓库中,需要从 GitHub 下载。
- 访问 GitHub Releases 页面:
- 下载
.deb安装包: 您需要找到与您的架构 (amd64) 和 Ubuntu 版本匹配的最新.deb包。 例如,下载0.3.10版本 (请检查最新版):
# 检查最新版本替换文件名
wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.10/cri-dockerd_0.3.10.3-0.ubuntu-jammy_amd64.deb- 安装
.deb包:
sudo dpkg -i cri-dockerd_0.3.10.3-0.ubuntu-jammy_amd64.deb- 验证服务:
安装
.deb包后,systemd服务会自动配置并启动。
sudo systemctl status cri-docker.service
# 您可能还会看到 cri-docker.socket 也在运行
sudo systemctl status cri-docker.socketKubelet 与 CRI 的关系
kubelet 是 K8s 在每个节点上的代理。当需要创建 Pod 时,kubelet 会通过 CRI API 发出指令(例如 “RunPodSandbox”)。它不关心谁在监听这个 API,只要对方能听懂 CRI 就行。
这里就出现了两个主要阵营:
- 原生 CRI 运行时 (主流):
containerd和cri-o。 - 通过适配器的运行时:
cri-docker+docker。
运行时阵营分析
containerd
containerd (现代 K8s 的默认选项):
containerd是一个高级运行时,它本身就包含一个 CRI 插件。kubelet可以直接与它通信。containerd收到指令后,会调用一个低级运行时(如runc)来创建和运行容器进程。- 依赖链:
kubelet-> (CRI API) ->containerd->runc
kubelet 使用使用 containerd (默认/推荐)的依赖关系图:
[ Kubelet ] <-- (CRI API) --> [ containerd ] --> [ runc ] --> (创建容器)
# 调试工具
[ crictl ] <-- (CRI API) --> [ containerd ]
[ ctr ] <-- (原生 API) ---> [ containerd ]cri-o
cri-o (为 K8s 而生):
cri-o的设计目标就是只实现 CRI,不多也不少。它非常轻量级。- 和
containerd一样,它也需要调用低级运行时(如runc)来干活。 - 依赖链:
kubelet-> (CRI API) ->cri-o->runc
docker + cri-docker
docker + cri-docker (传统方式):
docker(即dockerd守护进程) 本身并不实现 CRI。它有自己的一套 API。- 在 K8s 1.24 之前,
kubelet内部有一个叫dockershim的模块来做这个翻译。 - K8s 1.24 移除了
dockershim后,如果你还想用 Docker Engine,就必须安装cri-docker这个外部适配器。 - 讽刺的是: Docker Engine 本身现在也使用
containerd作为其底层的运行时。 - 依赖链:
kubelet-> (CRI API) ->cri-docker-> (Docker API) ->dockerd->containerd->runc
关键点: 正如你所见,docker + cri-docker 的调用链是最长的。这也是为什么社区转向 containerd 和 cri-o 的原因之一:路径更短,更高效。
kubelet 使用使用 cri-docker (为了兼容旧的 Docker Engine)的依赖关系图:
(Docker API) (containerd API)
[ Kubelet ] <-- (CRI API) --> [ cri-docker ] --> [ dockerd ] --> [ containerd ] --> [ runc ]
# 调试工具
[ crictl ] <-- (CRI API) --> [ cri-docker ]
[ docker ] <-- (Docker API) --> [ dockerd ]命令行工具阵营分析
这是最容易混淆的地方。我们有两个工具 ctr 和 crictl,它们都是用来调试的,但目标完全不同。
-
crictl(K8s 管理员的标准工具)- 作用: 这是一个CRI 客户端。它模拟
kubelet的行为,通过 CRI API 与运行时通信。 - 它能连谁? 它可以连接到
containerd、cri-o或cri-docker,只要对方开放了 CRI gRPC 套接字(socket)。 - 使用场景: 在 K8s 节点上排查问题,例如 “为什么我的 Pod 起不来?"。
crictl ps会显示 K8s 视角下的所有容器。 - 依赖链:
crictl-> (CRI API) -> (containerd或cri-o或cri-docker)
- 作用: 这是一个CRI 客户端。它模拟
-
ctr(containerd 工程师的专用工具)- 作用: 这是
containerd的原生客户端。它不通过 CRI API,而是直接和containerd的原生 API 通信。 - 它能连谁? 只能连接到
containerd。 - 使用场景: 深度调试
containerd自身的问题。ctr containers ls显示的是containerd视角下的所有容器(这可能包括 K8s 的容器,也可能包括其他非 K8s 的容器)。 - 依赖链:
ctr-> (containerd 原生 API) ->containerd
- 作用: 这是
-
docker(Docker 用户的工具)docker ps是大家最熟悉的。它通过 Docker API 与dockerd通信。如果你使用了cri-docker方案,docker ps的结果应该和crictl ps类似。
容器运行时 Socket 位置速查表
以下是这些组件在标准 Linux 系统上的默认 socket 路径:
| 组件 | 默认 Socket 路径 | 作用 / 客户端 |
|---|---|---|
| docker | /var/run/docker.sock |
Docker 引擎的原生 API。docker CLI 使用它。 |
| containerd | /run/containerd/containerd.sock |
CRI 接口 + 原生 API。kubelet、crictl 和 ctr 都使用它。 |
| cri-o | /run/crio/crio.sock |
CRI 接口。kubelet 和 crictl 使用它。 |
| cri-docker | /var/run/cri-dockerd.sock |
CRI 接口 (适配器)。kubelet 和 crictl 使用它。 |
理解这些 socket 文件的位置对于在 Kubernetes 节点上进行调试至关重要,因为它们是 kubelet 和 crictl 等工具与容器运行时通信的连接点。
Docker (docker)
- 路径:
/var/run/docker.sock - 说明: 这是最广为人知的 socket。它暴露的是 Docker Engine 的原生 API,而不是 Kubernetes 的 CRI。
- 谁在用:
docker命令行工具 (例如docker ps),以及所有需要与 Docker 引擎交互的第三方应用(如 CI/CD 工具)。 - 注意:
kubelet(自 1.24 版起) 不能直接使用这个 socket。
containerd
- 路径:
/run/containerd/containerd.sock - 说明:
containerd将其所有功能,包括 CRI 插件,都统一暴露在这个 gRPC socket 上。 - 谁在用:
- Kubelet (CRI):
kubelet将此路径作为 CRI 端点,与其通信以管理 Pod。 - crictl (CRI): 这是
crictl调试时连接的标准路径。 - ctr (原生):
containerd的原生调试工具ctr也使用这个 socket 来绕过 CRI,直接访问 containerd 的 API。
CRI-O (cri-o)
- 路径:
/run/crio/crio.sock - 说明:
cri-o是一个纯粹的 CRI 实现,它创建此 socket 专门用于kubelet的连接。 - 谁在用:
- Kubelet (CRI):
kubelet连接此路径以发出 CRI 指令。 - crictl (CRI):
crictl在cri-o节点上会连接此路径进行调试。
cri-docker (适配器)
-
路径:
/var/run/cri-dockerd.sock -
说明:
cri-docker作为一个翻译器运行。它创建这个 socket 来假装自己是一个 CRI 运行时。 -
谁在用:
-
Kubelet (CRI):
kubelet以为自己正在与一个标准 CRI 运行时对话。 -
crictl (CRI):
crictl也可以连接到这个 socket。 -
工作流: 当
kubelet向/var/run/cri-dockerd.sock发送 CRI 请求时,cri-docker会将该请求翻译成 Docker 原生 API,然后再发送给/var/run/docker.sock。
crictl 如何与不同容器运行通信
crictl 工具是 Kubernetes 管理员的瑞士军刀,它需要知道要连接到哪个 CRI socket。
如果不指定,crictl 会按顺序尝试连接一个默认列表,这个列表正好包括了上述的 CRI 运行时:
unix:///run/containerd/containerd.sockunix:///run/crio/crio.sockunix:///var/run/cri-dockerd.sockunix:///var/run/dockershim.sock(已被废弃的 K8s 1.24 之前的内置 Docker 适配器)
可以通过编辑配置文件 /etc/crictl.yaml 或设置环境变量 CONTAINER_RUNTIME_ENDPOINT 来显式指定正确的 socket 路径。
总结
containerd和cri-o是实现了 CRI 接口的运行时。cri-docker是一个适配器,让 K8s 能通过 CRI 与不实现 CRI 的docker引擎对话。crictl是 K8s 标准的 CRI 调试工具,能和所有 CRI 运行时工作。ctr是containerd专用的原生调试工具,它不关心 CRI。