k8s CRI 容器运行时接口介绍

发布时间: 更新时间: 总字数:3287 阅读时间:7m 作者: IP上海 分享 网址

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 节点上都应执行以下准备步骤:

  1. 加载内核模块:
sudo modprobe overlay
sudo modprobe br_netfilter
  1. 使模块持久化:
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
  1. 设置 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
  1. 应用 sysctl 参数:
sudo sysctl --system

安装 containerd

containerd 是最直接、最推荐的 K8s 运行时。

  1. 安装 containerd:
sudo apt-get update
# containerd 已经包含在 Ubuntu 的默认仓库中
sudo apt-get install containerd -y
  1. 生成默认配置文件 (关键步骤):
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
  1. 配置 cgroup 驱动 (K8s 必须): 为了让 kubeletcontainerd 使用相同的 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
  1. 重启并启用 containerd:
sudo systemctl restart containerd
sudo systemctl enable containerd

安装 CRI-O (K8s 专用)

cri-o 是专为 K8s 设计的轻量级运行时,需要添加其官方仓库。

  1. 设置 OS 和 CRI-O 版本 (示例):
# 示例使用 Ubuntu 22.04
export OS=Ubuntu_22.04
# 示例使用 1.28 版本的 K8s (CRI-O 版本通常与之对应)
export VERSION=1.28
  1. 添加 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
  1. 安装 CRI-O:
sudo apt-get update
sudo apt-get install cri-o cri-o-runc -y
  1. 启动并启用 CRI-O: cri-o 默认已配置为使用 systemd cgroup 驱动,通常无需额外配置。
sudo systemctl daemon-reload
sudo systemctl enable crio
sudo systemctl start crio

安装 Docker Engine (Docker CE)

这是安装完整的 Docker 平台。如果您选择这条路,K8s 还需要 cri-docker (见选项四)。

  1. 卸载旧版本 (如果存在):
sudo apt-get remove docker docker-engine docker.io containerd runc
  1. 设置 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
  1. 安装 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
  1. 配置 cgroup 驱动 (K8s 必须):containerd 类似,Docker 默认不使用 systemd cgroup 驱动。
# 创建或修改 /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
  1. 重启并启用 Docker:
sudo systemctl enable docker
sudo systemctl daemon-reload
sudo systemctl restart docker

安装 cri-docker (Docker 适配器)

前提条件:必须已经完成了 “安装 Docker Engine”。

cri-docker 是一个适配器,它没有在标准的 apt 仓库中,需要从 GitHub 下载。

  1. 访问 GitHub Releases 页面:
  1. 下载 .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
  1. 安装 .deb 包:
sudo dpkg -i cri-dockerd_0.3.10.3-0.ubuntu-jammy_amd64.deb
  1. 验证服务: 安装 .deb 包后,systemd 服务会自动配置并启动。
sudo systemctl status cri-docker.service
# 您可能还会看到 cri-docker.socket 也在运行
sudo systemctl status cri-docker.socket

Kubelet 与 CRI 的关系

kubelet 是 K8s 在每个节点上的代理。当需要创建 Pod 时,kubelet通过 CRI API 发出指令(例如 “RunPodSandbox”)。它不关心在监听这个 API,只要对方能听懂 CRI 就行。

这里就出现了两个主要阵营:

  1. 原生 CRI 运行时 (主流): containerdcri-o
  2. 通过适配器的运行时: 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 的调用链是最长的。这也是为什么社区转向 containerdcri-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 ]

命令行工具阵营分析

这是最容易混淆的地方。我们有两个工具 ctrcrictl,它们都是用来调试的,但目标完全不同。

  • crictl (K8s 管理员的标准工具)

    • 作用: 这是一个CRI 客户端。它模拟 kubelet 的行为,通过 CRI API 与运行时通信。
    • 它能连谁? 它可以连接到 containerdcri-ocri-docker,只要对方开放了 CRI gRPC 套接字(socket)。
    • 使用场景: 在 K8s 节点上排查问题,例如 “为什么我的 Pod 起不来?"。crictl ps 会显示 K8s 视角下的所有容器。
    • 依赖链: crictl -> (CRI API) -> (containerdcri-ocri-docker)
  • 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 引擎的原生 APIdocker CLI 使用它。
containerd /run/containerd/containerd.sock CRI 接口 + 原生 APIkubeletcrictlctr 都使用它。
cri-o /run/crio/crio.sock CRI 接口kubeletcrictl 使用它。
cri-docker /var/run/cri-dockerd.sock CRI 接口 (适配器)。kubeletcrictl 使用它。

理解这些 socket 文件的位置对于在 Kubernetes 节点上进行调试至关重要,因为它们是 kubeletcrictl 等工具与容器运行时通信的连接点

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): crictlcri-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 运行时:

  1. unix:///run/containerd/containerd.sock
  2. unix:///run/crio/crio.sock
  3. unix:///var/run/cri-dockerd.sock
  4. unix:///var/run/dockershim.sock (已被废弃的 K8s 1.24 之前的内置 Docker 适配器)

可以通过编辑配置文件 /etc/crictl.yaml 或设置环境变量 CONTAINER_RUNTIME_ENDPOINT 来显式指定正确的 socket 路径。

总结

  • containerdcri-o 是实现了 CRI 接口的运行时
  • cri-docker 是一个适配器,让 K8s 能通过 CRI 与实现 CRI 的 docker 引擎对话。
  • crictl 是 K8s 标准的 CRI 调试工具,能和所有 CRI 运行时工作。
  • ctrcontainerd 专用的原生调试工具,它不关心 CRI。
本文总阅读量 次 本站总访问量 次 本站总访客数
Home Archives Categories Tags Statistics