云计算百科
云计算领域专业知识百科平台

服务器磁盘空间怎么看?df 和 du 对不上时的排查路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}

最让人困惑的一种服务器磁盘空间告警,不是"满了",而是满了却找不到是谁占的。

df 说这个分区用了 95%,你用 du 把目录挨个加起来,却发现所有文件加起来才 40%。剩下的空间去哪了?

这种情况我遇到过不止一次。它通常不是误报,而是你漏看了一个维度——这两个命令统计的根本不是同一件事。

下面按真实排查顺序走一遍,从"怎么判断是哪一类满"到"删了文件为什么空间不释放"。

一、先分清是哪一类"满"

看到 No space left on device 或磁盘告警,第一件事不是去找大文件,而是判断满的是哪种资源。

日常在 linux 查看服务器磁盘空间,多数人只敲一个 df -h 就去找大文件了,结果经常白忙一场。正确的做法是至少同时看两个数:

df -hT
df -i

df -hT 看容量和文件系统类型,df -i 看 inode。

如果 Use% 接近 100%:这是真的空间不够,继续往下看第二节。

如果 Use% 还行,但 IUse% 到了 100%:这是 inode 耗尽。每个文件(含目录、硬链接)都要占一个 inode,文件数把 inode 用光了,哪怕还剩几十 GB 空间,创建新文件同样会报 No space left on device。

这两者报错完全一样,解法完全不同。inode 耗尽的常见来源是几百万个小文件:PHP session、邮件队列、CI 构建产物、某种"每次请求写一个文件"的应用 bug。

定位 inode 大户:

sudo find / -xdev -printf '%h\\n' 2>/dev/null | sort | uniq -c | sort -nr | head -20

-xdev 很关键——不让 find 跨文件系统跑,否则会扫到 /proc、/sys 这些虚拟文件系统,结果全是噪音。

还有一类容易漏的:/dev/shm、/tmp 这类 tmpfs 是内存文件系统,它们满了报的就是内存不够,不是磁盘不够。用 df -h /tmp 单独看一下就能区分。

二、找占用:du 的正确用法

确认是容量问题后,就该查清楚服务器磁盘空间具体被哪些目录吃掉了。查看linux服务器磁盘剩余的分布,用 du 定位。注意这两个参数:

du -xh / –max-depth=1 2>/dev/null | sort -rh | head -20

  • -x:不跨文件系统。/ 下面往往挂着别的分区甚至网络存储,不加 -x 会把所有挂载点的空间重复算一遍,出来的数字完全对不上。
  • –max-depth:控制层级。-x 配合 –max-depth=1 先定位到一级目录,然后对着最大的那个目录再往下钻,比一次性 du -sh * 铺满屏幕有效率。

想交互式的,装 ncdu:

ncdu -x /

它会把目录按大小排好,可以直接在界面里进目录、删文件。生产环境排查时这个比反复敲 du 快得多。

三、df 和 du 差很多:三个真实原因

这是本文标题想解决的问题。先说结论:这俩命令统计的口径本来就不一样,差值不可能严格等于零。真正需要处理的,是差值大到几十 GB 的情况。

原因一:文件被删了,但进程还拿着它(最常见)

Linux 下删除文件,本质是在目录里去掉一个链接。只要还有进程持有这个文件的句柄,inode 和数据块就不会真正释放。

于是:

  • du 遍历目录树,看不到这个已经从目录里消失的文件 → 不统计
  • df 读文件系统超级块,那块空间确实还被占着 → 统计

差值就是这么来的。

查证很简单:

lsof | grep deleted
# 或
lsof +L1

lsof +L1 列出链接数小于 1 的文件,也就是"已从目录删除但仍被打开"的那些。输出里能看到进程名、PID、占用大小。如果这里有大文件,df 和 du 的差就有了完整解释。

关于 lsof 的完整字段与选项,可查阅 lsof(8) 手册页。

原因二:挂载点把原来的文件盖住了

做 linux服务器磁盘挂载 时有个容易忽略的顺序问题:如果先往 /data 写了 50GB 数据,之后又把一块盘挂载到 /data,原来的 50GB 就被"盖住"了——du 遍历不到(被新挂载遮蔽),但空间实实在在被占着。

区分方法:临时 umount 掉该挂载点,再看原目录的大小。生产环境做这个操作前务必确认没有服务正在读写。

原因三:口径差异本身

这一条不是故障,是必须知道的常识:

  • df 统计的是文件系统的块使用情况,包含元数据和保留块
  • du 是逐个文件累加,对硬链接(只算一次)、稀疏文件(按实际占用而非表观大小)、无权限读取的目录(会被跳过)表现都不同

所以几 GB 的差异是正常的。只有当差值达到几十 GB,并且能在 lsof +L1 里找到对应的 deleted 文件,证据链才算完整。不要看到几 GB 差就急着重启服务。

四、空间不释放:用 truncate,不要用 rm

这是实操里最常犯的错。

日志涨到 30GB,很多人第一反应是:

rm /var/log/myapp/app.log # 错误示范

删完之后 df 一点没变——因为写日志的进程还持有这个文件的句柄,空间不会释放,而且你还失去了对这个文件的控制(没法再截断它了)。

正确做法是清空而不是删除,inode 保持不变,进程继续往里写,空间立刻释放:

