凌晨两点半,工位旁边的窗外连一辆车都没有,只有机房冷风机组低频的嗡嗡声透过墙壁传过来。我端着半杯已经完全冷透的咖啡,看着屏幕上飞速滚动的告警。
手机里的 PagerDuty 刚才疯狂叫唤,生产环境 K8s 集群的核心计算节点接连报出 NodeNotReady 和 DiskPressure,随后就是大规模的 Pod 驱逐(Eviction)。这是一个典型的级联故障前兆。
我登入其中一台报警的节点,习惯性地敲下 df -Th,结果并不意外:/var/lib/containerd 所在的根分区使用率 100%。
但在敲下 du -sh /* --exclude proc --exclude sys --exclude dev 之后,有趣的事情发生了——整个文件系统所有目录加起来的真实占用,连磁盘总容量的 30% 都不到。
磁盘满了,但文件找不到。
对于任何一个摸过几年 Linux 的人来说,这个现象几乎是条件反射般的常识:有被删除的文件依然被进程占用着句柄(File Descriptor)。
我顺手敲下排查命令:
lsof +L1 | grep deleted | sort -n -k 7 | tail -n 20
屏幕上齐刷刷地列出了几百个属于 containerd 的进程状态,每个后面都带着一个大大的 (deleted) 标记,占用空间从几个 G 到十几 G 不等。文件路径全都指向 /var/log/pods/。
顺藤摸瓜,我翻看了一下 /var/spool/cron/root,一行带着浓烈“聪明才智”的定时任务赫然在列:
*/10 * * * * find /var/log/pods/ -name "*.log" -type f -size +5G -exec rm -f {} \;
看着这行命令,我不仅揉了揉眉心,甚至有点被气笑了。
去问了下负责这个微服务集群的某位同事,果然,他下午发现某个业务 Pod 疯狂吐日志,导致节点磁盘告警。为了“快速解决问题”,他没有去改应用的日志级别,也没有去调整 Kubelet 的日志轮转配置,而是直接在节点上挂了个 Cronjob,暴力 rm 掉大于 5G 的日志文件。
在这个技术点上犯错,且直接应用在生产环境,不仅是愚蠢,更是对操作系统底层原理的极度匮乏。
在 Linux 的 VFS(虚拟文件系统)机制中,文件是以 Inode(索引节点)和 Block(数据块)两部分存在的。当我们在终端里执行 rm 命令时,底层调用的是 unlink() 系统调用。这个操作仅仅是删除了文件在目录项(dentry)里的名字,并把 Inode 的硬链接数(i_nlink)减一。
但是,要想真正释放磁盘空间,Linux 内核有两个硬性条件:
1. 文件的硬链接数为 0。
2. 没有任何进程打开这个文件(即 i_count 为 0)。
在这个场景下,容器引擎(containerd/dockerd)正在源源不断地把容器的标准输出流(stdout/stderr)写入这些 .log 文件。进程手里死死攥着这个文件的 File Descriptor。你用 rm 把文件名字扬了,骗过了 ls 和 du,但只要容器不重启,写入操作就不会停,数据块依旧被占用,而且由于目录项被删除,这些空间变成了名副其实的“暗物质”。
最终的结果就是:节点磁盘被这些看不见的文件彻底撑爆,触发 Kubelet 的硬驱逐阈值(evictionHard: imagefs.available<15%),导致节点上正常运行的其他业务 Pod 被无辜连坐,集群出现雪崩。
解决眼下的问题不难。由于原文件已经被 rm,用 > file 或者 truncate 清空已经不可能了,唯一的办法只能是让持有句柄的进程释放它们。
我写了个简单的脚本,把那些挂载了被删文件的容器找出来,通过 API 删掉 Pod 重建;对于实在太多的节点,挑着业务低谷期直接重启了 containerd 服务:
systemctl restart containerd
看着 df -h 里的使用率瞬间从 100% 跌回 22%,警报终于解除。
在这个行业里,遇到日志打满磁盘不是什么罕见事,但解决问题的方式决定了系统的稳定性。如果非要手动干预,正确的做法是用 truncate:
truncate -s 0 /var/log/pods/namespace_pod_uid/container/*.log
这不仅清空了文件内容,释放了 Block,同时也保留了 Inode 和 File Descriptor,容器运行时完全无感,可以继续正常写入。
但从架构的视角来看,任何需要登入节点去手动清日志的设计都是不合格的。根本的解决路径,要么在 Kubelet 的配置里把门焊死:
# /var/lib/kubelet/config.yaml
containerLogMaxSize: "500Mi"
containerLogMaxFiles: 3
要么在应用层引入成熟的日志收集与轮转机制(如 Filebeat/Fluent-bit 配合 Logrotate)。
凌晨三点一刻,集群状态全绿。我关掉终端,合上电脑。
不要总试图用外挂脚本去对抗操作系统的底层逻辑。在 Linux 面前耍小聪明,内核迟早会在你睡得最熟的深夜,狠狠给你一巴掌。