拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Linux磁盘空间告警排查:du与df对不上、inode耗尽及幽灵文件的7个深坑

1. 告警弹出来之后先花三分钟回答三个问题晚上十一点监控弹出一条告警/data 使用率 92%。登录服务器df -h确认了数字92% 没得跑。可紧接着du -sh /data/*一算整个目录加起来才占 40% 出头。那一刻我心里想的不是磁盘满了而是哪里对不上。磁盘空间问题最怕的不是数字高而是数字互相矛盾。数据对不上说明有人在文件系统层面藏了一笔私账。我排查磁盘告警有一个固定习惯先回答三个问题。第一告警的是哪个挂载点、什么文件系统第二是容量接近上限还是 inode 接近上限第三df和du的口径是否一致。这三个问题答完至少能筛掉一半的假故障。接下来要讲的 7 个深坑就藏在这些问题的答案背后按出现频率和对业务的影响我按下面的顺序逐个拆du 和 df 统计口径不一致导致误判谁占了空间已删文件不释放进程还攥着旧句柄空间怎么 rm 都回不来inode 耗尽磁盘明明有容量就是写不进文件root 保留块df 还剩 5%普通用户却一写就报错日志 rotate 了进程没重开文件空间纹丝不动嵌套挂载和稀疏文件带来的双重统计幻觉容器 overlay 与镜像层堆积宿主机有账、容器里没账1.1 是容量告警还是inode告警先说一个很反直觉的事实磁盘满不一定是容量满。Linux 文件系统里每个文件或目录都要占用一个 inode索引节点用来存权限、属主、时间戳和块指针。用档案室来类比inode 就是每份文件的档案卡档案卡本身也要占档案柜的位置。如果 inode 耗尽哪怕磁盘还剩几十 GB任何用户包括 root都会收到No space left on device。后面我会专门写 inode 的坑这里先记住一句df -h看的是容量df -i看的是 inode两个都要看。告警只盯容量、不看 inode等于一条腿走路。1.2 一屏命令把现场固定下来接到告警后我建议先跑下面这组命令把现场固定下来。这些命令不会改变任何状态纯粹是取证df -hT df -i mount | grep -E ^/dev|overlay du -xsh /* 2/dev/null | sort -rh | head -20 lsof L1 2/dev/null | head -20df -hT会带出文件系统类型ext4、xfs、overlay 等这决定了后面用哪套工具和处理思路df -i直接看 inode 水位du -xsh /*用-x限定在同一文件系统内避免 du 跨挂载点串门lsof L1则是为已删文件不释放准备的这一步能提前发现幽灵文件。不要一上来就rm -rf去找大文件很多事故就是在这种慌张状态下误删的。先把数据固定住再逐层排查信息完整了答案往往自己就浮出来了。2. du和df对不上两个命令各算各的账du 和 df 对不上几乎是磁盘告警里最经典的现象也是整个排查的起点。很多人第一反应是某个命令算错了其实两个都没错只是记账方式不同。一个像在房间里数家具一个像在看物业的整楼台账天然就对不上。2.1 底层逻辑一个是数家具一个是看物业台账打个比方du 像你拿着尺子走遍每个房间一件一件量家具占了多少平方米df 则是物业的系统台账直接显示这个楼盘总共登记了多少面积、还剩多少可售哪怕某个房间里堆着等垃圾车运走的旧家具台账也算它占着。具体到 Linuxdu 从你给定的路径出发递归遍历目录树对每个文件调用 stat 拿到 st_blocks再乘以 512 字节把所有文件、目录实际占用的块数加起来。它只认目录树里能看到的东西。df 则直接调用 statfs 读取文件系统的超级块统计的是整个文件系统所有已分配的块包括 inode 表、日志journal、保留块以及已经从目录里删掉、但还有进程持有句柄的文件。所以两条铁律先记住du永远统计不到已删除但仍被进程占用的文件块。df统计的是文件系统级别的真实块分配包括元数据开销。这两条可以解释绝大多数du 比 df 小很多的场景。2.2 对不上的四种典型成因我在实战里总结过du 和 df 打架基本跑不出下面四种情况现象最常见原因下一步命令du 远小于 df已删文件被进程持有lsof L1du 大于 df同挂载点内du 默认跨挂载点计数把别的盘也算进来了du -xsh /df 用了很多du 找不出大头文件系统元数据、日志、保留块占空间df -i、tune2fs -ldu 统计正常df 缓慢增长稀疏文件被逐渐写实或活跃容器层在增长lsof、docker system df这里特别提醒du -sh /默认会跨挂载点如果你在根目录上跑 du而 /data、/home 是独立分区du 会把它们全部算进来。此时拿 du 的结果去对比df /数字当然对不上。所以分析根分区时用du -xsh /分析某个目录时用du -xsh /目录-x就是不许越过文件系统边界。2.3 对不上时该信谁如果只是排查谁占了大头以 du 为主但要用-x限定如果目的是回答这个文件系统还能不能扛住写入以 df 为准因为它才反映真实的块分配。遇到 du 小、df 大优先怀疑幽灵文件直接上lsof L1别去翻目录。反过来du 大、df 小同一文件系统内几乎不太可能基本就是跨挂载点或稀疏文件在捣乱。这里多说一句经验排障时把 df 和 du 的差额当作一个明确信号而不是把差额定性成命令坏了。差额本身就在告诉你文件系统里有用户态看不到的分配。看到差额我一般直接跳过找大目录这个动作直奔 lsof 和挂载表时间能省出一大半。3. 已删文件不释放磁盘里最冤的幽灵租客这是磁盘告警里最让人抓狂的一种你明明rm掉了一个 20GB 的文件ls也看不到了du也数不到了可 df 的可用空间纹丝不动。感觉就像房子已经退租物业台账上却还记着有人住。3.1 内核里的链接计数与打开计数Linux 文件删除的本质不是抹掉数据而是unlink()把目录项和 inode 之间的链接断开inode 的 nlink 减一。只要 nlink 不为 0文件数据就还在。问题在于文件被删除时如果还有进程打开了它这个进程的文件描述符fd仍然指向那个 inode内核会保证数据块在 fd 关闭前一直保留。换句话说磁盘上的块被租给了进程而不是被释放。最常见的是日志服务、数据库、Web 服务器日志被 rotate 或者被手动rm但进程没有重开文件旧文件的 fd 就一直攥着那 20GB。这类问题有个非常形象的英文叫法——ghost files幽灵文件。它既不在目录树里又真实占着磁盘块普通手段根本看不见。3.2 一行 lsof 揪出幽灵文件定位这种问题不要瞎找直接让 lsof 把链接数已经变成 0 但仍被打开的文件列出来lsof L1 -nP 2/dev/null | grep -v COMMANDL1的意思是只列出 nlink 小于 1 的文件。为了快速估算这些幽灵文件一共占了多少空间可以对 size 列求和lsof L1 -nP 2/dev/null | awk NR1{s$7}END{printf %.1f GB\n, s/1024/1024/1024}实际输出里你会看到类似这样的行java 23121 app 8w REG 253,0 21474836480 1048577 /var/log/app/app.log (deleted) nginx 8831 root 5w REG 253,0 1073741824 8388611 /var/log/nginx/access.log (deleted)一眼就能看出是哪个进程、哪个文件、占了多大。如果 lsof 没装也可以用 proc 文件系统硬查find /proc/*/fd -lname * (deleted) 2/dev/null | head ls -l /proc/1234/fd/ 2/dev/null | grep deletedls -l /proc/PID/fd里文件名末尾带(deleted)的就是还占着空间的幽灵。3.3 释放空间的手段与代价拿到进程 PID 和 fd 之后按优先级做三选一。首选优雅重启或重载服务。日志类服务大多支持重开文件比如 nginx 用nginx -s reopenrsyslog 用systemctl restart rsyslog或发 HUP。进程重开后旧 fd 关闭空间立刻回来。次选kill 掉持有 fd 的进程。如果确定这个文件是日志、临时文件且进程可以重启直接kill也干净。但生产环境先评估影响别拿数据库开刀。兜底技巧通过 /proc 截断 fd。对于绝对不能重启的场景可以对这个 fd 本身做截断truncate -s 0 /proc/1234/fd/8这会把 fd 指向的文件大小清成 0块被释放而进程并不会立刻崩溃。但要命的是如果进程继续按原偏移写入文件会长成稀疏文件前面全是空洞。所以这个技巧只适合救急事后一定要找时间重启服务。我用它救过一台跑着核心采集程序的机器当时不敢重启靠这一手把 60GB 日志空间收了回来后续等流量低的时候才重启做到业务无感。4. inode耗尽写不进去但你找不到满的证据第三个大坑叫 inode 耗尽。现象非常魔幻df -h显示还剩 20% 空间可是一写文件就报No space left on device连touch空文件都失败。如果你只盯容量监控这类告警根本不会提前预警。4.1 inode 是什么为什么会耗尽inode 可以理解成每个文件的档案卡上面记录权限、属主、时间戳、以及数据块的索引。ext4 在格式化时就确定了 inode 的总数量默认大约是每 16KB 空间分配一个 inode。以 20GB 的分区为例inode 总数大概在 120 万到 130 万之间。如果上面堆了大量小文件——比如邮件队列、squid 缓存、node_modules、消息队列的积压——文件数量冲到百万级inode 就会先于容量被耗尽。注意inode 耗尽没有 root 保留空间一说root 一样写不进去。很多线上事故明明是大量小文件导致的但大家第一反应总是去删大文件方向就错了。你删掉一个 10GB 的大日志文件可能只释放了 1 个 inode而真正消耗 inode 的是那一百万个 1KB 的小文件删它们才能解决根因。4.2 快速定位 inode 消耗大户先看哪个文件系统满了df -iIUse% 接近 100% 的那一行就是案发现场。接下来找哪个目录的文件数量最多。最朴素也最可靠的方式是递归数文件for d in /var /tmp /home /data; do echo $d: $(find $d -xdev -type f 2/dev/null | wc -l) done更细一点按目录维度统计找出百万小文件的窝点find /data -xdev -type f -printf %h\n 2/dev/null | sort | uniq -c | sort -rn | head -20如果 coreutils 版本较新可以直接用 du 的 inode 模式du --inodes -xsh /data/* 2/dev/null | sort -rh | head -204.3 根治格式化规划与日常清理已经耗尽的情况只能删文件优先清缓存目录、历史消息、过期临时文件。但我想多说一句预防新磁盘格式化时就该规划 inode 密度。比如给一个明确要放大量小文件的目录单独划盘用 mkfs 时调整字节/inode 比mkfs.ext4 -i 8192 /dev/sdb1-i 8192表示平均每 8KB 空间分配一个 inodeinode 总量比默认多一倍。缺点是在 inode 表上多占一些磁盘每多一个 inode 大约 256 字节但对小文件密集场景值得。如果是 XFSinode 是按需动态分配的基本不会遇到inode 耗尽这类问题这也是我推荐小文件多就用 xfs的原因。经验之谈inode 问题最适合用监控提前拦截。磁盘容量监控之外给df -i的 IUse% 也加一条告警线90% 就该介入。现实中很多 inode 耗尽事故都是一夜之间量起来的等手动发现时连rm都要一条条执行半天。提前量就是生命线。5. 还有四个深坑每个都能让人挠头前面三个是主题里的主角排查完它们剩下的四个坑虽然不常上头条但踩中任何一个都够折腾半宿。5.1 root保留块df 还剩 5%普通用户却写不进去ext4 默认保留 5% 的块给 root 专用目的是防止磁盘写满后系统连基础日志、fsck 都无法运行。问题在于df 的 Avail 列展示的是普通用户可分配量已经把保留块扣掉了。所以你可能会看到这样的情况df 显示 Use 95%Avail 为 0普通用户和 Web 服务写文件直接 ENOSPCroot 却能继续写日志还能涨。于是运维很困惑明明 root 能写应用怎么报错。查法很简单tune2fs -l /dev/sdb1 | grep -i reserved block默认是 5%。如果是纯数据盘没有根分区那种必须留后路的需求可以调低tune2fs -m 1 /dev/sdb1 # 保留 1% tune2fs -m 0 /dev/sdb1 # 完全不保留数据盘可选系统盘建议保留 1% 到 3% 就好5% 在现在的大磁盘上浪费太多但千万不要在根分区设成 0真写满了日志和 sshd 都可能受影响。5.2 日志 rotate 了空间却没回来这个坑可以看成幽灵文件的衍生版logrotate 只是做了 rename如果进程没有重新打开日志文件它仍然往旧 inode 里写。你看到目录下已经生成了新日志文件旧日志也改名了但df的空间一点点都没回来因为旧 inode 还被进程攥着。典型场景是 Nginx/Apache 配了 logrotate 但postrotate里忘了发 reopen 信号。正确姿势是在 logrotate 配置里加postrotate [ -f /var/run/nginx.pid ] kill -USR1 $(cat /var/run/nginx.pid) endscriptNginx 收到 USR1 会重开日志句柄。如果你用的服务不支持信号重开比如某些 Java 进程只会打开一次文件就要用copytruncate模式它通过先复制、再 truncate 原文件来保证进程的 fd 始终有效create 0644 app app copytruncatecopytruncate的代价是复制和截断之间可能丢极少量日志高吞吐下不建议但在进程无法重开句柄这个限制下它是唯一安全解。排查这类问题时lsof L1同样一查一个准。5.3 嵌套挂载和稀疏文件两个统计幻觉这两个放一起说因为它们都会让 df 和 du 的数字彻底失真。嵌套挂载比如 /data 是一块独立数据盘/data/mysql 上又叠了一个挂载点。你在外面跑du -sh /datadu 默认会钻进嵌套挂载的目录里把其他文件系统的文件也算进来但df -h /data只统计 /data 这块盘的账。结果就是du 比 df 大得多。反过来根分区上跑du -sh /会把所有子挂载都算进去造成根分区假大。处理就一句话分析用du -x看挂载结构用findmnt -R /data。稀疏文件truncate -s 100G fake.img一秒生成一个100GB的文件但它只占 0 个块。ls 看它 100Gdu 看它 0两个都对因为这文件全是空洞。这种文件在数据库、虚拟机镜像、BT 下载里很常见。真正的坑在于后续操作把稀疏文件原样拷贝通常没问题cp 默认会保留空洞但用 tar 打包时如果不加-S空洞会被全部展开成真实数据几十 G 的备份瞬间把磁盘干满。所以对长期存在的大文件记得用cp --sparsealways或tar -S并且永远别拿 ls 的文件大小当磁盘占用。5.4 容器 overlay宿主机有账容器里没账容器场景是近些年新增大坑。Docker 的 overlay2 文件系统把镜像层做成只读 lowerdir容器写入放到可写的 upperdir。镜像层里的文件即使在容器里删了也只是在 upperdir 加一个 whiteout 标记底层文件仍然留在宿主机上df 照样记账。于是容器内df -h /显示没满容器内du -sh /也找不出大头宿主机/var/lib/docker却持续膨胀。典型的账对不上。处理工具就三个docker system df docker system df -v docker system prune -adocker system df -v能看到每个镜像、容器和构建缓存占多少特别是 RECLAIMABLE 那一列。用完即弃的构建缓存、悬挂镜像、以及长期不清理的旧版本镜像都是宿主机的隐形大嘴。治本的办法是构建镜像时用多阶段构建把编译环境和运行环境分开别把整个 toolchain 留在最终镜像里镜像体积能缩掉一半以上。6. 顺带解决 Windows 侧的同款空间消失如果你是因为搜索引擎摸到这篇文章告警的却是 Windows 的地盘下面的速查也许能救命。Windows 上磁盘空间神秘消失最常来自四个方向休眠文件 hiberfil.sys默认占物理内存的 40% 到 75%。不需要休眠功能就powercfg /h off立刻收回几十 GB。临时文件与系统缓存%TEMP%、C:\Windows\Temp、SoftwareDistribution\Download加上各浏览器缓存用 cleanmgr 或磁盘清理一次性清掉。WebView2 旧版本运行时很多应用会捆绑指定的 Microsoft.WebView2.FixedVersionRuntime 版本更新后旧版本不自动卸载几套运行时叠加起来轻松上 GB。去Program Files (x86)\Microsoft\EdgeWebView\Application或应用目录下查找类似Microsoft.WebView2.FixedVersionRuntime.*的文件夹确认没有程序引用后可以清理。OneDrive 文件按需占位Office 提示内存或磁盘空间不足默认保存到 OneDrive时往往是本地盘真的快满了。除了常规清理检查 OneDrive 设置里是不是把大量云文件下载到了本地必要时释放空间改为仅联机。Windows 侧的逻辑和 Linux 一样先看 Temp、再看休眠和更新缓存、最后排查大体积运行时组件。不要直接删 Program Files 里的同名文件夹尤其 WebView2 的固定版本运行时删错会导致依赖应用无法启动要确认无进程引用再动手。7. 把整套排查固化成一套动作最后分享一下我现在处理磁盘告警的固定套路直接存成脚本告警来了就跑一遍五分钟内能定位八成问题#!/bin/bash echo df -h ; df -h echo df -i ; df -i echo top dirs by size ; du -xsh /* 2/dev/null | sort -rh | head -15 echo deleted but open files ; lsof L1 -nP 2/dev/null | grep -v COMMAND echo docker usage ; docker system df 2/dev/null跑完这份输出对照下面的决策路径基本就有答案df 使用率高、du 找不出大头 →lsof L1查幽灵文件df -i 的 IUse% 高 → 找小文件密集目录清剿df 的 Avail 为 0 但 root 能写 → tune2fs 查保留块du -x 的结果正常但 root 分区 df 偏高 → 检查是否有子挂载被 du 默认计入改用du -x容器宿主机磁盘涨得快 →docker system df -v看镜像和缓存。我干这行十几年踩过最深的坑永远是在一台告警机器上凭直觉删文件。磁盘空间的数字背后是三种不同类型的资源块、inode 和文件句柄。它们各自独立又互相纠缠。遇到告警先固定现场、再交叉验证用 lsof 和 df -i 把视角从容量扩到元数据和句柄大部分疑难杂症都能在十分钟内水落石出。希望这篇文章能让你下次接到告警时不再对着 du 和 df 的差额发呆而是有条理地把每个坑挨个踩平。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门