
即使 df -h 显示还有数 GB 的空闲空间,某条命令仍可能以“设备上没有空间”(No space left on device)失败:
touch: cannot touch '/var/lib/app/cache/item': No space left on device
这条信息并不总是意味着文件系统耗尽了数据块。Linux 每创建一个新文件都需要一个空闲 inode,而报错的路径可能位于另一个挂载点或容量受限的内存文件系统上。Btrfs 在 df 仍报告有空闲容量时也可能耗尽可用的数据或元数据空间;当某个程序用尽 inotify 监视限制时,内核也会返回相同的错误。
本指南将说明如何按照实用的顺序逐项排查每个原因、定位已耗尽的资源,并安全地释放空间。
快速参考
| Check | 命令 |
|---|---|
| 受影响路径上的空间 | df -h /affected/path |
| 受影响路径上的 inode | df -ih /affected/path |
| 文件系统与源设备 | findmnt -T /affected/path |
| 占用 inode 较多的目录 | sudo du --inodes -x -d 2 /path | sort -n | tail -20 |
| 仍被打开的已删除文件 | sudo lsof +L1 |
| Btrfs 分配详情 | sudo btrfs filesystem usage /affected/path |
| inotify 监视限制 | sysctl fs.inotify.max_user_watches |
| Docker 存储用量 | docker system df |
| 日志大小 | sudo journalctl --disk-usage |
| APT 缓存大小 | sudo du -sh /var/cache/apt/archives |
检查报错的确切路径
首先将报错的路径传给 df,而不只是传给根目录:
df -h /var/lib/app/cache
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p5 40G 17G 22G 44% /var
Mounted on(挂载点)一列显示实际承载该路径的文件系统。此处报错的目录位于独立的 /var 文件系统上,而不是 df -h / 本应报告的根文件系统。一台服务器可能拥有独立的 /、/var、/home、/tmp、容器存储或应用数据文件系统,而它们各自独立地被填满。
使用以下命令显示源设备、文件系统类型和挂载点:
findmnt -T /var/lib/app/cache
TARGET SOURCE FSTYPE OPTIONS
/var /dev/nvme0n1p5 ext4 rw,relatime
如果 Use%(使用率)为 100%,则文件系统确实耗尽了数据块。可以使用 du 命令,或参考 Linux 中查找大文件的指南,来定位占用空间最大的目录和文件。
检查 inode 使用情况
当数据块使用率看起来正常时,检查同一路径的 inode 使用情况:
df -ih /var/lib/app/cache
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p5 2.6M 2.6M 0 100% /var
每个文件和目录都会消耗一个 inode。一个包含数百万个微小缓存、会话、队列或临时文件的文件系统,可能在字节层面仍有大量空闲容量时却已耗尽 inode。
如果 IFree(空闲 inode 数)为 0 或 IUse%(inode 使用率)为 100%,则 inode 耗尽就是原因所在。
找出占用 inode 最多的目录
GNU du 命令可以统计 inode 数量而非字节数。若要在不跨入其他已挂载文件系统的情况下检查 /var:
sudo du --inodes -x -d 2 /var 2>/dev/null |
sort -n |
tail -20
12540 /var/lib/systemd
48201 /var/lib/docker
73122 /var/cache
2310884 /var/lib/app/cache
2485119 /var/lib
2621402 /var
最大的数字出现在底部。在此示例中,仅 /var/lib/app/cache 一个目录就占用了文件系统中几乎全部的 inode。对占用最高的目录使用更大的深度再次运行该命令:
sudo du --inodes -x -d 3 /var/lib/app/cache 2>/dev/null |
sort -n |
tail -20
这样在删除任何文件之前先缩小排查范围。
要按父目录分组查看文件数量,可使用:
sudo find /var -xdev -type f -printf '%h\n' 2>/dev/null |
sort |
uniq -c |
sort -n |
tail -20
在一个包含数百万文件的文件系统上,该命令可能耗时较久。它不会删除任何文件。
安全删除多余的零散小文件
一旦定位到目录,先确认是哪个应用在使用它,以及这些文件是否可以丢弃。常见来源包括:
- 不会自动清理旧条目的应用缓存。
- PHP 会话目录。
- 邮件队列。
- 容器镜像层与构建缓存。
- 监控数据与轮转日志。
- 失败任务产生的临时文件。
删除旧临时文件前,先预览它们:
sudo find /tmp -xdev -type f -mtime +7 -print
如果列表中只包含可安全删除的文件,则加上 -delete 重新运行:
sudo find /tmp -xdev -type f -mtime +7 -delete
在了解应用目录的保留规则之前,不要对其执行范围过广的 find ... -delete 命令。如果存在应用自身的清理命令,应优先使用它。
在 Ubuntu 和 Debian 上,使用以下命令清理已下载的软件包归档:
sudo apt clean
检查系统日志(journal)当前占用的空间:
sudo journalctl --disk-usage
要删除超过 7 天的归档日志数据:
sudo journalctl --vacuum-time=7d
清理完成后,确认 inode 已重新可用:
df -ih /var/lib/app/cache
排查仍被打开的已删除文件
此项排查对应问题的另一种情形:df 报告文件系统已满,但 du 却无法解释这些用量。某个进程可能仍打开着一个已删除的文件。其目录项虽已消失,但内核在进程关闭文件描述符之前不会释放其数据块。
列出仍处于打开状态的已删除文件:
sudo lsof +L1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
java 4217 app 7w REG 259,3 8589934592 0 8123 /var/log/app.log (deleted)
示例显示有一个 8 GB 的已删除日志仍被一个 Java 进程占用。请在合适的维护窗口期内重启所属服务:
sudo systemctl restart app.service
请使用你进程的实际服务名。确认该文件已从 lsof +L1 的输出中消失,并再次用 df -h 检查空间。
关于已删除但被打开文件的详细说明,请参阅 lsof 命令指南。
检查 tmpfs 与容器文件系统
像 /run、/dev/shm,有时还有 /tmp 这样的路径可能使用 tmpfs,它由内存支撑并拥有独立的容量上限。直接检查报错的路径:
df -h /run /dev/shm /tmp
如果某个 tmpfs 挂载已写满,请删除相关应用拥有的陈旧文件,或在确认可用内存后再提高该挂载的容量上限。
容器还会通过叠加文件系统和挂载卷写入数据。宿主文件系统可能仍有空闲空间,而某个容器专属的挂载点或存储池却已满。在宿主机上,使用 findmnt -T 定位路径与挂载点;在容器内部,运行 df -h 与 df -i 来查看其自身的视角。
对于 Docker,可使用以下命令查看镜像、容器、本地卷与构建缓存所占用的空间:
docker system df
该命令会报告 Docker 的存储用量,但不会删除任何内容。在决定清理哪些对象之前,请先查看其详细输出。
检查 Btrfs 的数据与元数据分配
Btrfs 在独立的块组中分别管理数据与元数据。即使 df 报告的空闲空间总量看起来充足,其写时复制(copy-on-write)的分配方式仍可能返回“设备上没有空间”错误。
如果 findmnt 报告受影响路径的文件系统为 btrfs,则使用以下命令查看其分配详情:
sudo btrfs filesystem usage /affected/path
请综合查看 Device unallocated(设备未分配)、Free (estimated)(估算空闲)、Data(数据)和 Metadata(元数据)这几个数值。一个文件系统可能拥有空闲的数据空间,却没有足够的未分配容量来创建另一个元数据块组。
首先,根据保留策略删除不需要的文件或快照。随后,你可以使用以下命令回收完全未被使用的数据与元数据块组:
sudo btrfs balance start -dusage=0 -musage=0 /mountpoint
请将 /mountpoint 替换为 findmnt 显示的 Btrfs 挂载点。usage=0 的过滤项只选择空的块组,且不需要额外的工作空间。不要将未加过滤的 balance 作为通用的“磁盘已满”修复手段,因为它会重写所有被选中的块组,可能耗时很久,并且需要临时的空闲空间。
检查 inotify 监视限制
编辑器、构建工具、日志采集器与文件同步守护进程都通过 inotify 监视文件。每一个被监视的文件或目录都会消耗一个监视项,而内核会对每个用户施加数量限制。当某个程序超出该限制时,inotify_add_watch() 会失败并返回与“设备上没有空间”相同的错误,即便文件系统拥有空闲的数据块与 inode。应用程序常常以各自的措辞报告这一问题:
Error: ENOSPC: System limit for number of file watchers reached, watch '/srv/app/src'
查看当前限制:
sysctl fs.inotify.max_user_watches
fs.inotify.max_user_watches = 65536
该数值因发行版与内核版本而异。要找出是哪个进程在消耗监视项,可按文件描述符统计其数量:
sudo sh -c "grep -c '^inotify' /proc/*/fdinfo/* 2>/dev/null" |
awk -F: '$2 > 0 {print $2, $1}' |
sort -rn |
head
62184 /proc/2811/fdinfo/23
1204 /proc/1533/fdinfo/9
118 /proc/994/fdinfo/12
PID 显示在路径中,因此 ps -p 2811 -o comm= 可以列出持有这些监视项的程序。如果该程序正在监视一个它并不需要监视的目录(例如依赖目录或构建输出目录),应在程序自身的配置中将该目录排除,而不是提高限制。
当工作负载确实更需要更多的监视项时,可提高正在运行系统的限制:
sudo sysctl fs.inotify.max_user_watches=524288
让该修改在重启后仍持久生效:
echo 'fs.inotify.max_user_watches=524288' | sudo tee /etc/sysctl.d/90-inotify.conf
每个监视项都会消耗少量无法被换出(swap)的内核内存,因此应将限制设置为与工作负载相匹配,而不是设为系统所能接受的最大值。
检查文件系统与内核错误
如果空间与 inode 都可用,但写入仍然失败,请检查近期的内核消息:
sudo dmesg --level=err,warn | tail -50
同时检查系统日志:
sudo journalctl -k -p warning --since "1 hour ago"
请留意 Btrfs 分配警告、I/O 错误、文件系统损坏、只读重新挂载或存储设备故障。诸如 Input/output error(输入/输出错误)和 Read-only file system(只读文件系统)这类错误,需要的修复方式与“设备上没有空间”不同。可能需要进行文件系统检查,但不要在已挂载的文件系统上运行 fsck。应安排停机时间,先卸载文件系统,再按相应的恢复流程操作。
故障排查
df -h 显示有空间,但 df -i 显示 100%:文件系统已耗尽其 inode 表。找到包含海量文件的目录,然后依据所属应用的保留策略删除或归档文件。
df 显示的用量多于 du:很可能是某个进程仍打开着一个已删除的文件。运行 sudo lsof +L1,定位该进程,并重启所属服务以释放数据块。
错误仅发生在 /tmp、/run 或 /dev/shm:该路径可能是容量受限的 tmpfs 挂载。请针对该确切路径运行 df -h 与 df -i,而不是检查 /。
主机有空闲容量,Docker 却报告没有空间:使用 docker info、docker system df 与 findmnt -T /var/lib/docker 检查 Docker 的存储路径与文件系统。在删除之前,先查看未使用的镜像、容器、构建缓存与卷。
df 显示有空闲容量,Btrfs 却报告没有空间:运行 sudo btrfs filesystem usage /affected/path,对比数据、元数据与未分配空间。在删除不需要的文件或快照之后,限定为 -dusage=0 -musage=0 的 balance 可以回收空的块组,而无需启动一次完整的 balance。
编辑器、构建工具或同步守护进程报告没有空间,而 df 与 df -i 看起来都正常:该进程已触及 inotify 监视限制。检查 fs.inotify.max_user_watches,排除程序无需监视的目录,并在工作负载确实需要时于 /etc/sysctl.d/ 中提高该限制。
常见问题
什么是 inode?inode 存储文件或目录的文件系统元数据,例如其大小、所有者、权限、时间戳以及数据块的位置。每个文件系统的 inode 数量都是有限的,而每个文件无论大小通常都会消耗一个 inode。
能否为现有的 ext4 文件系统增加更多 inode?inode 数量是在创建文件系统时选定的,无法通过在线设置更改来增加。实际的解决办法是删除多余文件、将工作负载迁移到另一个文件系统,或者以更小的 bytes-per-inode(每 inode 字节数)比例重新创建文件系统,以适应大量小文件。
为什么删除大文件后空间没有立即释放?一个正在运行的进程可能仍打开着那个已删除的文件。Linux 只在最后一个文件描述符关闭时才释放数据块,因此 df 仍会将其报告为已用空间。运行 sudo lsof +L1 找到该进程,并重启所属服务。
为什么在拥有数 TB 空闲空间的系统上也会出现该错误?失败的写入受限于总容量之外的其他因素,最常见的是特定挂载点的 inode 数量、容量受限的 tmpfs、容器存储、Btrfs 分配或 inotify 监视限制。请始终检查报错的确切路径,而不是根文件系统。
总结
当 df -h 显示有空闲空间时,应使用 df -ih 检查受影响路径,并借助 findmnt 确认其挂载点与文件系统类型。inode 耗尽、已满的嵌套挂载点或 tmpfs、容器存储、Btrfs 分配以及 inotify 监视限制,各自需要不同的修复方式;而 lsof +L1 则解决另一种情形——df 已满,但 du 却无法解释这些用量。