一次K8s节点磁盘压力导致Pod批量驱逐的排查实录
问题背景
某日上午 10:00 左右,监控平台开始陆续推送 K8s 集群告警:多个 Pod 处于 Pending 或 Evicted 状态,部分服务出现间歇性不可用。
查看集群状态,发现有 3 个工作节点出现 DiskPressure 状态,kubelet 开始自动驱逐节点上的 Pod 以释放空间,导致相关服务的副本数不足,触发了监控告警。
集群为 K8s v1.26,节点操作系统为 CentOS 7,每个节点配置 100GB 系统盘(/ 分区)。
故障现象
kubectl get nodes显示 3 个节点状态带有DiskPressure条件- 受影响节点上的 Pod 被驱逐(Evicted),重新调度到其他节点后其他节点也逐渐出现磁盘压力
kubectl describe node <node-name>显示:
1 | Conditions: |
- 被驱逐的 Pod 状态为
Evicted,大量积压:
1 | kubectl get pods -A | grep Evicted | wc -l |
排查过程
第一步:查看节点磁盘使用情况
登录出现 DiskPressure 的节点(以 node-03 为例):
1 | df -h |
输出:
1 | Filesystem Size Used Avail Use% Mounted on |
系统盘使用率 99%!几乎满了。
第二步:找出磁盘占用大的目录
1 | du -sh /* 2>/dev/null | sort -rh | head -20 |
输出:
1 | 42G /var |
/var/log 占用了 38GB!进入查看:
1 | du -sh /var/log/* | sort -rh | head -20 |
输出:
1 | 35G /var/log/containers |
/var/log/containers 占用 35GB,这是容器日志目录。
第三步:分析容器日志目录
1 | ls -lhS /var/log/containers/ | head -20 |
发现有几个日志文件异常巨大:
1 | -rw-r----- 1 root root 18G Mar 12 09:55 business-api-xxx_default_business-api-xxxxxxx.log |
business-api 这个容器的日志文件高达 18GB,data-sync 的日志 9GB。
第四步:查看 kubelet 日志轮转配置
K8s 的 kubelet 支持对容器日志进行自动轮转,相关参数为:
1 | cat /etc/kubernetes/kubelet-config.yaml | grep -i log |
输出:
1 | containerLogMaxSize: "10Mi" |
等等——配置明明设置了单个日志文件最大 10MB,为什么实际文件有 18GB?
第五步:确认 kubelet 是否真正加载了该配置
1 | ps aux | grep kubelet |
输出:
1 | /usr/bin/kubelet --config=/etc/kubernetes/kubelet-config.yaml ... |
看起来配置文件是加载了的,但再仔细检查配置文件:
1 | cat /etc/kubernetes/kubelet-config.yaml |
1 | apiVersion: kubelet.config.k8s.io/v1beta1 |
配置看起来正确……但为什么没有生效?
第六步:深入排查日志轮转未生效的原因
查看 kubelet 的实际运行时日志:
1 | journalctl -u kubelet --since "2 hours ago" | grep -i "log" |
发现一条警告:
1 | W0312 09:23:15.123456 1234 kubelet.go:567] "Failed to rotate container log" err="error truncating file: ..." containerID="..." |
日志轮转失败!原因是 error truncating file,进一步查看:
1 | journalctl -u kubelet --since "2 hours ago" | grep -i "truncat" |
1 | W0312 ... "Failed to truncate log file" path="/var/log/containers/business-api-xxx.log" err="truncate /var/log/containers/business-api-xxx.log: read-only file system" |
关键信息:read-only file system,磁盘挂载为只读了!
1 | dmesg | tail -50 | grep -i "error\|readonly\|remount" |
1 | [1234567.890] EXT4-fs error (device sda1): ext4_journal_check_start:61: Detected aborted journal |
根本原因找到了:磁盘写满 → 文件系统出错(journal abort)→ 内核自动将文件系统 remount 为只读 → kubelet 无法轮转日志文件 → 日志文件不断增大(等等,如果文件系统只读,为什么日志还能写入?)
再仔细看:发现这台节点上 business-api 容器使用了 hostPath 挂载,日志输出路径是 /data/logs/,挂载的是另一块数据盘(/dev/sdb),而 /dev/sdb 挂载的 /data 目录没有配置日志轮转,数据盘的日志一直增长,而我们之前找到的 18GB 日志文件是软链接,指向了 /data/logs/ 下的实际文件。
1 | ls -la /var/log/containers/business-api-xxx.log |
数据盘 /dev/sdb 也满了,但报错发生在系统盘 /dev/sda1。这是两个叠加的问题。
解决方案
立即处理
清理已驱逐的 Pod 记录(Evicted 状态的 Pod 不会自动清除):
1 | kubectl get pods -A | grep Evicted | awk '{print $1, $2}' | xargs -n2 kubectl delete pod -n |
清理超大日志文件(谨慎操作,先确认不影响业务):
1 | # 对于仍在写入的日志,不要直接 rm,使用截断方式 |
修复系统盘只读问题(需要重启或 remount):
1 | # 运行 fsck(需要先 umount 或在 rescue 模式下操作) |
长期修复
1. 为应用容器的 hostPath 日志添加 logrotate 配置:
1 | # 创建 /etc/logrotate.d/business-api |
2. 对 K8s 容器日志设置合理的 size 限制和 TTL:
在应用的 K8s Deployment 中,使用 terminationMessagePolicy 和日志驱动层面控制。或推荐将应用日志输出到 stdout/stderr,由 kubelet 统一管理轮转(不使用 hostPath)。
3. 扩容数据盘并规划磁盘告警:
在磁盘使用率达到 70% 时告警,不等到 99% 再发现问题。
根因分析
两个问题叠加:
- 应用日志通过 hostPath 写入数据盘,且没有配置 logrotate,导致日志文件无限增长,最终耗尽数据盘空间
- 数据盘写满后的错误日志写到系统盘,导致系统盘也快速被填满,触发 EXT4 journal abort 和 read-only remount,引发 kubelet 日志轮转失败,进一步加速了磁盘占用
预防措施
- 所有使用 hostPath 挂载的应用必须配置 logrotate
- 容器日志优先使用 stdout/stderr + kubelet 自动轮转,不自行挂载日志目录
- 节点磁盘使用率告警阈值设置为 70%/85%/95% 三档
- 定期(每周)扫描所有节点上
/var/log/containers/和应用日志目录的大小
总结
K8s 集群中,Pod 驱逐事件往往是表象,真正的根因要深入到节点操作系统层面去排查。这次事件表明,日志管理是 K8s 运维中容易被忽视但影响显著的环节。无论是 kubelet 自带的日志轮转还是应用侧的 logrotate,都需要主动配置和验证,不能假设”默认会管好日志”。