Linux 报 No space left on device 时,“找最大的文件删掉”并不总是正确。文件系统可能是数据块用尽,也可能是 inode 用尽;还可能存在文件已经从目录删除、但仍被进程打开,导致 du 看不到而 df 仍显示空间被占用。正确诊断必须先确认发生错误的挂载点,再分别检查块、inode 和打开文件。
适用范围与先给结论
本文适用于常见 Linux 文件系统和容器宿主机上的 ENOSPC 故障。V2CE 复现实验使用 Alpine 3.20 与两个容器内 tmpfs:一个限制为 4 MiB 数据空间,另一个限制为 64 个 inode。两项限制都不会消耗完宿主机文件系统。
第一轮只读检查应包含:
findmnt -T /发生错误的路径
df -hT /发生错误的路径
df -i /发生错误的路径
sudo lsof +L1
四条命令回答的是不同问题:路径落在哪个挂载点、数据块是否满、inode 是否满、是否存在已删除但仍打开的文件。只看根分区的 df -h / 可能错过单独挂载的 /var、容器卷或临时文件系统。
第一步:锁定真正失败的路径和挂载点
先从应用错误日志中找到具体路径,例如日志文件、数据库目录、上传目录或临时目录。然后运行:
findmnt -T /var/lib/my-app/data.db
stat -f /var/lib/my-app/data.db
df -hT /var/lib/my-app/data.db
df -i /var/lib/my-app/data.db
如果目标文件尚不存在,对它的父目录执行检查。记录文件系统类型、设备、挂载点、总量、可用量和采样时间。容器内看到的路径可能是 overlay 层、bind mount、named volume 或 tmpfs,应同时从容器配置确认真实存储位置。
第二步:区分数据块满与 inode 满
数据块耗尽
df -h 的 Use% 接近 100%、Available 为 0,通常表示可分配数据块不足。先在同一文件系统内按一级目录统计,不跨越其他挂载点:
sudo du -xhd1 /var 2>/dev/null | sort -h
sudo du -xhd1 /var/lib 2>/dev/null | sort -h
sudo find /var -xdev -type f -size +500M -printf '%s %p\n' 2>/dev/null | sort -n
不要一开始就在整台机器运行无边界的递归扫描;繁忙磁盘上 du 和 find 本身会增加 I/O。先从错误路径所在挂载点和最可能增长的日志、缓存、上传、备份、数据库目录开始。
inode 耗尽
df -h 可能仍有大量空间,但 df -i 的 IUse% 已是 100%。这通常来自海量小文件,例如失控的 session、缓存分片、邮件队列或临时文件。此时删除一个大文件可能释放很多数据块,却只释放一个 inode。
在受控范围内统计文件数量,逐层缩小:
sudo find /var/lib/my-app -xdev -type f -printf '%h\n' 2>/dev/null \
| sort | uniq -c | sort -n | tail -30
大型目录上该命令可能很慢;生产环境优先使用已有文件系统监控、应用指标或分批采样。确认文件生命周期和业务归属后再清理,不能仅凭数量多就批量删除。
第三步:解释 df 与 du 为什么可能不一致
当进程打开文件后,管理员从目录中删除该文件,目录项会消失,du 无法再遍历到它;但只要文件描述符仍打开,文件系统数据块就不会释放。检查链接数为 0 的打开文件:
sudo lsof +L1
重点记录进程、PID、文件描述符、文件大小和路径。优先让应用按正常方式重新打开日志,或在变更窗口滚动重启对应实例。不要不加判断地终止数据库等关键进程;它可能触发恢复、复制中断或更长停机。
此外,快照、文件系统保留块、用户配额、容器 overlay 层和存储驱动也会影响可用空间。若普通用户写入失败而 root 尚有少量空间,应检查文件系统保留与配额,不能通过长期以 root 运行应用解决。
第四步:先释放可控的应急空间
先暂停会继续快速写入的任务或摘除流量,再选择已确认可再生成、可归档或已过保留期的数据。常见候选包括应用缓存、已轮转且确认上传成功的旧日志、失败的临时制品和重复备份。每项删除都要有明确路径、时间范围和恢复来源。
对于 systemd journal,先查看实际占用和配置:
journalctl --disk-usage
systemd-analyze cat-config systemd/journald.conf
不要把直接清空当前日志、数据库文件或容器存储目录当成通用方案。Docker 宿主机也不应手工删除 /var/lib/docker 内部文件;应通过 Docker 自身的对象清单确认镜像、构建缓存、容器和卷的所有权。
第五步:恢复后找出增长来源
释放少量空间只能恢复写入能力。继续检查故障窗口内哪个目录、日志或指标增长最快,并处理根因:
- 修复无限日志、异常重试或未设置上限的缓存;
- 为日志配置轮转、压缩、保留天数和失败监控;
- 为临时文件和 session 配置生命周期;
- 把备份上传与本地清理做成可验证的两阶段流程;
- 对块使用率、inode 使用率和增长速度分别设置告警。
容量规划要看增长速度和清理失败后的缓冲时间,而不是只在 95% 设置一个静态阈值。大量小文件系统必须单独监控 inode。
复核与回滚
处置后重复 df -hT、df -i 和应用真实写入检查,确认可用块/inode 已恢复、错误日志不再增长、数据库或队列没有遗留损坏。若执行了服务重启,还要验证健康检查、数据恢复和复制状态。
删除操作通常不可直接回滚,因此操作前必须确认备份或再生成方式;配置变更则保留旧配置并在语法检查后应用。若清理对象不确定,先移动到同一文件系统的隔离目录不会释放空间,只适合阻止应用继续读取,不能作为容量恢复手段。
V2CE 复现实验记录
数据块实验把 /lab 限制为 4 MiB。写入 8 MiB 文件时,dd 在写入 4 MiB 后返回 No space left on device,df -h 显示 4.0 MiB 已用、0 可用、使用率 100%。截断隔离填充文件后,同一挂载点恢复为约 4.0 MiB 可用,并成功创建恢复标记。
inode 实验把另一个 16 MiB tmpfs 限制为 64 个 inode。创建第 64 个小文件时失败;此时 df -h 仍显示 16 MiB 可用、数据使用率 0%,而 df -i 显示 64/64、使用率 100%。释放 10 个实验文件后成功新建恢复标记,最终还有 9 个 inode 可用。这证明两种 ENOSPC 必须使用不同证据和处置方式。