containerd 与 CRI 生产运维及磁盘爆满排障

在 Kubernetes 节点上,containerd 同时连接 kubelet、镜像仓库、OCI Runtime、快照文件系统和本地磁盘。很多“节点磁盘满”“镜像删不掉”“Pod 一直 ContainerCreating”“重启 containerd 后业务异常”的问题,根因并不在 Kubernetes 对象,而在 containerd 的内容、镜像、容器、快照和租约关系。

本文从运行时结构入手,给出日常巡检、容量分析、安全清理和故障恢复方法。文中的清理步骤必须先确认引用关系,禁止直接删除 /var/lib/containerd。

一、理解 containerd 在节点中的位置

典型调用链如下:

kubelet
  └─ CRI gRPC
      └─ containerd CRI plugin
          ├─ image service
          ├─ content store
          ├─ metadata database
          ├─ snapshotter
          └─ OCI runtime(通常为 runc)

几个容易混淆的概念:

对象 含义
Content 按 digest 保存的镜像层、配置和 manifest 数据
Image 名称到目标 descriptor 的引用,不等于实际层文件
Container 容器元数据,不代表进程一定运行
Task 正在运行或已停止的运行时任务
Snapshot 容器可写层或镜像层解包后的文件系统视图
Lease 临时保护一组资源不被垃圾回收
Namespace containerd 内部隔离域,Kubernetes 通常使用 k8s.io

删除 Image 引用不一定马上释放空间,因为内容层可能仍被其他镜像、快照、容器或 lease 引用。

二、先确认目录与配置

containerd config dump > /tmp/containerd-effective-config.toml
containerd --version
runc --version
crictl version
systemctl status containerd --no-pager

常见路径:

  • 配置:/etc/containerd/config.toml;
  • 根目录:/var/lib/containerd;
  • 状态目录:/run/containerd;
  • 日志:systemd journal;
  • kubelet 数据:/var/lib/kubelet。

不要假设所有发行版路径都相同,应以有效配置中的 root、state 和插件设置为准。

三、crictl、ctr与nerdctl怎么选

crictl

crictl 面向 CRI,看到的是 kubelet 视角的 Pod Sandbox、容器和镜像,生产排障优先使用。

crictl info
crictl pods
crictl ps -a
crictl images
crictl stats

ctr

ctr 是 containerd 低层调试工具,必须显式指定命名空间:

ctr namespaces list
ctr -n k8s.io images list
ctr -n k8s.io containers list
ctr -n k8s.io tasks list
ctr -n k8s.io snapshots list

忘记 -n k8s.io 时看到空列表,不代表节点没有镜像和容器。

nerdctl

nerdctl 提供更接近 Docker 的体验,适合人工调试和独立 containerd 环境,但不要用它绕开 kubelet直接修改 Kubernetes 正在管理的容器。

四、建立运行时基线

1. 配置基线

至少审查:

  • CRI 插件是否启用;
  • sandbox image 是否来自可用仓库;
  • SystemdCgroup 是否与 kubelet 一致;
  • registry mirrors、TLS 和认证配置;
  • snapshotter 类型;
  • 日志级别、OOM 分值和资源限制;
  • 是否配置了不安全仓库;
  • pause 镜像架构是否匹配节点。

2. 健康基线

crictl info
sudo ctr plugins list
sudo journalctl -u containerd --since '-30 min' --no-pager
sudo journalctl -u kubelet --since '-30 min' --no-pager

ctr plugins list 中关键插件应为 ok。CRI 未加载时,kubelet 通常会报告连接 runtime service 失败。

3. 容量基线

df -hT
df -ih
du -xhd1 /var/lib/containerd 2>/dev/null | sort -h
du -xhd1 /var/lib/kubelet 2>/dev/null | sort -h

磁盘使用率、inode 使用率和文件系统预留空间都要观察。大量小文件可能先耗尽 inode,而不是字节容量。

五、磁盘爆满的定位顺序

第一步:确认满的是哪个文件系统

findmnt -T /var/lib/containerd
findmnt -T /var/lib/kubelet
df -hT /var/lib/containerd /var/lib/kubelet
df -ih /var/lib/containerd /var/lib/kubelet

如果 containerd 和 kubelet 位于不同挂载点,必须分别判断。

第二步:区分内容、快照、日志还是空目录卷