# 方式一
truncate -s 0 /var/log/myapp/app.log

# 方式二(效果相同)
> /var/log/myapp/app.log

如果文件已经被 rm 掉了,还能补救——通过 /proc 找到那个还在被持有的文件描述符,直接截断它:

# PID 和 FD 从 lsof +L1 的输出里取
truncate -s 0 /proc/$PID/fd/$FD

这样不需要重启。只有当拿不到 PID、或者想彻底恢复干净状态时,才去 systemctl restart 对应服务——注意是重启服务,不是重启整机。

顺带说预防:配好 logrotate。copytruncate 是一个选项,更规范的做法是让程序在收到信号后重新打开日志文件(reload hook)。无论哪种,核心是不要让"删除"成为清理手段。

五、三个大户:journal、Docker、包缓存

清理要按风险从低到高来。先动这些确定无害的:

systemd journal

journalctl –disk-usage # 先看占了多少
journalctl –vacuum-size=500M # 保留 500MB
journalctl –vacuum-time=7d # 或保留最近 7 天

治本的做法是在 journald.conf 里设 SystemMaxUse=,否则它会周期性涨回来。

Docker

docker system df # 看镜像/容器/卷/构建缓存各占多少
docker system prune -f # 清理停止的容器、无用网络、悬空镜像、构建缓存
docker image prune -a -f # 清理未被任何容器使用的镜像

⚠️ docker system prune –volumes 会删数据,不只是删层。如果有匿名卷装着数据库文件,执行前必须确认。生产环境我只在明确知道卷可弃时才加这个参数。

容器日志也是隐形大户,可以在 Docker 配置里限死:

{
"log-driver": "json-file",
"log-opts": { "max-size": "50m", "max-file": "3" }
}

包管理缓存

apt clean # Debian/Ubuntu
dnf clean all # RHEL 系

六、还有空间却写不进去:ext4 的保留块

有种情况很迷惑:df 显示还剩 5%,但应用就是报 No space left on device。

这是 ext4 默认保留了 5% 的块给 root,目的是磁盘写满时管理员还能登录清理。非 root 进程在用到 95% 时就写不进去了——明明有空间,但那部分不是给你的。

查看保留量:

sudo tune2fs -l /dev/sda1 | grep -i 'reserved block'

数据盘上可以调低:

sudo tune2fs -m 1 /dev/sdb1

⚠️ 三条限制必须知道:

  • 只对 ext4 生效。XFS 没有这个机制,别对着 XFS 分区执行。不确定类型就用 df -hT 或 findmnt -n -o SOURCE /data 确认。
  • 根分区不要盲目调低。那 5% 是系统出事时你还能登进去的保障。
  • 回滚要小心。如果调出来的空间已经被业务写满,再把保留比例调回去,普通用户的可用空间会进一步下降。
  • 七、一个可以直接照做的清理顺序

    顺序动作风险
    1 df -hT + df -i 判定是容量还是 inode 无
    2 du -xh –max-depth 定位大目录 无
    3 lsof +L1 查幽灵文件 无
    4 journalctl –vacuum-size 低
    5 truncate 清空大日志(不要 rm) 低
    6 apt/dnf clean、清理旧内核 低
    7 docker system prune -f 中(别带 –volumes)
    8 动应用数据 高,必须有备份或快照
    9 tune2fs -m 调保留块 中(仅限 ext4 数据盘)

    第 8 步之前,先问一句:这些数据删了能不能恢复?生产环境没有快照就删应用数据,是把一次磁盘告警变成一次数据事故。

    八、什么时候该扩容,而不是清理

    如果做完上面这些,空间在很短时间内又涨回去,说明这不是一次清理能解决的问题,而是容量规划问题。

    判断依据很简单:把清理掉的量和增长速率做个除法。清了 30GB、一周后又满了,那扩容比反复清理划算——运维的时间成本也是成本。

    两种扩容方向:

    • 纵向扩:直接把现有云盘扩到更大,多数云平台支持在线扩容(不停机扩盘,之后再扩分区与文件系统)。具体步骤和限制参见云服务器在线扩容云盘文档。
    • 横向拆:把 /var、容器数据、数据库拆到独立的数据盘上。好处是某一类数据暴涨不会连带把根分区塞满导致系统登不进去——前面第 1 步里"根分区满了"是最难处理的一种,因为连排查工具都可能写不了临时文件。

    容量告警阈值也建议提前设成 75% / 85% 两级,而不是等到 98%。等 98% 才告警,意味着你没有时间从容排查,只能在压力下做决策——而这恰恰是最容易误删的时刻。


    关于本文命令的说明:文中的命令与参数基于主流 Linux 发行版的常见行为,du/lsof/journalctl 等工具在不同版本间选项基本一致,但涉及文件系统调整的操作(tune2fs -m、扩容后扩分区)会直接改动磁盘元数据,执行前请确认文件系统类型、设备路径,并在有快照的前提下进行。保留块比例、journal 上限这类数值给的是常用参考,具体应按你的磁盘规模和业务特性调整。

    回到开头那个问题:服务器磁盘空间告警的时候,真正要花时间的不是删文件,而是判断满的是容量、inode 还是句柄。判断对了,后面就是几条命令的事;判断错了,删再多文件也不动地方。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 服务器磁盘空间怎么看?df 和 du 对不上时的排查路径
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!