Kubelet 会监控 inode,但只在紧急时才行动。

Kubelet 会监控 inode,但只在紧急时才行动。

💡 原文英文,约2500词,阅读约需9分钟。
📝

内容提要

Kubelet 仅在 inode 剩余 5% 时触发硬驱逐,而镜像 GC 只关注字节使用率(85%),导致小文件多的镜像耗尽 inode 却不触发清理。案例中 containerd 快照重复解包 node_modules,占用数百万 inode。修复方案:采用多阶段构建精简镜像,并增加 inode 使用率 80% 的告警。

🔎

延伸解读

字节与 inode:两套独立机制

Kubelet 对磁盘字节和 inode 的监控是分开的。镜像 GC 只按字节使用率触发(85% 开始,80% 停止),而 inode 阈值仅出现在硬驱逐信号中,默认剩余 5% 才触发。这意味着当小文件大量堆积时,字节使用率可能远未达到 85%,但 inode 已快速消耗,却没有任何后台清理机制介入。理解这一点是排查此类问题的关键。

小文件为何先耗尽 inode

ext4 在创建时固定 inode 总数,默认每 16384 字节一个 inode。每个非空文件至少占用一个 4 KiB 块,因此大量小文件会先耗尽 inode,而磁盘空间仍有富余。文章中的实验显示,64 MiB 文件系统在放入 4000 个一字节文件后,inode 使用率高达 97.9%,而块使用率仅 32.6%。这解释了为何磁盘未满却出现 inode 告警。

containerd 快照重复放大文件数

containerd 的 overlayfs 快照器会为每个不同摘要的层单独解包,即使内容相同也不共享。案例中,多个快照都包含 app/node_modules/@mui/icons-material 目录,每个快照有 21,553 个文件。单阶段构建且未使用 .dockerignore 时,COPY . . 可能将 node_modules 重复打入新层,每次构建产生新摘要,导致节点上快照数量成倍增加,inode 消耗急剧上升。

修复与监控建议

修复应从构建入手:采用多阶段 Dockerfile,最终镜像只包含编译产物,可将文件数从数万降至几十。监控方面,除了依赖 NodeFilesystemFilesFillingUp 这类预测告警,还应增加 inode 使用率超过 80% 的静态阈值告警,并设置 for: 30m 避免误报。清理现有快照可用 crictl rmi --prune,但避免在 cron 中使用 crictl rm --all,以免丢失日志。

❓

Q&A

Kubelet 在什么情况下会因 inode 不足而驱逐 Pod?

Kubelet 默认在 inode 剩余 5% 时触发硬驱逐,即 inode 使用率达到 95% 时才会采取行动。

为什么镜像 GC 无法清理掉占用大量 inode 的镜像?

镜像 GC 只关注字节使用率,默认在镜像文件系统使用率达到 85% 时触发,而 inode 使用率不在其监控范围内,因此即使 inode 耗尽也不会触发清理。

containerd 快照存储为什么会重复占用大量 inode?

containerd 的 overlayfs 快照器会将每个不同的镜像层解包到独立目录,即使层内容相同但摘要不同,也会产生多份副本,导致像 node_modules 这样的目录被重复存储,每个快照可能包含数万个文件。

如何快速定位节点上哪些目录占用了最多 inode?

可以使用命令 `du --inodes -xS /var | sort -rh | head -n 20`,其中 `-x` 避免跨越文件系统,`-S` 单独统计每个目录,从而找出 inode 使用最多的目录。

如何通过多阶段构建减少镜像中的文件数量?

使用多阶段 Dockerfile,例如一个阶段用于安装依赖和构建,最终阶段仅复制编译产物到轻量基础镜像(如 nginx:alpine),这样部署镜像的文件数可从数万减少到几十个。

应该添加什么样的监控告警来提前发现 inode 耗尽风险?

可以添加基于 inode 使用率的告警,例如当根文件系统 inode 使用率超过 80% 并持续 30 分钟时触发,以早于 kubelet 的 95% 驱逐阈值。

🏷️

标签

➡️

继续阅读