磁盘空间报警但 du 对不上:先区分块、inode 与已删除文件

57 次浏览3 条回复

适用于 Linux 上排查 df 显示文件系统接近满载、但 du 汇总明显较小的情况。命令基线为 GNU coreutils 8.32+、util-linux 2.36+;lsof 为可选工具,示例挂载点为 /,执行前应替换为实际目标。先采集证据,不要一开始就删除文件或重启整机。

1. 先确认满的是空间还是 inode

date -u
df -hT /
df -ih /
findmnt -T / -o TARGET,SOURCE,FSTYPE,OPTIONS

Use% 接近 100% 表示数据块紧张;IUse% 接近 100% 表示 inode 紧张,常见原因是大量小文件。findmnt 用于确认后续命令针对的真实文件系统,避免把容器层、临时挂载或其他分区混在一起。

2. 用同一文件系统口径汇总目录

sudo du -xhd1 / 2>/dev/null | sort -h
sudo du -xhd1 /var 2>/dev/null | sort -h

-x 禁止跨越文件系统边界,因此结果才适合和目标文件系统的 df 对照。若 inode 满而空间并不大,可改看:

sudo du --inodes -x -d1 / 2>/dev/null | sort -n

大目录应继续逐层下钻,不要直接用不限制范围的 find /,生产主机上全盘遍历可能产生明显 I/O。

3. 检查“已删除但仍被进程打开”的文件

sudo lsof -nP +L1

这类文件已经不在目录树中,所以 du 看不到;只要进程仍持有文件描述符,文件系统就不会释放对应块。重点记录 COMMANDPIDFDSIZE/OFF 和路径。

没有 lsof 时可先做只读确认:

sudo find /proc/[0-9]*/fd -lname '* (deleted)' -printf '%p -> %l\n' 2>/dev/null

修复应针对持有者:让服务按其文档重新打开日志,或在确认可恢复后重启该单个服务。不要直接截断 /proc/<pid>/fd/<n>;文件描述符可能不是日志,写入或截断会破坏进程状态。操作后重新运行 lsof +L1df -hT,确认占用确实下降。

4. 排除挂载点遮住底层文件

findmnt -R /
mountpoint /var/lib/example

如果应用先向目录写入大量数据,随后又把另一个文件系统挂到同一路径,底层文件仍占用原文件系统,但普通 du 只能看到挂载后的内容。不要为检查而直接卸载生产挂载;应在维护窗口或独立挂载命名空间中查看底层目录,并先确认相关服务不会继续写入。

5. 按文件系统类型检查额外占用

ext4 可能保留一部分块供特权进程使用。只读查看:

SOURCE=$(findmnt -T / -n -o SOURCE)
sudo tune2fs -l "$SOURCE" 2>/dev/null | \
  grep -E 'Block count|Reserved block count|Block size'

仅当 FSTYPE 为 ext2/ext3/ext4 时使用 tune2fs。保留块不是异常,也不要在没有容量规划和恢复空间的前提下直接修改比例。Btrfs、ZFS 以及带快照的存储应使用各自的空间统计工具;CoW、快照和配额会使 df 与目录级 du 不再是一一对应关系。

6. 闭环标准

一次排查至少应留下这组结果:

  1. 明确问题是数据块、inode,还是两者同时紧张。
  2. dfdu -xfindmnt 针对同一个文件系统。
  3. 已删除但仍打开的文件、隐藏在挂载点下的文件、快照或保留块均已逐项确认。
  4. 处理后再次记录 df -hTdf -ih,并确认相关服务仍正常写日志和响应健康检查。

如果释放后空间很快再次增长,应继续定位增长速率和文件创建者,而不是把定期删除文件当作最终修复。

再补一个容器场景的口径:如果 findmnt -T / 显示根文件系统为 overlay,容器内的 df / 反映的是后端挂载整体占用,而 du -x / 只统计当前容器合并视图中可见的目录。其他容器的可写层、镜像层、构建缓存或运行时日志,都可能造成两者差距。

以 Docker Engine 20.10+ 为例,应回到宿主机先确认运行时根目录及其真实文件系统,再做只读对照:

