
简介面向容器运行时部署、Kubernetes 节点维护与云原生离线实验场景这份压缩包提供 containerd 1.7.23 的 Linux AMD64 版本适合需要在无外网环境安装、升级或验证 CRI 运行时的运维人员与学习者使用可减少镜像拉取依赖、版本不一致造成的部署阻碍。包内共 19 个文件整体约 101.22MB既有运行时核心程序、命令行客户端、容器执行器与兼容层等二进制组件也配有配置清单、服务单元、环境变量和模板文件并附带版本说明与弃用提示文本目录按 etc、usr/local、opt 分层组织便于离线分发、按需替换和快速定位。已有 262 人学习说明该版本在同类离线包中具备一定参考价值。读者可借助 systemd 服务模板完成运行时注册用 crictl、critest 做接口联调与合规验证通过 stress 工具开展压测并结合弃用说明评估后续迁移适合搭建可复现的容器运行时实验环境。1. 一个四十多兆的 tar 包能让 kubelet 卡在 NotReady 上半小时上周帮人看一台测试机装完cri-containerd-1.7.23-linux-amd64.tar之后 kubelet 一直 NotReadysystemctl status containerd显示 active可 Pod 全卡在 ContainerCreating。翻了半小时 journalctl 才发现包解压对了服务起来了但config.toml还是包内自带的旧值SystemdCgroup没开sandbox_image指向一个根本拉不到的外网地址。这种“服务活着、运行时死了”的状态最容易让人误判因为进程在跑你就不会怀疑它。containerd 这套环境现在几乎是容器节点的标配不管是自己攒的小集群、CI 机器还是离线交付的一体机只要跑 CRI基本都绕不开它。装不好不会立刻报错而是延后到你部署第一个工作负载时才集中爆发。这篇笔记面向要在 amd64 Linux 上手装 1.7.23 的人——版本怎么选、包往哪解、config.toml 改哪几行、坏了看哪个日志。2. 装前核对版本信息1.7.23 的版本位置与宿主机三项前提跳过直接用、装到一半又删掉重来的成本比从容地对一遍版本要高得多。这一章先把版本位置、内核条件、磁盘和目录规划确认清楚后面解包才不容易出错。我一般把这一步控制在十分钟以内但绝不省略。2.1 1.7.23 在 1.7 系列里的位置以及为什么不直接上 2.x1.7 是 containerd 里生命周期较长的一条线1.7.23 属于这条线上的后期补丁版本意味着 config 结构、插件路径、CLI 参数都已经稳定社区里绝大多数 kubelet 对接文档、CSI 排查帖、CRI 相关问题都能对得上号。这一点非常关键装 containerd 最麻烦的不是安装本身而是出问题之后搜索到的资料和你手上的版本结构不一致——1.6 时代的[plugins.cri.xxx]写法和 1.7 的[plugins.io.containerd.grpc.v1.cri.xxx]完全不是一回事。不直接上 2.x 的理由也很实在。2.x 在配置项、沙箱运行时拆分、镜像仓库配置上做了较多调整配套的 kubelet 版本、CNI 插件、监控采集脚本都得跟着动。如果你的节点是给一套已有集群扩容或者要复现一个别人交付的环境选 1.7.23 是最稳的。反过来说如果是全新搭一套、上游组件也都在新版本上那就没必要把自己限制在 1.7直接按上游的兼容矩阵走。还有一点是我遇到过的1.7 系列内部的补丁版本之间containerd config default输出的默认值是会变的比如sandbox_image的默认 pause 标签就调整过。所以别拿着一份 1.7.10 的配置模板往 1.7.23 上套默认配置一定要在本机现生成一份。2.2 内核、cgroup 与 swap装之前用三条命令确认containerd 对内核的要求不算苛刻但有两个模块必须就位overlay和br_netfilter。前者决定 overlayfs 快照器能不能用后者决定桥接流量能不能被 iptables 规则处理。很多精简版系统镜像默认不加载这两个模块服务能起容器一跑就报failed to mount overlay。# 加载模块并写入开机自启避免重启后容器全挂 modprobe overlay modprobe br_netfilter cat /etc/modules-load.d/containerd.conf EOF overlay br_netfilter EOF # 顺手把转发参数落盘kubelet 场景基本都要 cat /etc/sysctl.d/99-kubernetes-cri.conf EOF net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sysctl --systemmodules-load.d那个文件是给重启用的modprobe只是当前会话生效两者都要做。sysctl 那一段在纯 containerd 场景下不是强制的但只要节点后面会跑 kubeletip_forward不打开Pod 之间跨节点通信必然出问题早配早省事。cgroup 版本要单独确认因为 1.7.23 的 cgroup driver 配置跟它强相关# 返回 cgroup2fs 说明是 cgroup v2返回 tmpfs 一般是 v1 stat -fc %T /sys/fs/cgroup/这一步的输出直接决定第 4 章SystemdCgroup怎么填。记住这个结果后面要用。swap 则是老生常谈kubelet 默认不允许 swap 开启装之前先swapoff -a并把/etc/fstab里对应的行注释掉。free -h确认一下 Swap 那行全是 0 再往下走。2.3 目录与磁盘/var/lib/containerd 该给多大containerd 默认把镜像层、快照、容器元数据全放在/var/lib/containerdsocket 和运行时状态放在/run/containerd。前者是磁盘大户后者是 tmpfs重启即清空不用管。我给的经验值只跑几个测试容器20G 起步正经跑业务负载/var/lib/containerd单独挂一块盘或者至少保证/var分区有 100G 以上余量。镜像层是层层叠加的删了容器不一定释放空间没有一个宽裕的余量迟早会遇到no space left on device然后一整天都在清理。先看一眼现状df -h /var /run ls -ld /var/lib/containerd 2/dev/null || echo 目录还不存在正常安装后自动建如果/var/lib/containerd已经存在并且里面有旧数据那是之前装过 containerd 留下的。要么继续用要么先备份再清空——直接rm -rf之前想清楚里面的镜像层删掉就得重新拉。3. 解包并放到目标位置cri-containerd-1.7.23-linux-amd64.tar 中有什么、放到哪里这个包的目录结构其实很规整但它是按根目录相对路径打包的解压位置错了会导致二进制散落一地。先看清单再动手比解压完发现路径不对再回头清理省事得多。3.1 先看清单再解包包内路径与两个 runc 的取舍解压前先列一下内容这一步不费事但能避免很多“我到底装了什么”的困惑tar -tf cri-containerd-1.7.23-linux-amd64.tar | head -30正常会看到这么几类路径etc/systemd/system/containerd.service etc/containerd/config.toml usr/local/bin/containerd usr/local/bin/containerd-shim-runc-v2 usr/local/bin/ctr usr/local/bin/crictl usr/local/bin/runc usr/local/sbin/runcusr/local/bin下是运行时本体containerd-shim-runc-v2是 runc 的 shim真正负责起容器的就是它ctr是 containerd 自带的低层 CLIcrictl是 CRI 层的排障工具两个都有会更方便。etc/containerd/config.toml是包内自带的示例配置不要直接拿它当生产配置用第 4 章会解释为什么。值得注意的是包里usr/local/sbin/runc和usr/local/bin/runc各有一份实际是同一个二进制只是放在两个路径下兼容不同的 PATH 习惯。装完记得which -a runc看一眼如果系统里本来就有一个旧版 runc比如发行版包管理器装的PATH 里谁在前就用谁版本对不上会出现容器起来就退出的怪问题。我一般会显式确认一次/usr/local/bin/runc --version。3.2 一条 tar 命令放到根目录附权限核对因为这个包内的路径是usr/local/bin/...和etc/...这种根相对结构解压目标就是/cd /tmp tar -C / -xf cri-containerd-1.7.23-linux-amd64.tar # 核对落盘结果 ls -l /usr/local/bin/containerd /usr/local/bin/ctr /usr/local/bin/crictl /usr/local/bin/runc ls -l /etc/systemd/system/containerd.service-C /是切换解压目标目录-x是解包-f指定文件名。tar 会保留包内的权限位正常情况下二进制都是 755执行权限不会丢。如果ls出来发现 containerd 没有x位chmod 755 /usr/local/bin/containerd*补一下再继续。包内自带的etc/containerd/config.toml解出来会落在/etc/containerd/config.toml。我通常会立刻把它改成.bak防止后面containerd config default生成新配置时被混淆也防止自己手抖改错了还以为是默认值mv /etc/containerd/config.toml /etc/containerd/config.toml.pkg.bak3.3 systemd 单元文件与开机自启的前置检查包里的containerd.service基本可以直接用关键几行是这样的[Service] ExecStartPre-/sbin/modprobe overlay ExecStart/usr/local/bin/containerd Typenotify Delegateyes KillModeprocess Restartalways LimitNPROCinfinity TasksMaxinfinity OOMScoreAdjust-999逐个说下值得留意的地方。ExecStartPre前面那个减号表示命令失败也不阻断启动这是为了防止某些内核已经把 overlay 编进内核、modprobe报“模块不存在”时误伤服务启动。ExecStart写的是绝对路径/usr/local/bin/containerd这跟我们解包的位置一致如果你把它装到了/usr/bin这里必须同步改否则systemctl start会直接报 203/EXEC。Delegateyes让容器内的 cgroup 可以由容器自己管理缺了它systemd 会去回收容器内的进程组表现为容器刚起来莫名被 Kill。OOMScoreAdjust-999是降低 containerd 被 OOM Killer 选中的概率——容器吃爆内存时先死的不该是运行时。装完之后不要急着 start先做一次重载和 enablesystemctl daemon-reload systemctl enable containerd systemctl status containerd --no-pagerdaemon-reload是必须的否则新放进去的 unit 文件 systemd 根本看不见。enable只设置开机自启此时服务还是 inactive属于正常状态。4. 配置 /etc/containerd/config.toml1.7.23 上必须手动修改的五处真正决定这套环境能不能用的不是二进制装没装对而是这个 config.toml。默认配置只是“能启动”不是“能用”。下面五处是我每次装完都会过一遍的。4.1 先生成默认配置再备份不要用包里自带的那份包里那份config.toml是打包时固化的模板跟当前二进制不一定匹配。正确做法是用二进制自己生成mkdir -p /etc/containerd /usr/local/bin/containerd config default /etc/containerd/config.toml cp /etc/containerd/config.toml /etc/containerd/config.toml.bak.$(date %F)注意第一行必须是绝对路径/usr/local/bin/containerd不要只写containerd。如果机器上曾经通过包管理器装过旧版PATH 里/usr/bin/containerd可能排在前面你生成出来的就是旧版本的配置结构往后凡是按 1.7 语法改的地方全都会踩空。这个坑我踩过一次排查了两个小时才发现生成配置的二进制压根不是新装的那个。生成后做一份带日期的备份是为了改崩了能一键回滚。配置文件是纯文本手改出错的概率不低有个.bak就是后悔药。4.2 SystemdCgroup与 cgroup v2、kubelet 的对应关系这是最容易漏、后果最直接的一项。1.7.23 的默认配置里SystemdCgroup是false意思是 containerd 自己用 cgroupfs 管理 cgroup而 kubelet 从 1.22 之后默认用 systemd 作为 cgroup driver。两边不一致的典型表现是容器能起来但资源限制不生效或者 kubelet 报failed to create pod sandbox: cgroup ... not found。改动位置在 runc 运行时的 options 下[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 trueSystemdCgroup true表示由 systemd 接管 cgroup 创建跟 kubelet 保持一致。如果你的节点是 cgroup v2第 2 章那条stat -fc %T返回cgroup2fs这一项更是必开cgroup v2 下 cgroupfs driver 的支持并不完整。改完这一处前面那次 sigh 才算解开。改的方法有很多sed一把梭也行但我不推荐sed -i s/SystemdCgroup false/SystemdCgroup true/ /etc/containerd/config.toml grep -n SystemdCgroup /etc/containerd/config.toml之所以还贴出来是让你在改完立刻grep验证。如果 grep 结果为空说明匹配串没对上——可能是空格数不同也可能是默认值本来就是别的那就老老实实打开文件手改。4.3 sandbox_imagepause 镜像拿不到是 ContainerCreating 的主要原因Pod sandbox 需要一个 pause 镜像这个镜像地址由sandbox_image决定。默认值指向公共仓库在没有互联网出口或走内网仓库的机器上必然拉不下来现象就是所有 Pod 卡在ContainerCreatingcrictl pods能看到 sandbox 一直没建好。[plugins.io.containerd.grpc.v1.cri] sandbox_image registry.internal:5000/pause:3.9这里要填你环境里实际能拿到的 pause 镜像地址标签以你集群 kubelet 的--pod-infra-container-image为准两边不一致会出现每次重启 Pod 都重拉一次 sandbox 的情况。改之前先用crictl pull手动试一下这个地址能不能拉通能拉通再写进配置比改完重启服务再排查要快得多。如果你手上只有 pause 的 tar 包可以离线导入ctr -n k8s.io images import pause-3.9-amd64.tar ctr -n k8s.io images ls | grep pause-n k8s.io这个命名空间不能省kubelet 看的正是k8s.io这个 namespace 下的镜像导到default里等于白导。4.4 registry 与 hosts.toml私有仓库和内网 mirror 的写法1.7 版本推荐用certs.d目录来做仓库配置比在 config.toml 里堆mirrors更清晰也更好按仓库粒度加证书。先让 config.toml 指向这个目录[plugins.io.containerd.grpc.v1.cri.registry] config_path /etc/containerd/certs.d然后按仓库地址建子目录目录名就是仓库的 host:portmkdir -p /etc/containerd/certs.d/registry.internal:5000 cat /etc/containerd/certs.d/registry.internal:5000/hosts.toml EOF server https://registry.internal:5000 [host.https://registry.internal:5000] capabilities [pull, resolve] ca /etc/containerd/certs.d/registry.internal:5000/ca.crt EOFserver是默认走的地址capabilities声明这个 host 支持哪些操作pull拉取、resolve解析标签一般这两个就够。自签证书场景下把 CA 文件放进对应目录并写ca字段比全局skip_verify true安全得多。全局跳过校验只适合临时调试别留在生产配置里。配完不要凭感觉用 ctr 指定命名空间直接验证ctr -n k8s.io images pull registry.internal:5000/library/busybox:1.364.5 日志与并发max_concurrent_downloads、level 与 OOM 权重剩下几项属于调优性质但都有明确的适用场景。[debug] level info [plugins.io.containerd.grpc.v1.cri.containerd] max_concurrent_downloads 3level info是默认的平衡点。排查问题时临时改成debugsystemctl restart containerd后复现一次日志量会明显变大记得查完改回来否则 journal 很快就被冲爆。max_concurrent_downloads默认是 3控制同时拉取镜像层的并发数。带宽充足的内网仓库可以调到 5 到 8加快大镜像拉取但如果你的仓库是共享的、或者节点磁盘 IO 一般调高反而会让拉取超时变多。我一般的做法是保持默认只有在明确观察到拉取排队严重时才调。镜像加速这块如果内网有统一入口把它配成 mirror 比在每台机器上单独配 hosts.toml 更好维护[plugins.io.containerd.grpc.v1.cri.registry.mirrors.docker.io] endpoint [https://registry.internal:5000]docker.io是要被代理的原始仓库名endpoint是实际去拉的地址。写多条会按顺序回退第一条失败试第二条。改完这一整章的内容统一做一次校验再重启/usr/local/bin/containerd config dump /dev/null echo 配置语法 OK systemctl restart containerd systemctl status containerd --no-pagerconfig dump会解析并输出完整配置语法有问题会直接报错行号比 restart 失败后再翻 journal 快得多。这个习惯值得固定下来。5. 排查与避坑containerd 起不来、容器起不来时的五条现场记录前面每一步都做对了也不代表一定跑得通。运行时的问题有个共同点现象在上一层原因在下一层。这一章按“现象 → 原因 → 解决”的顺序把最常见的五种讲清楚。5.1 现象systemctl start 秒退journalctl 只有一行 error现象systemctl start containerd执行后立刻返回失败systemctl status显示status1/FAILUREjournalctl -u containerd -n 50里只有一行类似failed to load plugin io.containerd.grpc.v1.cri: ...。原因绝大多数是 config.toml 的 TOML 语法或插件路径写错。1.7 的插件键名是带引号的完整路径[plugins.io.containerd.grpc.v1.cri...]从 1.6 的模板抄过来、或者少写一个引号都会让整个 CRI 插件加载失败。另一种可能是config_path指向的目录不存在。解决先跑containerd config dump定位报错行再检查插件路径。certs.d目录不存在本身不致命但如果 config.toml 里写了config_path而目录缺失某些版本会报错mkdir -p /etc/containerd/certs.d补上即可。改完先config dump验证再 restart。5.2 现象crictl 报 connection refused 或找不到 endpoint现象crictl ps报connect: connection refused或者提示validate service connection: ... no such file or directory。原因crictl 默认按内置顺序找 socket如果机器上同时装过 docker可能先连到 docker 的 socket或者它默认路径跟 containerd 实际的/run/containerd/containerd.sock不一致。解决写死 endpoint别依赖默认值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 inforuntime-endpoint管 Pod 和容器操作image-endpoint管镜像操作两个都写上省心。crictl info能返回一大段 JSON说明连接是通的。5.3 现象Pod 一直 ContainerCreating日志提示 pull sandbox image 失败现象crictl pods里 sandbox 状态一直是NotReadycrictl ps -a里没有容器journalctl -u containerd里能看到failed to pull image ... pause。原因4.3 说的sandbox_image不可达。要么地址是外网的要么内网仓库需要认证而没配凭据要么镜像根本没同步到内网仓库。解决先手动crictl pull sandbox_image 地址复现。拉不动就换地址或先ctr -n k8s.io images import离线导入再确认crictl images | grep pause能看到。别忘了检查 kubelet 的--pod-infra-container-image是否跟 config.toml 保持一致。5.4 现象容器起来就退出runc 报 cgroup 或 permission denied现象crictl ps -a里容器状态Exited退出码 1 或 128crictl logs里没什么有用信息journalctl -u containerd里能看到 runc 相关的报错比如unable to create new cgroup或挂载权限错误。原因三类。一是SystemdCgroup没开跟 kubelet 驱动不一致二是系统里存在旧版 runcPATH 优先级让它被调用了三是 SELinux 或 AppArmor 拦截了挂载。解决先grep SystemdCgroup /etc/containerd/config.toml确认是 true再which -a runc和/usr/local/bin/runc --version确认版本。SELinux 场景看getenforce临时置为 permissive 复现一次有助于判断但别长期开着。这三步走完八成能定位。5.5 现象/var/lib/containerd 撑爆服务开始报 no space现象镜像拉一半失败容器创建失败df -h显示/var使用率 100%。原因镜像层和快照堆积删容器不一定释放底层 layer另外max_concurrent_downloads调得太高、日志级别开 debug 也会加速消耗。解决先看清是谁在占du -sh /var/lib/containerd/* 2/dev/null | sort -h ctr -n k8s.io images ls | wc -l清理优先用crictl rmi --prune它会删掉没被容器引用的镜像。rm -rf /var/lib/containerd/io.containerd.content.v1.content这种粗暴做法除非你确定要清空所有镜像否则别碰。长期方案是把/var/lib/containerd挪到独立分区用root配置项改路径改之前先停服务、迁数据、改配置顺序不能乱。6. 装完如何验证从 ctr version 到真正跑起一个容器验证不能只看systemctl status是不是 active。我按三层来验收从低到高每层过了才继续。第一层二进制和服务。三条命令输出能对得上就说明安装本身没问题systemctl is-active containerd ctr version crictl --runtime-endpoint unix:///run/containerd/containerd.sock versionctr version会打出 Client 和 Server 两段Server 段里的 Version 应该是 1.7.23Runtime 那行会带上 runc 的版本。如果只看到 Client 没有 Server说明 socket 连不上回 5.2 看。第二层配置是否真的生效。别信配置文件信 dump 出来的结果/usr/local/bin/containerd config dump | grep -E SystemdCgroup|sandbox_image|config_path三项的输出要跟你改的一致。这一步特别值得做因为containerd config default生成的文件里有些项是注释状态grep配置文件看着像改了实际 dump 出来才知道生效没有。第三层跑一个真实容器。这一步过了才算真装完crictl pull registry.internal:5000/library/busybox:1.36 ctr -n k8s.io run -d --net-host registry.internal:5000/library/busybox:1.36 verify-sh /bin/sh -c sleep 3600 ctr -n k8s.io tasks ls ctr -n k8s.io tasks exec --exec-id v1 verify-sh /bin/sh -c echo ok第三行tasks ls里能看到verify-sh处于 RUNNING第四行能打出ok说明镜像拉取、快照创建、runc 启动、shim 托管这一整条链路都是通的。跑完记得ctr -n k8s.io task kill verify-sh清掉别留着占资源。下面是三层验收的对照表出问题时按行回查层级命令通过标准失败先看服务systemctl is-active containerd输出activejournalctl -u containerd -n 100二进制ctr versionServer 段有版本号socket 路径、5.2配置containerd config dump三项关键值与预期一致4.2、4.3、4.4CRIcrictl info返回 JSONready为 true/etc/crictl.yaml运行ctr tasks ls任务处于 RUNNINGrunc 版本、5.4从那以后我每次装完 containerd不管多急这三层验证都强制走一遍尤其是config dump那一层。有次赶时间跳过了结果在一台机器上折腾到半夜最后发现只是SystemdCgroup那行被 sed 改成了True首字母大写TOML 解析时把它当成字符串而不是布尔值服务照起不误直到跑负载才暴露。希望帮到你。本文还有配套的精品资源点击获取