du -xhd2 /var/lib/containerd 2>/dev/null | sort -h | tail -30
du -xhd2 /var/lib/kubelet 2>/dev/null | sort -h | tail -30

常见大户包括:

  • io.containerd.content.v1.content:压缩镜像层;
  • io.containerd.snapshotter.v1.overlayfs:解包层和容器可写层;
  • Pod 日志目录;
  • emptyDir;
  • CSI 挂载或残留目录;
  • 已删除但仍被进程打开的日志文件。

第三步:检查已删除但未释放的文件

lsof +L1

如果大文件链接数为 0 但仍被进程打开,重启对应进程或使其重新打开日志后空间才会释放。不要看到 deleted 就立即重启整个节点,应先确认进程和业务影响。

第四步:检查运行时对象

crictl images
crictl ps -a
crictl pods
ctr -n k8s.io leases list
ctr -n k8s.io snapshots list

结合镜像创建时间、大小、当前 Pod 引用和拉取策略判断是否真正无用。

六、理解垃圾回收

containerd 使用标记与引用关系判断资源是否可回收。镜像、容器、快照和 lease 都可能成为 GC 根或引用链的一部分。

Kubernetes 还由 kubelet 管理镜像垃圾回收。应优先让 kubelet 和 containerd 的正常机制工作,而不是手工删除数据目录。

需要重点理解:

  • 删除镜像名称只是移除一个引用;
  • 共享层仍被其他镜像引用时不会释放;
  • 活跃容器的快照不能删除;
  • 未过期 lease 可能保护临时资源;
  • 元数据和磁盘文件必须保持一致;
  • GC 可能为降低锁占用而分批执行,不一定瞬间释放全部空间。

七、安全释放空间的操作顺序

1. 先停止空间继续增长

  • 暂停大量镜像构建或预拉取任务;
  • 限制异常日志输出;
  • cordon 节点,避免调度更多工作负载;
  • 保留足够空间让 containerd 和 kubelet继续工作。
kubectl cordon <node-name>

2. 清理 Kubernetes 已不使用的容器和镜像

先确认容器状态:

crictl ps -a
crictl pods
crictl images

仅删除明确无用的已停止容器或镜像。不要使用不加筛选的批量命令,也不要删除当前运行 Pod 所需镜像后立刻断开仓库连接。

3. 处理日志

检查 kubelet 容器日志轮转配置、应用日志输出方式和 journald 占用:

journalctl --disk-usage
journalctl -u containerd --since today --no-pager | tail -200
find /var/log/pods -xdev -type f -printf '%s %p\n' 2>/dev/null | sort -n | tail -30

应通过日志轮转和保留策略治理,避免用截断正在写入文件的方式作为长期方案。

4. 触发和观察正常GC

修复异常引用或删除无用对象后,观察 containerd 日志和目录占用。不要为了“强制GC”随意修改未知参数。GC 配置位于 containerd scheduler 插件中,调整前需做压测和版本核对。

5. 节点恢复

systemctl is-active containerd kubelet
crictl info
kubectl get node <node-name>
kubectl uncordon <node-name>

恢复后继续观察镜像拉取、Pod 创建和磁盘曲线,确认问题没有再次增长。

八、严禁直接删除的数据

不要直接执行以下操作:

rm -rf /var/lib/containerd/*
rm -rf /var/lib/kubelet/*

这样做可能造成:

  • 元数据库与快照目录不一致;
  • 正在运行容器丢失可写层;
  • kubelet 看到 Pod 存在但运行时对象消失;
  • CSI 挂载和设备引用异常;
  • 节点只能通过重建恢复。

如果节点已严重损坏,通常更安全的做法是:隔离节点、迁移业务、确认有状态数据安全、按标准流程重置或重装节点,再重新加入集群。

九、镜像拉取故障

ImagePullBackOff

依次检查:

  1. 镜像名称、tag 和 digest;
  2. DNS 与仓库连通性;
  3. 仓库证书链;
  4. imagePullSecrets;
  5. containerd registry 配置;
  6. 代理和 NO_PROXY;
  7. 仓库限流;
  8. 节点磁盘与 inode。
crictl pull registry.example.com/project/image:tag
journalctl -u containerd -n 200 --no-pager

x509证书错误

不要通过长期关闭 TLS 验证解决。正确做法是安装受信任 CA、修复仓库证书和主机名,并确认 containerd 是否需要重启才能加载信任链。

镜像解包失败

重点检查 snapshotter、文件系统空间、inode、OverlayFS 支持、SELinux/AppArmor 和内核日志。

十、CRI连接故障

常见报错包括:

container runtime is down
failed to connect to containerd
unknown service runtime.v1.RuntimeService

排查步骤:

systemctl status containerd --no-pager
ss -xl | grep containerd
crictl --runtime-endpoint unix:///run/containerd/containerd.sock info
ctr plugins list
journalctl -u containerd -b --no-pager | tail -300

确认 kubelet 与 crictl 指向相同 socket,并检查 CRI 插件是否被配置为 disabled。修改配置后先执行配置审查,再滚动处理节点。

十一、快照与OverlayFS问题

快照目录增长

快照ID与 Kubernetes Pod 名称不是一一对应,不能根据目录名猜测后删除。应从容器、快照和内容引用关系逐层确认。

overlay mount失败

检查:

  • 内核是否支持 overlay;
  • backing filesystem 是否兼容;
  • dmesg 中是否有 I/O、只读或 xattr 错误;
  • SELinux 上下文;
  • 挂载点是否残留;
  • 是否发生非正常断电。
dmesg -T | tail -200
findmnt -t overlay
ctr -n k8s.io snapshots list

十二、运行时升级与变更

生产升级前应核对:

  • Kubernetes 与 CRI 版本兼容性;
  • containerd 配置版本差异;
  • runc 安全公告;
  • cgroup v1/v2 和 systemd cgroup 驱动;
  • snapshotter 数据兼容性;
  • registry 配置迁移;
  • pause 镜像版本。

标准流程:

  1. 在测试节点验证;
  2. cordon、drain 单个节点;
  3. 备份配置并升级;
  4. 验证 CRI、CNI、CSI 和样例 Pod;
  5. uncordon 并观察;
  6. 再进入下一批。

不要同时升级全部节点的运行时。

十三、监控与告警

至少监控:

  • containerd 服务状态和重启次数;
  • CRI 操作延迟与错误;
  • 镜像拉取失败率;
  • Pod sandbox 创建失败;
  • 节点文件系统容量和 inode;
  • 容器日志增长率;
  • kubelet DiskPressure;
  • 镜像层和快照目录增长趋势。

告警应包含节点名、文件系统、剩余容量、增长速度和最近异常事件,而不是只报一个百分比。

十四、典型案例:overlay快照持续增长

现象

  • /var/lib/containerd 持续增长;
  • crictl images 显示镜像数量并不多;
  • 重启 containerd 不能明显释放空间。

分析

  1. 确认主要空间位于 overlayfs snapshotter;
  2. 检查是否存在大量退出容器和 sandbox;
  3. 检查 lease 与 snapshot 引用;
  4. 对照 kubelet 垃圾回收日志;
  5. 检查异常客户端是否不断创建临时资源;
  6. 判断是否为已删除但仍打开的文件。

修复

  • 隔离节点并停止异常创建源;
  • 通过 CRI 删除确认无用的对象;
  • 修复失效 lease 或客户端逻辑;
  • 等待并观察正常 GC;
  • 如元数据损坏,迁移业务并重建节点,而不是手工拼接数据目录。

十五、生产检查清单

日常

  • [ ] containerd、kubelet状态正常;
  • [ ] 关键插件均为 ok;
  • [ ] 磁盘与 inode 有充足余量;
  • [ ] 没有持续镜像拉取和 sandbox 创建错误;
  • [ ] 日志轮转有效;
  • [ ] 镜像和快照增长趋势可解释。

故障处理

  • [ ] 已确认具体满盘挂载点;
  • [ ] 已区分内容、快照、日志、emptyDir 和 CSI;
  • [ ] 已检查 deleted-open 文件;
  • [ ] 已 cordon 节点并阻止继续增长;
  • [ ] 只删除确认无引用的对象;
  • [ ] 没有直接删除 containerd 或 kubelet 数据目录;
  • [ ] 恢复后已验证 Pod 创建、镜像拉取和业务流量。

参考资料

Copyright © yan-jian.com 2023 - 2026 · Powered by GitBook更新时间: 2026-10-25 22:20:46

results matching ""

    No results matching ""