ROOT=$(docker info --format '{{.DockerRootDir}}')
printf 'DockerRootDir=%s\n' "$ROOT"
findmnt -T "$ROOT" -o TARGET,SOURCE,FSTYPE,OPTIONS
df -hT "$ROOT"
docker system df -v
sudo du -xhd1 "$ROOT" 2>/dev/null | sort -h

docker system df -vRECLAIMABLE 只是候选估算,不能据此直接清理;还应核对正在运行的容器、镜像依赖和构建任务。闭环时在宿主机对同一 DockerRootDir 所在文件系统重新执行 df -hT,并确认容器健康检查与日志写入正常。若使用 containerd、CRI-O 或 Kubernetes,应先查对应运行时的数据根目录,不要套用 Docker 路径。

补一个 Btrfs 分支:df 统计整个 Btrfs 文件系统的块分配,而从某个挂载子卷执行 du -x 只看到该挂载视图中的目录;未挂载的其他子卷、快照、文件系统元数据和共享 extent 都可能让两者无法直接对应。反过来,把多个快照的 du 相加又可能重复计算共享 extent,不能当作物理占用。

前提是目标确认为 Btrfs,并已安装 btrfs-progs 5.4+。将 MNT 替换为目标挂载点,先做只读核查:

MNT=/var/lib/example
findmnt -T "$MNT" -o TARGET,SOURCE,FSTYPE,OPTIONS
df -hT "$MNT"
sudo btrfs filesystem usage -T "$MNT"
sudo btrfs device usage "$MNT"
sudo btrfs subvolume list -s "$MNT"

btrfs filesystem usage -T 应重点对照 Data、Metadata、System 的 UsedTotal 和各设备的未分配空间;出现 ENOSPC 时不能只看 Data,Metadata 接近分配上限、存储配置或设备可分配空间不足也可能是原因。subvolume list -s 只能证明有哪些快照,不能证明它们都可删除。若启用了 qgroup,可再运行 sudo btrfs qgroup show "$MNT" 查看各子卷的 referenced/exclusive 口径;命令提示 quota 未启用时直接记录这一事实,不要仅为排查临时开启 quota。

不要依据 du 差值直接删除快照或执行全盘 balance;两者都会改写存储状态,且 balance 在空间紧张时可能进一步增加压力。应先确认快照归属和保留策略,再在维护流程中只处理已确认的对象。变更后重复执行 df -hTbtrfs filesystem usage -T 和应用健康检查;只有目标分配指标下降且服务读写正常,才算完成闭环。

补充第 4 节中“挂载点遮住底层文件”的具体只读核查方式。前提是 util-linux 2.36+、具有 root 权限,并且 TARGET 的父目录与被遮蔽目录位于待核查文件系统。先确认目标确实是挂载点:

TARGET=/var/lib/example
mountpoint -q "$TARGET" || { echo 'TARGET is not a mountpoint' >&2; exit 1; }
findmnt -T "$TARGET" -o TARGET,SOURCE,FSTYPE,OPTIONS
findmnt -T "$(dirname -- "$TARGET")" -o TARGET,SOURCE,FSTYPE,OPTIONS

随后进入独立挂载命名空间,做一次非递归 bind mount。这样不会把 TARGET 上的子挂载复制到检查路径,能够看到父文件系统中被遮住的目录内容:

sudo unshare --mount --propagation private bash
TARGET=/var/lib/example
PARENT=$(dirname -- "$TARGET")
NAME=$(basename -- "$TARGET")
mkdir -p /mnt/underlay-check
mount --bind "$PARENT" /mnt/underlay-check
findmnt /mnt/underlay-check
du -xhd1 "/mnt/underlay-check/$NAME" 2>/dev/null | sort -h
umount /mnt/underlay-check
rmdir /mnt/underlay-check
exit

这里必须使用 --bind,不要改成 --rbind,后者会复制子挂载,重新遮住待检查目录。应用进程仍通过原路径访问现有挂载;检查期间只读取临时视图,不应移动或删除其中的文件。若确认存在历史数据,应先核对文件归属和回滚方案,再安排维护窗口处理。退出命名空间后,用 findmnt "$TARGET" 确认原挂载未变;实际处理完成后,再以同一文件系统口径复查 df -hT 与应用健康状态。