ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

K8s节点切换containerd:cri-containerd 1.7.23部署与配置指南

K8s节点切换containerd:cri-containerd 1.7.23部署与配置指南 简介cri-containerd-1.7.23-linux-amd64.tar 是一款面向 Linux/AMD64 架构的 containerd 1.7.23 离线安装包适合运维、开发人员在 Kubernetes 节点上快速准备容器运行时环境。作为 CNCF 旗下项目及 Kubernetes CRI 标准实现containerd 承担镜像拉取、存储、容器创建销毁等生命周期管理该压缩包非常适合没有外网条件或需要统一运行时版本的部署场景。资源共 19 个文件、101.22MB主要包含 systemd 服务模板、yaml 与环境变量配置、shell 辅助脚本以及 containerd、crictl 等运行工具能从服务注册、客户端配置到容器启动形成较完整闭环。随包附带弃用提示和目录结构便于使用者了解功能替代与迁移路径。目前已有 261 人学习/下载。解压后即可按规范目录结构快速部署减少手工编译和依赖排查是 Kubernetes 节点初始化时的实用基础组件。1. cri-containerd 1.7.23 这个包是把 K8s 节点从 Docker 切到 containerd 的关键一步Kubernetes 从 1.24 移除 dockershim 之后节点上的容器运行时基本只剩两条路containerd 或 CRI-O。我接手过的几个集群里最终都让 containerd 上场了而 cri-containerd 是 containerd 官方提供的一种打包形态把 containerd 主程序、CRI 插件、crictl、CNI 插件和 systemd 服务文件全部揉进一个 tar 包里解压即用不用再单独装 runc、不用手写服务单元适合批量铺节点也适合把老节点从 Docker 平滑迁移过来。这份资源要解决的问题很具体在一个干净的 linux amd64 节点上怎么快速把 kubelet 认账的运行时装好并且不踩配置上的坑。适合正在搭 K8s 集群、或者接手了老集群想换运行时的一线运维。2. 先搞懂 cri-containerd 和 containerd 的关系不是同一个二进制也差不了多少很多人在刚开始接触这份资源时会纠结一个名字问题我下的明明是 cri-containerd为什么解压出来只看到一个 containerd 主程序这就涉及到 containerd 整体架构里一个容易混淆的点containerd 本身是容器管理引擎但 kubelet 走的是 CRI 接口两者之间需要一个“翻译层”。cri-containerd 这个 tar 包本质是把这层翻译插件编译进了 containerd 主程序里所以不是一个独立进程更不是另一个运行时它在 containerd 启动时被一并加载成了内置插件。2.1 containerd 本身不直接服务 kubelet要的是 CRI 插件containerd 从设计之初就是一套插件化架构核心 daemon 负责镜像拉取、存储快照、容器生命周期管理外部通过 gRPC API 调它。kubelet 需要的是一套固定格式的 CRI 请求这套请求由 containerd 里的 cri plugin 负责翻译。用一句话概括 설치 containerd 之后没有加载 cri plugin那 kubelet 是连不上的只会报 “CRI v1 runtime API is not implemented” 这种错。这份 tar 包解决的问题就是把这个插件和主程序一起打包让你少操一步手动编译或手动拼接的心。它在初始化时会自动判断是否启用 CRI 插件默认配置里对应字段已经在工作状态不需要额外加参数。常见做法是直接用官方打包产物而不是自编译 containerd原因很简单自编译版本很容易漏掉 cni、漏掉 cri plugin或者版本不匹配导致 kubelet 和运行时之间握手失败。2.2 版本号 1.7.23 意味着什么为什么是稳定线1.7.x 是 containerd 长期维护的一个大版本分支1.7.23 是这条分支上的小版本主要吸收各类安全补丁和稳定性修复不会激进地改 API 或动默认行为。如果你要生产的 K8s 集群这个分支很合适它不是实验特性最多的版本但它是兼容面最广、社区踩坑文档最全的版本。判断一份 tar 能否放心用于生产我的经验是先看三点一是这个版本是否在 containerd 官方 release 列表里能查到二是它和当前 kubelet 主版本的兼容矩阵是否匹配三是包内文件是否齐全。这份包对应 linux amd64 架构在 x86_64 服务器上都适用。如果你手上有 arm64 架构服务器需要另外找对应架构的包硬解压是会启动失败的。有一点值得提醒1.7 分支的配置格式和更老的 1.4、1.5 分支有些差异两代版本之间的 config.toml 不能直接混用。网上很多老博客写的配置路径是 plugins.cri而在 1.7 里正确的字段是 plugins.io.containerd.grpc.v1.cri这些细节如果在迁移时不注意很容易翻车。2.3 和 CRI-O、Docker 的对比选它而不是别的原因运行时方案进程模型kubelet 对接方式我遇到的主要问题Docker dockershim需要 docker 守护进程 shim 模块走 docker socket依赖 shim 翻译k8s 1.24 后已移除维护成本高CRI-O独立守护进程直接走 CRI配置较轻生态和镜像工具链不如 containerd 完整cri-containerdcontainerd 单一守护进程内置 cri plugin 直接对接配置字段需要按 1.7 规范写别套老文档在这个对比里cri-containerd 的优势是链路短。容器操作要么走 crictl要么走 Kubernetes API不需要经过 docker daemon也没有额外一层 shim内存占用和启动速度都有改善。调试方面crictl 的命令和 docker 的命令很接近甚至支持 exec、logs、inspect迁移时心理负担非常小。3. 落地部署从 tar 包到 Kubelet 认账完整的安装与配置步骤这一章是整个资源落地的主体部分。我一般会把安装过程拆成四步解压落位、生成配置文件、调 CRI 参数和镜像加速、启动服务。这四步做完再用 crictl 验证一两个命令基本就能接 kubelet 了。3.1 解压落位先看清包内结构再动手拿到这份 tar 包后不要急着解压。先用 tar 的列表模式看一眼包内目录结构这一步很关键能够让你确认解压目标路径。老的 cri-containerd 包通常带着 usr/local 前缀直接解压到根目录就能把二进制放到 /usr/local/bin配置放到 /etc/containerd。# 先看包内结构确认目录前缀 tar -tf cri-containerd-1.7.23-linux-amd64.tar | head -30 # 包内路径是 usr/local/bin 等标准路径解压到根目录即可 tar -xf cri-containerd-1.7.23-linux-amd64.tar -C / # 如果你拿到的是 .tar.gz 压缩格式用下面这条 tar -xzf cri-containerd-1.7.23-linux-amd64.tar.gz -C /执行第一条命令时你会看到包内包含 usr/local/bin/containerd、usr/local/bin/containerd-shim、usr/local/bin/crictl、usr/local/bin/ctr以及 etc/systemd/system/containerd.service、etc/containerd/config.toml。看到这些路径再做解压就有底了。tar -xf 的逻辑是保留包内目录结构释放文件-C 指定释放到哪个根目录。这里有一点需要提醒如果你拿到的是纯 .tar 格式就用 -xf如果是 gzip 压缩过的就必须加 -z 参数否则会报 not in gzip format。解压完成后用 which containerd 确认一下二进制路径。默认安装到 /usr/local/bin这个路径也在 systemd 服务文件里被直接引用是启动服务的关键。3.2 生成 config.toml 文件并校准 CRI 常用参数正常情况下解压出来的 etc/containerd/config.toml 已经存在一份默认配置但里面的参数可能不是你想要的样子。我一贯的做法是让 containerd 自己生成一份完整默认配置再手工修改关键字段这样能保证版本匹配不会因为网上抄来的旧配置导致启动失败。# 备份默认文件让 containerd 生成标准配置 mv /etc/containerd/config.toml /etc/containerd/config.toml.bak containerd config default /etc/containerd/config.toml这条命令的逻辑是containerd config default 会把当前版本的完整默认配置输出到 stdout然后重定向写到配置文件里。这样生成的配置在字段结构上和当前版本 100% 匹配避免从网上复制一份 1.4 时代的配置过来结果 1.7 根本读不了。生成之后需要用你的编辑器改一个核心字段Cgroup 驱动。K8s 节点上 kubelet 默认用 systemd 驱动containerd 侧也要保持一致否则 Pod 沙箱创建时会报 cgroup 相关错误。[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字段含义说明runtime_type 指定用 runc v2 运行容器这是 containerd 1.7 默认推荐的SystemdCgroup 改为 true意义是让 runc 通过 systemd 来管理 cgroup而不是直接写 cgroupfs。kubelet 如果以 systemd cgroup 驱动运行而这里保持 false后面创建沙箱就会报 failed to apply cgroup 之类的错排查起来非常绕。3.3 镜像加速器配置在 docker.io 拉取慢时的有效办法在国内网络环境下从 docker.io 直连拉镜像经常超时这是运行 containerd 时最常遇到的问题。好的做法是在配置里给镜像仓库指定 mirror 端点。containerd 1.7 支持在 CRI 配置段里声明 registry mirrors把请求转发到可访问的镜像站点。[plugins.io.containerd.grpc.v1.cri.registry] [plugins.io.containerd.grpc.v1.cri.registry.mirrors] [plugins.io.containerd.grpc.v1.cri.registry.mirrors.docker.io] endpoint [https://docker.m.daocloud.io, https://hub-mirror.c.163.com] [plugins.io.containerd.grpc.v1.cri.registry.mirrors.registry.k8s.io] endpoint [https://registry.aliyuncs.com]这里设置的核心含义是当 containerd 需要拉取 docker.io 下的镜像时会优先请求 endpoint 列表里的镜像站对应地也把 k8s 官方镜像仓库 registry.k8s.io 的拉取指向阿里云。endpoint 的顺序就是优先级第一个失败会自动尝试第二个。另外还要校准 sandbox_image 参数。kubelet 创建 Pod 沙箱时依赖一个 pause 镜像这个镜像是整个节点上最先需要拉取的东西。如果默认值 registry.k8s.io/pause:3.9 在你的网络环境下拉不动整个节点就跑不起来。我会把它直接改成可用镜像仓库里的 pause 镜像配合上面配置的加速器一次性解决。3.4 systemd 服务单元与开机自启别小看这几行配置tar 包里的 etc/systemd/system/containerd.service 是官方提供的服务单元解压后已经放到正确位置但很多人的习惯是解压完只启动 containerd 二进制等机器重启后就发现服务没起来。正确的做法是用 systemd 来管理。systemctl daemon-reload systemctl enable containerd.service systemctl restart containerd systemctl status containerd.service --no-pager -lsystemctl daemon-reload 这步不能省因为新写入的服务文件需要系统重新加载才能识别。enable 的意思是设置开机自启restart 是让服务按新配置重新拉起status 用来确认状态。我建议在 status 输出里留意两个信息Active 是不是 active (running)以及日志里有没有 WARN 级别以上的提示。服务文件里有一个字段值得你留意LimitNOFILE1048576它规定 containerd 进程能打开的最大文件句柄数。容器多、镜像层多的节点上如果不设置这个值很容易在宿主机高负载时出现 no file descriptors available 错误。官方包默认把这个参数写好了这也是我推荐直接用官方 tar 包而不是手动编译的原因之一。4. 验证运行时并接入 Kubelet让 containerd 真的接管节点配置完成后下一步是证明运行时真的能用再接入 kubelet。这一步我习惯分三层验证先是 crictl 能连通 socket然后拉一个真实镜像并启动容器最后才让 kubelet 工作避免把问题混在一起难以定位。4.1 用 crictl 验证运行时端点crictl 是 cri-containerd 包里自带的 CRI 命令行工具它直接通过 CRI 接口和 containerd 交互。如果 crictl 能够列出容器、镜像说明 CRI 插件已经工作正常。# 写一份 crictl 默认配置文件指向 containerd 的 socket cat /etc/crictl.yaml EOF runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock timeout: 10 debug: false EOF # 查看镜像列表 crictl images ls # 查看容器列表 crictl ps -aruntime-endpoint 指明 crictl 连接的 CRI 服务地址image-endpoint 是镜像服务的地址通常情况下两者一致。timeout 设置 10 秒防止命令卡死。crictl ps -a 如果能返回空列表而不是报错就说明 socket 通道是通的。很多新手在这里看到 empty 列表以为是故障其实空列表代表连接成功只是此刻没有容器。同时可以看一眼 containerd 自己管理的镜像命名空间# 查看 containerd 原生视角下的镜像注意 k8s.io 是 CRI 使用的命名空间 ctr -n k8s.io images listctr 和 crictl 是两套工具ctr 直接走 containerd 原生 APIcrictl 走 CRI 接口。这里用 -n k8s.io 限定命名空间是有讲究的K8s 创建的镜像和容器都会放在 k8s.io 这个命名空间下用 ctr 不加 -n 的话会看到一片空白这也是一个非常经典的误导点。4.2 手动拉取一个镜像验证完整链路光能连上 socket 还不够要验证镜像拉取、快照挂载、runc 启动这一整套链路。我用的是直接让 containerd 跑一个最小容器。# 先拉取一个测试镜像验证镜像加速配置是否生效 ctr -n k8s.io images pull docker.io/library/nginx:alpine # 用 crictl 启动一个测试容器 crictl run --runtimerunc \ --image docker.io/library/nginx:alpine \ --name test-nginx \ --cpu 1 --memory 128M \ /mnt/testcrictl run 不是容器创建的唯一方式但它适合做验证因为它能直接测试 CRI 层的容器配置。docker.io/library/nginx:alpine 镜像很小拉取速度快如果这一步能成功证明镜像加速配置是对的runc v2 也能正常启动容器。如果失败优先看 journalctl -u containerd 的输出大多数情况是镜像源本身不可达或者加速站点没有这个镜像。这里补充一句生产环境下很少直接 crictl run 容器这些操作通常由 kubelet 来完成但安装阶段用这种方式来验证运行时链路是非常高效的。4.3 kubeadm init 时怎么让 kubelet 把容器交给 containerdkubelet 对接 containerd 需要知道 CRI socket 的地址。不同 K8s 版本对这个参数的处理方式略有差异在 Kubernetes 1.26 以后kubelet 默认已经走 CRI只需要显式指定 endpoint。# 方式一在 kubeadm 初始化时显式指定 CRI socket kubeadm init --kubernetes-versionv1.28.2 --cri-socketunix:///run/containerd/containerd.sock # 方式二如果是给已有节点配置 kubelet写在 kubelet 启动参数里 # 编辑 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf # 在 KUBELET_EXTRA_ARGS 里加入 --container-runtime-endpointunix:///run/containerd/containerd.sock--cri-socket 参数的作用是告诉 kubelet 去哪里找容器运行时的 CRI 服务。如果节点上只装了 containerdkubeadm 自动探测也能找到它但显式声明更稳妥也避免在存在多个 socket 文件时选错。KUBELET_EXTRA_ARGS 的方式适用于通过 kubeadm 生成的节点文件这里改完后要 systemctl daemon-reload 并重启 kubelet。接入后用两个命令验证状态# 看节点容器运行时相关信息 kubectl get nodes -o wide # 看节点上的 kubelet 系统状态 systemctl status kubelet --no-pager -l如果 node 状态是 Ready并且 kubelet 日志里有 Started container manager 之类的输出就说明 containerd 和 kubelet 的对接全部完成。5. 遇到过的 5 个真人真事的坑这里有一条条亲自排错过的记录这一章写的是我在服务器上实际踩过、且对应的修复方式可复现的故障案例。每条按“现象 → 原因 → 解决”的顺序写方便你未来对照排查。5.1 服务启动失败/usr/local/bin/containerd 不存在现象systemctl start containerd 后日志显示 Failed at step EXEC spawning /usr/local/bin/containerd: No such file or directory但手工执行 which containerd 又能找到路径。原因tar 包被解压到了 /usr/local但 systemd 服务文件里写的是绝对路径。某个步骤把服务文件里的 ExecStart 路径改坏了或者系统是 CentOS 7 这种老版本/usr/local/bin 不在 systemd 服务的默认 PATH 里导致找不到程序。解决不要用相对路径把服务文件里的 ExecStart 固定写成 /usr/local/bin/containerd然后 systemctl daemon-reload 再重新 start。我后来所有节点都用同一个脚本部署就是为了避免这种路径不统一的玄学问题。5.2 Pod 沙箱创建失败kubernetes cgroup 驱动不一致现象kubeadm init 成功但创建 Pod 时一直处于 ContainerCreatingnode 状态 NotReady。kubelet 日志里反复出现 failed to apply cgroup 或者 failed to run Kubelet: failed to run kubelet: failed to create kubelet runtime 之类的错误。原因kubelet 使用 systemd 驱动管理 cgroup而 containerd 配置里 SystemdCgroup 还是 false两边驱动不同kubelet 无法对沙箱做 cgroup 操作。这是换 containerd 时最常见的第一大坑几乎每个 migration 项目都会遇到一次。解决在 config.toml 里找到 runc options把 SystemdCgroup 改成 true重启 containerd再重启 kubelet。这里最忌讳只改一边两边必须都是 systemd 才能正常工作。5.3 沙箱镜像拉取失败pause 镜像的仓库不可达现象节点初始化的第一个 Pod 卡在 sandbox 阶段日志里出现 Failed to pull image registry.k8s.io/pause:3.9。看起来网络是通的但拉取总是超时。原因默认 sandbox_image 指向 registry.k8s.io 这个域名在网络环境无法直连或访问很慢的情况下镜像始终拉不下来。问题不是 containerd也不是 kubelet而是这个默认镜像源不适合当前环境。解决把 sandbox_image 改成镜像加速仓库里的 pause 镜像。推荐操作是先手工把 pause 镜像拉下来然后在配置里指向包含该镜像的加速仓库。我从那以后每次部署新节点都会在启动服务前手工拉一次 pause 镜像宁可多花一步不跟网络环境打游击。5.4 kubelet 仍然去连 docker socket现象明明 containerd 已经正常运行crictl ps 也能返回结果但 kubelet 始终报 unable to connect to docker.sock。系统里已经没有 docker 服务为什么 kubelet 还去找 docker。原因kubelet 配置里残留了旧版本的 container-runtime-endpoint或者是在 kubeadm 生成的 10-kubeadm.conf 里还保留着 unix:///var/run/docker.sock 的配置。docker 二进制的 socket 文件还在但已经没有任何进程监听。这是迁移老节点时的常见问题属于配置残留不是运行时的问题。解决直接改 kubelet service 的 drop-in 配置把 container-runtime-endpoint 改成 unix:///run/containerd/containerd.sock确认没有多余参数然后 daemon-reload 并重启 kubelet。想要更彻底可以删掉旧 docker.sock 文件断了这个路径的后路。5.5 双运行时环境里的 CNI 插件互相覆盖现象节点上之前装过别的运行时卸载后安装 cri-containerdPod 能起来但网络不通Pod 一直重启。日志里报消息关于 network plugin 的配置或回环地址。原因不同运行时的 CNI 插件被装到了同一个 /opt/cni/bin 目录新旧插件混在一起配置目录 /etc/cni/net.d 里的配置混乱导致容器网络被配成了不存在的 plugin。这不是 containerd 本身的问题是节点回收再利用时没清理干净。解决安装前先清空 /opt/cni/bin 和 /etc/cni/net.d 目录里的残留文件再重新解压 tar 包把官方 CNI 插件重新铺进去。我有个习惯是执行完解压后用 find /opt/cni/bin -type f | wc -l 数一下插件文件数量数量不对就直接重来避免后续网络问题反复纠缠。6. 升级时机与保留现场从旧版本换到 1.7.23 的验证路径如果你节点上已经在跑 containerd 1.6.x想升级到 1.7.23不建议原地覆盖二进制后就立刻拿生产流量测试。我更常做的是一套固定的验证路径每次升级都强制走一遍能挡掉大多数问题。先备份当前版本的关键文件和现场这是后悔药不能省。containerd 的版本升级其实变动的文件就几个但升级前把配置和镜像列表留个底出了问题可以快速回滚。# 备份配置与当前版本信息 cp /etc/containerd/config.toml /etc/containerd/config.toml.bak-$(date %F) containerd --version containerd-version-before.txt ctr -n k8s.io images list images-before.txt接着做新 tar 包的解压。按 3.1 里的步骤解压后比较一下新旧两个版本的服务文件差异很多情况下 1.6 到 1.7 的 service 文件差异很小但 config.toml 里如果有旧字段containerd 可能启动不了。我的经验是先不启动服务用 containerd config check 把配置数据预检一遍它会在启动前告诉你哪些字段已经废弃或无法识别。# 预检配置启动前发现潜在问题 containerd config check --config/etc/containerd/config.toml升级完成后启动服务然后执行一串 m 顺序的验证命令systemctl status 确认进程在跑crictl ps 确认 CRI 通信正常拉一个小镜像再删除确认存储和网络两层都正常。只要有一步异常直接回滚到备份文件。最后强调一个容易被人忽视的细节把 tar 包的版本号固定在部署脚本里强制写死比如 containerd-1.7.23-linux-amd64 这个版本名因为它能直接被系统管理和运维系统识别。不要用 latest 这种标签否则某天基准版本变了你重启一个节点就得到一个无法预期的运行时状态。我从那次因为版本漂移导致集群一半节点新一半节点旧、排错排到半夜之后每次部署都强制锁版本把验证路径完整走完再也不会心存侥幸。希望这些经验能帮你的接入过程少走几步弯路。本文还有配套的精品资源点击获取
返回列表