简介:cri-containerd-1.7.23-linux-amd64.tar 是 containerd 1.7.23 的 Linux AMD64 版运行时压缩包,源自云原生计算基金会(CNCF)维护的 containerd 项目,面向 Kubernetes 集群管理员、容器运维人员和 CRI 实现学习者,可直接用于在 x86_64 架构主机上部署和接管容器生命周期。压缩包共包含19个文件,大小约101.22MB,既有 containerd、ctr、crictl、runc、containerd-shim 系列等容器运行时核心二进制,也有 yaml 配置、systemd service 模板、环境变量文件与启动脚本等配套内容,目录按 etc、usr、opt 标准路径组织,便于离线部署、按需裁剪和二次定制,整体结构紧凑,关键组件一应俱全。其中 cri-containerd.DEPRECATED.txt 说明了旧版 cri-containerd 的弃用背景与迁移提示;crictl.yaml 可对接容器运行时进行镜像和 Pod 管理,systemd 服务模板则可直接用于注册后台守护进程。目前已有261人学习该资源,适合作为生产环境安装对照、组件结构梳理和容器启动排错的参考包,能帮助快速搭建 containerd 运行时环境,并为迁移到新版 CRI 运行时或自研容器调度场景提供起点。
1. cri-containerd 不只是一个压缩包,它是 kubelet 和容器之间的“运行时接口”
拿到cri-containerd-1.7.23-linux-amd64.tar这个文件名时,你多半已经在给 Kubernetes 节点准备容器运行时了。它不是一个普通的 containerd 发行包,而是 containerd 面向 Kubernetes CRI(Container Runtime Interface)的完整发布形态:里面除了 containerd 主程序,还带上了 crictl、CNI 插件、systemd 服务文件,装上之后 kubelet 可以直接通过 CRI socket 跟它对话,不再需要 Docker daemon。对集群运维和 kubeadm 初始化场景来说,这个 tar 解决的是“离线准备一套 kubelet 认可的运行时”这个落地问题,免去手动拼装各组件的麻烦。适合正在从 Docker 切换运行时、或者要新装一套自建集群的工程师。一个前提认识很重要:kubelet 是客户端,containerd 是服务端,两者通过 gRPC 通信,这种身份关系决定了后面所有排查方向。
2. 拆开 cri-containerd tar 包:先看懂文件清单和职责分工
2.1 先看包里有什么:解压前用 tar -tf 读目录结构
拿到包以后,我习惯先不解压,直接看一下 tar 的目录清单。这一步能让你在十几秒内确认包的用途,也避免后面解压错位置。
cd /root/deploy tar -tf cri-containerd-1.7.23-linux-amd64.tar输出会是一串相对路径,开头通常是usr/local/bin/...、etc/systemd/system/...、opt/cni/bin/...这种结构。注意这些路径是相对的,不带前导斜杠,这意味着解压时必须指定目标根目录,否则它会按照包里的相对路径直接落到当前目录下。
从结构上能看出这个 tar 设计成“可直接解压到根目录生效”的形态。里面至少有三部分:可执行文件放在/usr/local/bin/,systemd 服务单元放在/etc/systemd/system/,CNI 插件的二进制放在/opt/cni/bin/。这种布局和源码编译后手动安装的套路一致,也解释了为什么很多老教程会让你直接tar -C / -xzf,而不是解压到某个自定义目录再配路径。
对比 RPM 和 DEB 包,这种 tar 发布形式的好处是版本隔离干净、卸载简单,删掉几个目录和 systemd 文件就回到原状,适合内网离线环境一次分发到位。缺点是它不会自动处理依赖,比如缺少runc、containerd-shim相关的可执行权限时,你得自己补。
2.2 每个二进制文件在一条调用链上的位置
包里的核心可执行文件我梳理一下,它们在 kubelet 启动一个容器时是这么协作的:
kubelet -> CRI gRPC 请求 unix:///run/containerd/containerd.sock -> containerd 的 CRI 插件 -> containerd 核心 API -> containerd-shim-runc-v2 -> runc 创建并运行容器进程containerd是这个链路的大总管,它负责镜像的 pull、解压、快照挂载以及容器生命周期的管理。但单靠它还不足以对接 Kubernetes,必须启用内嵌的 CRI 插件,这个插件监听在同一个 socket 上,kubelet 发送的 CreateContainer、RunPodSandbox 等 CRI 请求由它转换成 containerd 自己的 API 调用。
containerd-shim-runc-v2是容器进程的直接管理者。containerd 本身不直接 fork 用户进程,而是通过 shim 进程隔离出来,这样即使 containerd 重启,已经运行的容器进程也不会被连带干掉。这也是它的守护进程模型比 Docker 少一层嵌套、故障隔离更干净的主要原因。
crictl和ctr是两个调试命令行工具:crictl 走的是 CRI 接口,能模拟 kubelet 的视角查看 Pod 和容器;ctr 走的是 containerd 原生接口,用于操作镜像和底层内容。排障时这两个工具要区分开,用 ctr 拉镜像不会经过 CRI 层的镜像配置,容易得到和你预期不一样的结果。
2.3 为什么是 containerd 1.7 LTS:选型理由与发布形态
1.7 是 containerd 的长期维护分支,很多生产环境里的 Kubernetes 1.24 以上集群都在用这一系版本。1.7.23 是其中一个修复版,主要意义在于它是经过较多生产环境验证的稳定点,对只想装一次就不乱动的运维来说比追最新 2.x 更稳妥。
至于发布形态,containerd 官方在 1.7 时代还会同步提供 cri-containerd 这种完整打包,到了 2.x 以后发布形式发生调整,所以不少老环境资料里看到的文件名才总是带cri-containerd。如果你未来看到新版本归档里没有 cri-containerd 字样,不是官方下架了,而是它换了一种打包方式。
这里我一般会提醒团队:理解这条调用链比记住安装命令更重要。后面遇到 Pod 一直 ContainerCreating、crictl ps 看不到容器、镜像 pull 卡住这类问题,本质上都是链路中某一环没接通,有了这个框架就能顺着 socket、配置、镜像源、网络插件一层层排查。
3. 在 linux-amd64 节点上装起来:解压、systemd 配置与首次启动
3.1 解压到根目录,而不是解压到 /usr/local
安装的第一步最容易翻车。这个 tar 里的路径是usr/local/bin/...,不是usr/local/usr/local/bin/...,所以目标目录应该是系统根。
# 假设安装包已传到 /root/deploy cd /root/deploy tar -C / -xf cri-containerd-1.7.23-linux-amd64.tar如果你的实际文件名后缀是.tar.gz,那就用等效的tar -C / -zxvf cri-containerd-1.7.23-linux-amd64.tar.gz,z参数让 tar 先做 gzip 解压。两种写法在 Linux 上都是常用命令,核心是-C /这一项不能丢。
解压完成后检查几个路径,确认文件确实落到了期望位置:
ls -l /usr/local/bin/containerd systemctl cat containerd.service 2>/dev/null | head -20 ls /opt/cni/bin/ | headsystemctl cat能看到服务文件里的 ExecStart、Restart 策略。如果这一步发现containerd.service在/usr/local/etc/systemd/system/下面,说明刚才用了-C /usr/local,赶紧重新解压,不要手动去拷文件,后面维护起来会非常难受。
这个包不区分发行版,Ubuntu、Debian、Rocky Linux、openEuler 都能用。唯一要确认的是内核和 cgroup 版本,CentOS 7 这种老系统需要额外注意 cgroup 驱动是否匹配 systemd,这个坑留到第 5 章细说。
3.2 生成配置:不要让 containerd 用“裸默认”启动
解压完成后,先创建一个配置目录,再用 containerd 自带的默认配置生成器生成一份完整的 config.toml:
mkdir -p /etc/containerd containerd config default > /etc/containerd/config.toml这一步非常关键。containerd 在没有配置文件时会使用内置默认配置启动,CRI 插件默认启用,看起来能跑。但只要你创建一个/etc/containerd/config.toml,行为就变了:配置文件里显式包含的插件配置才生效。如果你创建一个空文件或者随手写了半截配置,CRI 插件可能没被启用,kubelet 就连不上了。
所以我的建议是永远用containerd config default生成,然后在这个基础上修改。生成后看一下文件尾部确认 CRI 插件段还在:
grep -n 'io.containerd.grpc.v1.cri' /etc/containerd/config.toml | head输出里能看到[plugins.'io.containerd.grpc.v1.cri']这样的段落,说明 CRI 插件在配置层面是启用的。接下来启动服务:
systemctl daemon-reload systemctl enable --now containerd systemctl status containerd --no-pagerenable --now是一次性完成开机自启和本次启动。如果 status 显示Active: active (running),说明服务起来了;如果提示 Unit not found,多半是服务文件路径不对,回到 3.1 检查解压位置。
此时观察一下 socket 是否生成:
ls -l /run/containerd/containerd.sock这个 socket 文件是 CRI 插件的对外入口,kubelet、crictl 都靠它通信。它不存在,后面一切免谈。
3.3 用 crictl 验证运行时本身:先于 kubelet 自证清白
服务启动了不代表 CRI 插件真的工作,最好先用 crictl 验证。cri-containerd 包自带 crictl,但它默认可能指向 Docker 的 socket,需要手工配置:
crictl config runtime-endpoint unix:///run/containerd/containerd.sock crictl config image-endpoint unix:///run/containerd/containerd.sock crictl versioncrictl version输出里会包含 RuntimeVersion 和 RuntimeName,看到RuntimeName: containerd说明 CRI 通道已经打通。再执行:
crictl info | grep -A5 'cgroupDriver'这里能确认 cgroup 驱动与 kubelet 是否一致。此时不要急着接 kubelet,先在运行时层面把基础验证做完,能给后面省大量时间。我一般会把 containerd 是否正常、crictl 是否连通、runc 是否可用这三件事看作一组前置检查,全绿之后才允许执行 kubeadm init 或 join。
4. 接入 kubelet:config.toml 必调参数与 CNI 网络插件落地
4.1 三个必调参数:SystemdCgroup、sandbox_image、镜像源
拿到默认配置后,不要直接拿去生产用,至少改三个参数。打开/etc/containerd/config.toml,按下面这张表对照调整:
| 参数 | 所在配置段 | 默认值 | 推荐值 | 说明 |
|---|---|---|---|---|
SystemdCgroup | [plugins.'io.containerd.grpc.v1.cri'.containerd.runtimes.runc.options] | false | true | 与 kubelet 的 cgroup 驱动保持一致,避免资源限制失效 |
sandbox_image | [plugins.'io.containerd.grpc.v1.cri'] | registry.k8s.io 的 pause 镜像 | 内网可拉取的 pause 镜像 | 基础设施容器必须能拉到,否则 Pod 起不来 |
endpoint | [plugins.'io.containerd.grpc.v1.cri'.registry.mirrors."docker.io"] | docker.io 直连 | 可用的镜像仓库源 | 生产环境必须能稳定拉公共镜像 |
具体到文件里,改动后的关键片段是:
[plugins.'io.containerd.grpc.v1.cri'] sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.8" [plugins.'io.containerd.grpc.v1.cri'.containerd.runtimes.runc] runtime_type = "io.containerd.runc.v2" [plugins.'io.containerd.grpc.v1.cri'.containerd.runtimes.runc.options] SystemdCgroup = true [plugins.'io.containerd.grpc.v1.cri'.registry.mirrors."docker.io"] endpoint = ["https://docker.m.daocloud.io"]可以看到SystemdCgroup放在了runc.options下面。它解决的是 systemd 作为 init 时 cgroup 驱动不一致的问题。kubelet 默认以systemd驱动管理 cgroup,如果 containerd 仍用 cgroupfs,两者各自维护一套 cgroup 状态,最终表现为容器 CPU、内存限额不生效,kubelet 反复报failed to update cgroup。
sandbox_image是 Pod 的 pause 基础设施容器镜像。它先于业务容器启动,用来持有网络命名空间。拉不到它,Pod 会一直卡在ContainerCreating。默认值通常指向 registry.k8s.io,如果你的节点网络访问不到,必须改成内网镜像仓库地址,或者预先用 ctr 把镜像导入本地。
镜像源的配置路径在 CRI 插件段下的registry.mirrors,这组endpoint会替换默认的 docker.io 拉取地址。改成内网源后记得测试,光写配置不验证等于没改。
4.2 kubelet 与 kubeadm 怎么指定 CRI socket
运行时准备好了,kubelet 侧还需要明确告诉它往哪个 socket 发请求。Kubernetes 1.24 之后已经默认不再内置 Docker 支持,必须启用 CRI 模式:
# kubelet 运行时参数 --container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock用 kubeadm 初始化时,它会在常见路径下探测运行时,包括 docker.sock 和 containerd.sock。如果节点上曾经装过 Docker,探测顺序可能会干扰结果,最稳妥的做法是显式传参:
kubeadm init --cri-socket=/run/containerd/containerd.sock加入集群的工作节点同样要传:
kubeadm join 192.168.10.10:6443 --token xxx \ --discovery-token-ca-cert-hash sha256:xxx \ --cri-socket=/run/containerd/containerd.sock这里有个容易忽略的细节:kubeadm 的--cri-socket不需要unix://前缀,直接写路径即可;而 kubelet 的--container-runtime-endpoint必须带unix://。两份配置格式不一样,混用会报failed to connect。
4.3 CNI 插件从 tar 到系统路径并规避网段冲突
cri-containerd包的好处是连 CNI 插件一并带了,不需要额外去下载 Calico 或 Cilium 的二进制。解压后插件在/opt/cni/bin/,列表里有 bridge、host-local、portmap、bandwidth、vlan 这些官方插件。
ls /opt/cni/bin/同时/etc/cni/net.d/10-containerd-net.conflist就是它自带的默认网络配置,基于 bridge 和 host-local 实现一个最简单的单机 CNI 网络。它适合快速验证,但不适合直接当生产集群网络用。原因很简单:它不跨节点,Pod IP 只在单机内有效。
实际部署时要注意它和集群级 CNI 的冲突。装 Calico 或 Cilium 时,安装器通常也会向/etc/cni/net.d/写入自己的配置文件。如果10-containerd-net.conflist存在,CNI 插件选择器可能会优先读到它,导致节点即使 Ready,跨节点 Pod 网络也不通。我一般的做法是先备份再移除默认 conflist,让集群级 CNI 接管:
mv /etc/cni/net.d/10-containerd-net.conflist /etc/cni/net.d/10-containerd-net.conflist.bak如果只是想本机验证,保留这个默认配置没问题,但要检查它的subnet是否与宿主机网段重叠:
cat /etc/cni/net.d/10-containerd-net.conflist | grep -A2 subnet常见默认值是10.244.0.0/24或者10.88.0.0/16,视版本而定。重叠时改掉再重启 containerd 即可。改完不要忘记重启容器网络相关组件,单改文件不会自动生效。
5. 上线避坑:crictl 连不上、cgroup 失效、pause 镜像拉不到的四个现场
5.1 crictl 报 connection refused,socket 文件却不出现
现象:containerd 服务是 running 状态,但crictl version检查失败,提示connection refused,/run/containerd/containerd.sock文件不存在。
原因:最普遍的是存在自定义 config.toml,但里面没有 CRI 插件配置段。记住这个行为差异:没有配置文件时 containerd 用内置默认值,CRI 默认启用;一旦有了配置文件,CRI 插件是否启用完全取决于配置里有没有[plugins.'io.containerd.grpc.v1.cri']这一节。手动拼配置的人最容易漏掉这段。
解决:删掉手写的配置文件,用containerd config default > /etc/containerd/config.toml重新生成,确认含有 cri 段后systemctl restart containerd。如果 restart 后 socket 仍然不出现,再看 containerd 日志里有没有 CRI 插件初始化失败的关键字,例如端口冲突或者权限问题。
5.2 kubelet 启动即失败,cgroup 驱动不一致
现象:kubelet 反复重启,日志里出现Failed to run kubelet和failed to update cgroup,节点状态一直NotReady。
原因:kubelet 以 systemd 作为 cgroup 驱动,而 containerd 配置里SystemdCgroup仍是默认 false。两套驱动各自在 cgroup 文件系统上操作,kubelet 更新 cgroup 时找不到 containerd 创建的结构,直接报错。这个错很“玄学”,因为节点上明明什么资源都没跑。
解决:在 config.toml 的runc.options段下面把SystemdCgroup = true,然后systemctl restart containerd。用 4.3 的校验命令确认驱动已切换成 systemd。这里不要图省事去改 kubelet 的 cgroupDriver 来将就 containerd,整个集群都按 systemd 驱动管理才是主流做法,单独一个节点特立独行只会埋雷。
5.3 Pod 卡在 ContainerCreating,pause 镜像拉不下来
现象:kubelet 已经连通 containerd,调度也正常,但 Pod 一直ContainerCreating。kubectl describe pod显示 sandbox image 拉取失败,错误类似ErrImagePull或ImagePullBackOff。
原因:sandbox_image还指向 registry.k8s.io 的默认地址,而这个地址在当前网络环境下不可达。pause 镜像不拉到,Pod Sandbox 创建不出来,后面所有业务容器都会阻塞。
解决:离线环境最靠谱的做法是提前在有网环境把 pause 镜像导出,再在目标节点导入到 containerd 本地仓库:
# 在能拉取镜像的机器上导出 ctr images export pause.tar registry.k8s.io/pause:3.8 # 在目标节点导入并打 tag ctr images import pause.tar ctr images tag registry.k8s.io/pause:3.8 <内网仓库地址>/pause:3.8然后把 config.toml 里的sandbox_image改成内网地址,restart containerd。注意ctr导入的是 containerd 原生镜像存储,crictl 也能看到,但整个过程中要保证 tag 和配置里写的完全一致,pause:3.8写错一个小版本都会重新卡住。
5.4 解压位置错误,服务文件和二进制散落两处
现象:systemctl start containerd报Unit containerd.service not found,但在/usr/local/etc/systemd/system/下能看到服务文件;containerd命令也找不到,因为二进制落到了/usr/local/usr/local/bin/。
原因:解压时把-C参数写成了/usr/local。tar 包里的相对路径前面再拼一层/usr/local,整个布局就全错了。
解决:这是最不该花时间排查的坑,直接重来一遍:
tar -C / -xf cri-containerd-1.7.23-linux-amd64.tar rm -rf /usr/local/usr/local /usr/local/etc /usr/local/opt systemctl daemon-reload之后用which containerd验证路径是否落在/usr/local/bin/containerd。养成解压前先tar -tf看目录结构的习惯,可以完全绕开这个坑。
5.5 节点 Ready 但 Pod 网络不通,CNI 网段重叠
现象:节点加入集群后状态 Ready,Pod 也能创建,但从 Pod 内 ping 其他节点或集群外部地址不通,同一节点上的 Pod 之间通信正常。
原因:默认的10-containerd-net.conflist接管了本机 Pod 网络,而它的 subnet 恰好与宿主机网段或者集群内其他 CNI 的子网重叠。数据包出不去,路由表也不认它,表现为单向或完全不通。
解决:确认生产集群已安装集群级 CNI 后,把默认 conflist 备份移除,重启 containerd,让新的 CNI 接管。如果还在用默认 bridge 网络,就把 subnet 改成不与任何现有网段冲突的地址,然后重建一遍测试 Pod。重叠问题是网络插件最容易忽略的边界,配置前先扫一遍全集群的 CIDR 表格。
6. 运行时健康的自检手段:crictl 三条命令加一条日志查询
验证 containerd 是否适合承载 kubelet,不需要一上来就用 kubeadm,先靠 crictl 做三层自检就够了。
第一层是连接层,crictl version确认 CRI 通道打通。第二层是运行层,crictl pods看基础设施容器和业务容器是否能正常创建并进入 Ready。第三层是业务视角,crictl logs直接读容器 stdout,省得进到节点里找容器日志文件。
crictl pods crictl ps -a crictl logs <container-id> --tail 50这三条命令覆盖了大部分日常巡检。如果有一批节点同时异常,优先用 journalctl 查 kubelet 和 containerd 的对应关系:
journalctl -u kubelet -n 100 --no-pager | grep -i 'containerd\|cri' journalctl -u containerd -n 100 --no-pager | grep -i 'error\|failed'我个人的习惯是:任何未知报错先看 kubelet 日志里的--container-runtime-endpoint是否真的指向 containerd.sock,再看 containerd 日志有没有Plugins are ready这条启动记录。两者都对上了,剩下的问题基本都在镜像源和网络插件层面。
还有一个容易被忽略的收尾动作:crictl config runtime-endpoint只对当前用户生效,root 执行过一次之后,后续切到普通用户排查时 crictl 可能重新指向默认 socket 导致假故障。建议把校验命令写进节点的初始化脚本,团队其他人复现问题时不会在同一块石头上再撞一次。这套验证流程帮我在不少集群排过障,希望帮到你。
本文还有配套的精品资源,点击获取