Loading... # Kubernetes 节点根分区磁盘占用过高排查复盘 # 一、事件概述 ## 1. 事件背景 2026 年 7 月 15 日上午,监控发现 K8s 工作节点 zcyun-k8s-node5(IP:10.10.184.204)根分区使用率持续攀升至 89%,逼近告警阈值,存在磁盘写满导致节点上所有 Pod 业务中断的风险。该节点承载物联网(IoT)数据、规则引擎、设备管理等核心服务。 ## 2. 影响范围 ### A. 影响资源 节点根分区 /dev/vda1(总容量 40G,已用 34G,剩余 4.5G) ### B. 风险等级 P2 级(尚未触发业务故障,但距离写满仅一步之遥) ### C. 潜在影响 若根分区写满,将导致节点上 28 个容器无法写入日志与临时数据,引发服务大面积异常、数据库连接失败、Pod 被驱逐等连锁问题。 ## 3. 处理结果 经排查定位并处置后,根分区使用率从 89% 下降至 23%,释放约 25G 空间,节点恢复正常水位。 # 二、排查过程 ## 1. 排查思路 磁盘使用率过高的问题,按「先排除 inode 耗尽,再定位大文件归属,最后定位到具体文件」的层级化思路推进,整体决策流程如下。 ```mermaid graph TD A["df 显示根分区 89%"] --> B{"inode 使用率?"} B -->|"仅 5%"| C["排除碎文件问题<br/>锁定大文件"] C --> D["du 逐层定位<br/>/var 29G"] D --> E["/var/lib/docker 24G<br/>overlay2 可写层"] E --> F["docker system df<br/>Containers 22.6G"] F --> G["docker ps -s 定位容器"] G --> H["iot-data 11.4G<br/>iot-rule 10.9G"] H --> I["进入容器可写层 UpperDir"] I --> J["rocketmq_client.log<br/>10 份 1.1G 归档"] J --> K["根因锁定"] ```  ## 2. 步骤一:确认磁盘与 inode 状态 首先通过 df 命令确认磁盘整体情况,并检查 inode 使用率以排除「小文件过多导致 inode 耗尽」的可能。 ```bash df -h # 根分区 40G,已用 34G,89% df -i # inode 仅使用 5% ``` 结论:inode 使用率仅 5%,排除碎文件问题,判定为「少数大文件」占用空间。 ## 3. 步骤二:逐层定位大目录 使用 du 命令从根目录逐层向下统计,定位空间占用大头。 ```bash du -hx --max-depth=1 / # /var 29G du -hx --max-depth=1 /var # /var/lib 24G du -hx --max-depth=1 /var/lib # /var/lib/docker 24G ``` 结论:/var/lib/docker 占用 24G,其中 overlay2 目录为最大头;镜像本身仅 2.8G,说明问题出在容器运行时数据。 ## 4. 步骤三:区分镜像与容器可写层 通过 docker system df 区分镜像缓存与容器可写层占用。 ```bash docker system df ``` 关键发现:Containers 项 SIZE 为 22.61GB,但 RECLAIMABLE 为 0。这意味着占用来自「容器运行时写入可写层」的数据,而非镜像缓存,因此常规的 docker prune 清理无效。 ## 5. 步骤四:定位到具体容器 通过 docker ps -s 查看每个容器可写层大小,按体积排序。 ```bash docker ps -s --format 'table {{.Size}}\t{{.Names}}\t{{.Image}}' ``` 定位到两个异常容器: | 容器 | 可写层大小 | 镜像 | | --- | --- | --- | | xcloud-srv-iot-data | 11.4G | new-iot/xcloud-srv-iot-data | | xcloud-srv-iot-rule | 10.9G | new-iot/xcloud-srv-iot-rule | 两者合计约 22G,与容器总占用 22.61G 完全吻合。 ## 6. 步骤五:进入可写层定位具体文件 通过 docker inspect 获取容器可写层路径(UpperDir),直接查看其中写入的大文件。 ```bash docker inspect <容器名> --format '{{.GraphDriver.Data.UpperDir}}' # /var/lib/docker/overlay2/<hash>/diff ``` 在两个容器的 /root/logs/rocketmqlogs/ 目录下,均发现大量 RocketMQ 客户端日志: | 文件 | 大小 | | --- | --- | | rocketmq_client.log.1 ~ rocketmq_client.log.10 | 每份 1.1G | | rocketmq_client.log(当前写入) | 191M ~ 653M | ## 7. 步骤六:分析日志内容确认根因 查看当前日志文件内容,发现全是 RocketMQ 客户端在 INFO 级别高频打印的持久化日志。 ``` 2026-07-15 08:49:56 INFO RocketmqClient - [persistAll] Group: iot-data-online-g updateConsumeOffsetToBroker MessageQueue [topic=iot-device-online-changed, brokerName=broker-a, queueId=0] 16020 ``` 日志在同一毫秒内针对每个 MessageQueue 打印一条,由于该服务订阅了多 topic、多 group、多 queueId、双 broker,每次心跳与持久化周期都会刷出大量 INFO 行,导致日志文件持续膨胀。 # 三、根因分析 ## 1. 磁盘占用构成 根分区 34G 占用的构成关系如下图所示,容器可写层中的 RocketMQ 日志占据了绝对主体。 ```mermaid graph LR A["/dev/vda1 占用 34G"] --> B["容器可写层 22G"] A --> C["journald 日志 3.9G"] A --> D["镜像层 2.8G"] A --> E["系统与其它 5.3G"] B --> B1["iot-data<br/>RocketMQ 日志 11.4G"] B --> B2["iot-rule<br/>RocketMQ 日志 10.9G"] ```  ## 2. 直接原因 两个 IoT 服务容器内嵌的 RocketMQ 客户端,在默认 INFO 日志级别下高频打印 persistAll 与 updateConsumeOffsetToBroker 日志;日志写入容器可写层(overlay2 的 UpperDir),单文件上限 1G、滚动保留 10 份,导致每个容器稳定占用约 11G。 ## 3. 根本原因(5 Whys 分析) ### A. 为什么磁盘占用这么高? 两个容器的 RocketMQ 客户端日志累积到约 22G。 ### B. 为什么日志会累积到这么大量? 客户端日志级别配置为默认的 INFO,而该级别下会按 MessageQueue 维度高频打印持久化日志。 ### C. 为什么已有的定时清理没有生效? 原有的 clearDockerLog.sh 脚本只清理 /var/lib/docker/containers/ 下的 stdout 日志(json.log),而 stdout 日志全机仅 1M;真正的元凶是容器可写层内的应用日志,原脚本完全不涉及。 ### D. 为什么日志写在容器可写层而非挂载卷? 应用将日志输出到容器内 /root/logs 目录,未通过 emptyDir、hostPath 或 PVC 挂载到独立存储,也未接入 logrotate 轮转。 ### E. 为什么客户端日志级别未做约束? RocketMQ 客户端默认日志级别为 INFO,应用启动参数未通过 -Drocketmq.client.logLevel 显式调低,缺少日志治理规范。 ## 4. 深层反思 容器可写层本质是临时存储,不应承载业务日志。日志要么输出到 stdout 由容器运行时统一接管并轮转,要么挂载到独立卷并配置切分。本次问题暴露了日志治理与容器存储规范的双重缺失。 # 四、解决方案 ## 1. 临时方案:优化定时清理脚本 对原有 clearDockerLog.sh 脚本进行增强,使其覆盖三类元凶,并复用已有的 cron 任务(/etc/cron.d/logclear,每 10 分钟执行一次)。 清理脚本的核心逻辑如下: ```bash # 1) 容器 stdout 日志(原有功能) find /var/lib/docker/containers/ -type f -name '*-json.log' | while read f; do : > "$f"; done # 2) 容器可写层内的应用日志(新增,命中本次根因) docker ps -q | while read cid; do upper=$(docker inspect "$cid" -f '{{.GraphDriver.Data.UpperDir}}') # 已轮转归档日志:直接 rm 立即释放(不被进程持有) find "$upper" -type f \( -name '*.log.[0-9]*' -o -name '*log*.gz' \) -exec rm -f {} \; # 当前写入的大日志:truncate 清空 find "$upper" -type f -name '*.log' -size +50M -exec sh -c ': > "$1"' _ {} \; done # 3) journald 系统日志(新增) journalctl --vacuum-size=200M ``` 执行后效果:overlay2 占用从 24G 降至 7.5G,docker system df 的 Containers 从 22.6G 降至 1.1G。 ## 2. 一个关键坑:df 显示滞后 清理归档日志后,df 一度仍显示 77% 使用率,而 du 与 docker system df 都显示真实占用仅 8.6G。经过多轮排查(排除 deleted-but-open 文件、mmap 映射、孤儿块、保留块后),最终定位为 Linux 内核在大量文件删除后的块使用统计延迟(VFS lazy accounting)。 ```bash sync && echo 3 > /proc/sys/vm/drop_caches # 刷新内核统计 df -h / # 立即显示真实值 23% ``` ```mermaid sequenceDiagram participant U as 运维 participant S as 服务器 participant D as Docker participant C as 清理脚本 U->>S: df 发现根分区 89% U->>S: du 逐层定位 S-->>U: /var/lib/docker 24G U->>D: docker ps -s 定位容器 D-->>U: iot-data / iot-rule 共 22G U->>S: 查看可写层日志 S-->>U: RocketMQ 日志爆炸 U->>C: 优化清理脚本并手动执行 C-->>S: 删除归档 + vacuum journal U->>S: df 仍显示 77%(统计延迟) U->>S: sync + drop_caches S-->>U: 真实使用率 23% ```  经验:清理大文件后若 df 未及时下降,应先执行 sync 与 drop_caches 刷新内核统计,避免误判为文件残留或文件系统损坏,切勿贸然 fsck。 ## 3. 永久方案:从源头治理日志 定时清理只是兜底,RocketMQ 日志仍会持续产生。根治需从应用侧入手,在启动参数中调低客户端日志级别: ``` -Drocketmq.client.logLevel=WARN ``` 调整后,INFO 级别的 persistAll 刷屏日志将停止输出,从源头杜绝日志膨胀。 ## 4. 预防措施 ### A. 日志治理规范 - 业务日志统一输出到 stdout,由容器运行时接管并配置 json 日志轮转(max-size、max-file) - 若需文件日志,必须挂载到 emptyDir 或独立卷,并接入 logrotate ### B. 监控告警前置 - 对节点磁盘使用率设置分级告警(如 75% 预警、85% 紧急) - 纳入容器可写层增长监控,及时发现异常写入的 Pod ### C. 清理脚本完善 保留增强后的清理脚本作为兜底机制,但应明确其仅处理症状,根治仍依赖应用侧日志级别调整。 # 五、经验总结 ## 1. 排查方法论 - **inode 优先**:磁盘高占用先查 df -i,快速区分「碎文件」与「大文件」两类问题 - **层级化定位**:du 逐层缩小范围,再用 docker system df 区分镜像与可写层 - **docker prune 的局限**:RECLAIMABLE 为 0 表示占用在运行时可写层,prune 无效,需从可写层入手 ## 2. 关键认知 ### A. 容器可写层非持久存储 可写层(overlay2 UpperDir)随容器生命周期存在,不应承载重要日志。容器删除重建后可写层即清空,这也提供了一种「重置可写层」的应急手段。 ### B. stdout 日志与可写层日志是两回事 /var/lib/docker/containers/ 下的 json 日志是容器标准输出,而应用写到的容器内文件落在可写层,两者清理方式完全不同。排查时需明确区分。 ### C. df 统计可能滞后 大量删除文件后,df 的使用率可能因内核统计延迟而不准确,需结合 du、docker system df 交叉验证,必要时 sync + drop_caches 刷新。 ## 3. 流程优化建议 - 建立 RocketMQ、Kafka 等消息中间件客户端的日志级别基线规范 - 将本次增强的清理脚本推广到其它 K8s 节点 - 排查类问题形成标准化 playbook,缩短定位时间 *** ## 参考资料 1. [RocketMQ Client 日志级别配置说明](https://rocketmq.apache.org/docs/) 2. [Docker 存储驱动 overlay2 工作原理](https://docs.docker.com/storage/storagedriver/overlayfs-driver/) 3. [Linux drop_caches 与内存回收](https://www.kernel.org/doc/Documentation/sysctl/vm.txt) 最后修改:2026 年 07 月 15 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