Linux磁盘告警排查指南:df/du不一致、inode耗尽等7大深坑
发布时间:2026/9/16 4:38:45 作者:尧图编辑部 阅读量:1,286

磁盘告警这种事最折磨人的不是告警本身而是你打开终端准备解决问题时发现df -h和du -sh给你的答案完全对不上明明df显示分区已经用了90%可你用du一层层翻目录怎么都加不出那么多空间或者你rm掉几个大文件之后df的Used数字纹丝不动像见了鬼。更隐蔽的是inode耗尽df -h还宽裕得很可服务器就是写不进任何新文件创建个空文件都报No space left on device。我前前后后处理过的磁盘告警没有二十次也有十五次踩过的坑基本能凑成一张磁盘排查的九九八十一难清单。这篇文章我把其中最典型的7个深坑按排查顺序拆开讲每个坑是什么原理、怎么判断、怎么救、怎么防止下次再犯。适合所有跟Linux服务器打交道的人不管是刚入门被du和df绕晕的新手还是被线上告警折腾过几回的运维老手都能在这里找到直接抄的答案。1. 先分清盘是谁满的du统计文件和df统计文件系统不是一个维度第一次遇到du和df对不上时我的第一反应是服务器数据有问题甚至怀疑有人偷偷存了东西。后来把底层机制搞明白才意识到这俩命令本质上统计的是两个不同的东西数字对不上才是常态能严丝合缝对上反而少见。1.1 一个真实场景du算出来8Gdf却显示用了15G有一次凌晨收到告警根分区用了95%。我登录上去先执行du -sh --max-depth1 /把根下面每个一级目录都过了一遍加起来大概8G。可df -h /显示的Used是15G整整差了7G。当时我以为是du没权限看不到某些目录加上sudo重新跑了一遍还是8G。这就是第一个深坑把du和df当成同一个东西。这俩命令的统计口径完全不一样。1.2 du的统计口径只看目录树里看得见的文件du的全称是disk usage它的工作方式是沿着目录树走挨个stat能看到的所有文件把每个文件实际占用的数据块st_blocks累加起来。这里有几个天然盲区已删除但还有进程打开的文件目录树里已经看不到它了du自然统计不到。文件系统的元数据比如inode表、目录项本身占用的块du也不管。文件系统预留块、日志journal占用的空间du同样不算。挂载点下面的其他文件系统如果没用-x参数du会傻乎乎地跨进去一起算。1.3 df的统计口径直接读文件系统超级块df全称disk free它不遍历目录而是直接读取文件系统的超级块信息拿到总块数、空闲块数用总块数 - 空闲块数算出Used。它统计的是整个文件系统层面的块分配情况上面说的元数据、预留块、日志、已删除但占用中的文件全部算进Used里。所以正常情况下df的Used应该大于等于du的结果。理解了这个区别之后回到我刚才那个场景du算出8Gdf显示15G中间差的7G基本就是三块——已删除但被进程占用的文件、ext4默认的5%预留块、元数据和日志。后面我会逐个展开怎么查。对比项dudf统计方式遍历目录树读取文件系统超级块是否包含已删除但被占用文件否是是否包含元数据/inode表否是是否包含预留块否是跨挂载点时表现会跨入子挂载点按文件系统各自独立记住一句话du回答的是哪些文件占了多少空间df回答的是这个文件系统还剩多少空间。排查磁盘告警时第一件事不是纠结谁对谁错而是先用df -h定位哪个分区满了再用du -x -h --max-depth1 /逐层定位占用大户。-x这个参数一定要加它的意思是跨过挂载点只统计当前文件系统否则你会把别的分区的数据也算进来。2. 已删除文件不释放rm之后Used纹丝不动的元凶这是磁盘告警里最经典、也最容易让人抓狂的场景你明明rm掉了一个几十G的文件df -h一看Used纹丝不动Free还是那么点。很多人这时候会反复确认自己删错没有甚至怀疑磁盘坏了。其实文件已经删了只是它还被某个进程含在嘴里。2.1 原理rm删的是目录项进程手里的文件描述符还攥着inodeLinux的文件系统里一个文件由目录项dentry和inode组成。rm实际上做的是删除目录项把文件的链接数减到0。但是如果某个进程已经用open()打开过这个文件它的文件描述符还指向这个inode这个inode就还活着它占用的数据块也继续被标记为已分配不会归还给文件系统。只有所有打开这个文件的描述符都关闭了inode才会真正被回收空间才释放。最常见的触发场景是日志类应用。很多Java进程、Python后台服务启动的时候打开了一个日志文件之后就再也不重新打开。logrotate按天把app.log重命名成app.log.1再新建一个app.log但进程根本不知道这回事还一直往旧的那个已经被删除的inode里写数据。于是你看到的磁盘空间越来越少实际是被一个看不见的透明文件吃掉了。2.2 定位已删除文件的完整排查链路排查这类问题别瞎猜直接上lsof一条命令锁定所有链接数为0但还占着空间的文件# 列出所有已删除但仍有进程打开的文件 lsof -nP L1 # 只看体积较大的避免被一堆小文件刷屏 lsof -nP L1 2/dev/null | awk $7 1048576 {print $1, $2, $4, $7, $10, $NF}L1的意思是只显示link count小于1的文件也就是已经被删除的文件。输出里能看到进程名、PID、文件描述符、大小、文件路径通常会带(deleted)标记。如果lsof没装用ls -l /proc/*/fd/手工翻也能找# 找出所有指向已删除文件的文件描述符 ls -l /proc/*/fd/* 2/dev/null | grep deleted找到了是哪个进程占用的下一步就看情况处理。如果这个进程可以重启直接重启最干净systemctl restart app-name重启之后再看df -h空间会立刻回来。注意即使文件被删了ls -l /proc/PID/fd/3这类命令依然能看到它指向一个inode也能看到它的大小这个大小就是它实时占用的空间。2.3 不想重启进程时的救急手段直接清空文件描述符有些核心服务不能随便重启比如正在处理业务的Java应用、数据库。这时候有一个很实用的救急技巧通过/proc/PID/fd/N直接清空那个已删除的文件。先找到进程的PID和对应的文件描述符编号然后执行# 假设PID是12345fd是3 : /proc/12345/fd/3这个操作等效于在进程内部把那个日志文件的内容清空空间立刻释放进程还能继续往里面写。但有个深坑我必须提醒如果这个描述符不是以追加模式O_APPEND打开的清空之后文件偏移量还在原来的位置进程下一次写数据会在偏移量处写入文件中间会出现一个空洞一段时间后空间可能又涨回来甚至产生稀疏文件的假象。所以这个技巧只适合救急根治还是要调整应用的日志策略让它支持重开日志或者干脆重启。2.4 从根上防logrotate的copytruncate方案如果确认是日志rotate导致的问题可以在/etc/logrotate.d/下面的配置文件里把默认的create改成copytruncate/var/log/app/app.log { daily rotate 7 copytruncate compress missingok notifempty }copytruncate的做法是先把原文件复制一份然后原地把原文件截断为0。这样进程手里的文件描述符始终指向同一个inode日志不会写丢也不会产生已删除文件。代价是复制和截断之间存在一个极短的时间窗口可能丢失几行日志。对于日志要求不极端严格的业务这个代价完全可接受。另一条路是给应用配logrotate后发信号让它重开日志比如Nginx的/etc/logrotate.d/nginx配置里有postrotate段执行kill -USR1 $(cat /var/run/nginx.pid)。关键是你要了解你管理的应用支不支持信号重开日志不支持的话就用copytruncate这个决定能省掉一晚上的折腾。3. inode耗尽df -h显示绿色服务器却写不进文件第三个深坑比前两个更隐蔽。磁盘空间明明还多得很df -h一看才用了60%但你去/tmp下创建个临时文件系统直接报No space left on device。这时候很多人第一反应是磁盘满了反复确认空间还有一大半完全摸不着头脑。3.1 inode是什么为什么会耗尽inode是文件系统里记录文件元信息的数据结构文件名、权限、属主、时间戳、数据块指针都存在里面。每个文件或目录都要消耗一个inode。df -h看的是数据块的使用情况df -i看的才是inode的使用情况df -i输出里Inodes列显示总共有多少inodeIUsed列显示用了多少。当IUse%达到100%时文件系统就无法创建任何新文件和新目录了哪怕数据块还剩一大半。什么情况下容易耗尽inode总结下来就一句话小文件爆炸。常见元凶有/var/spool/postfix/maildrop邮件投递失败后堆积的零碎文件每封失败邮件可能就几百字节但占一个inode。/tmp各种session文件、缓存文件堆积。Docker的overlay2目录容器镜像分层、容器可写层会产生大量小文件容器频繁启停后尤其严重。npm、composer、pip这类包管理器的缓存目录。/var/log/journalsystemd日志按索引拆成很多小文件。3.2 三步找出inode大户第一步用df -i确认哪个分区inode快满了。第二步在这个分区上用find或du --inodes找出文件数量最多的目录# GNU coreutils的du支持 --inodes 参数按inode数排序 du --inodes -x -h --max-depth2 / 2/dev/null | sort -hr | head -20如果你的du版本不支持--inodes用find加管道组合# 找出 / 下每个一级目录的文件数量 for d in /[a-z]*; do echo $(find $d -xdev -type f 2/dev/null | wc -l) $d done | sort -rn | head -20第三步锁定具体目录后用ls -U快速列文件、用find按时间批量清理。清理inode占用时有一个小技巧先用find /目标目录 -type f -mtime 30 -delete删掉超过30天的文件优先释放inode而不是盲目全删避免误删还在用的文件。3.3 inode耗尽后的紧急处理和前端预防紧急情况下要立刻腾出inode优先清理这些安全区# 清空postfix未投递邮件高风险操作确认业务不需要再执行 find /var/spool/postfix/maildrop -type f -delete # 清理journal日志并限制日志体积 journalctl --vacuum-time3d journalctl --vacuum-size100M # 清理Docker无用数据 docker system prune -f docker system df从根上预防分两层。第一层是文件系统创建时就规划好inode数量。ext4系列的inode数量在mkfs时就定死了默认每16384字节的数据块分配一个inode-i 16384。如果你知道这台机器将来主要跑大量小文件业务创建文件系统时可以调整# 每8192字节分配一个inodeinode总数翻倍 mkfs.ext4 -i 8192 /dev/sdb1注意mkfs之后inode数量不能再调整除非备份数据重新格式化所以这一步必须在格式化时做对。XFS没有这个问题它支持动态分配inode这也是我新项目数据盘优先选XFS的原因之一。第二层是监控。df -h的告警要加df -i的告警更要加。Prometheus的node_filesystem_files和node_filesystem_files_free指标就是干这个的阈值设在85%报警90%就要立刻处理真等到100%服务基本已经出故障了。4. 挂载点重叠、保留块、快照三个制造假满的空间黑洞排查磁盘问题时有几种情况会让你的数字算账怎么都对不上而且方向刚好相反一种是du比df大看着像空间被吃光了其实是统计范围重叠另一种是df永远比实际文件多占一部分保留了块。4.1 du比df还大挂载点重叠引发的统计幻觉如果你执行du -sh /不带-x而你的/var、/home、/data都是独立分区那么du会沿着目录树一路走下去把子挂载点里的文件系统全部加进/目录的统计里。结果就是du /显示的数可能比df /大得多。这本身不是空间真的被占用了而是统计范围重叠。有人拿着这个虚高的数字去清理根分区删了半天df /也没见空间回来原因是空间占用在别的挂载点上方向完全搞错了。排查方法是先看清挂载结构# 列出所有挂载点 findmnt -R / # 或者 mount -l | grep -E ^/dev/ # 用-x参数让du只在当前文件系统内统计 du -x -h --max-depth1 / 2/dev/null | sort -hr | head -20再看某个具体目录属于哪个文件系统可以用stat -fstat -f /var/log输出里的Filesystem类型和Files字段能告诉你它和根分区是不是同一个文件系统。排查磁盘问题时尽量养成习惯du永远带-x或者针对目标分区跑du -x --max-depth1 挂载点避免被跨挂载点干扰。4.2 df永远虚高5%ext4保留块这是另一个容易让人误判的坑。ext2/3/4文件系统默认会预留5%的数据块给root用户专用目的是在磁盘被用户塞满时root还能登录进去清理救援、系统进程还能写关键文件。问题是这5%在df里是被算进Used的。换句话说一块100G的ext4数据盘刚格式化挂载上去啥文件都还没放df -h显示就已经用了5%5G。du一算却是0很多人以为系统偷偷写了什么。其实只是保留块。查看保留块数量和比例tune2fs -l /dev/sdb1 | grep -E Block count|Reserved block count如果确认这台机器不需要预留这么多空间比如纯数据盘不跑系统可以把保留比例调低甚至归零立刻释放出可用空间# 将保留块比例调整为0% tune2fs -m 0 /dev/sdb1 # 或者指定具体块数量 tune2fs -r 0 /dev/sdb1有一点要提醒根分区/所在分区我不建议把保留块调成0。等哪天磁盘被写满系统关键进程和root登录会出大问题。数据盘一般问题不大最多少了写满时的最后一道保险。XFS和Btrfs没有这个5%的默认机制所以用XFS的话基本不会有这块困惑。4.3 快照和日志机制空间被文件系统内部动作吞掉最后这一类深坑跟文件系统的高级特性有关。LVM快照。用LVM管理的分区做快照后快照卷采用写时复制CoW机制源卷里任何数据块发生变化旧数据会被复制到快照卷里。如果业务写得很频繁快照卷会迅速膨胀。这时候你在源分区上df -h看到空间是够的但真实物理空间物理卷VG里的剩余空间可能已经见底。最危险的是快照卷100%满这时候源分区所有写操作都会报I/O错误。排查方法# 查看LV和快照状态 lvs vgs lvdisplay | grep -E LV Name|Allocated|Snapshot发现无用的快照直接删lvremove /dev/vgname/snapnameBtrfs子卷快照同理。Btrfs的快照对用户是透明的文件从原目录删除但如果快照里还引用了这个文件的数据块空间就不会释放。btrfs subvolume list /查看所有子卷btrfs filesystem df /看实际空间分配清理时用btrfs subvolume delete删掉旧快照。系统日志和进度文件也会造成小规模看不见的空间。/var/log/journal如果没限制大小日志索引文件会一直膨胀。journalctl --disk-usage查看用journalctl --vacuum-size限制。另外补充一个反直觉的误区有人以为sync; echo 3 /proc/sys/vm/drop_caches可以释放磁盘空间还以为删缓存能腾出容量。实际上page cache是内存缓存释放它只能释放内存df的Used不会变别在这个方向上浪费时间。5. 一分钟判断空间去哪了手写一个应急定位脚本上面讲的都是原理和单点命令实际告警时人会很慌。我建议把这套排查逻辑固定成一个脚本遇到磁盘告警直接跑按权重从大到小把嫌疑对象全列出来基本能在5分钟内锁定元凶。5.1 核心排查清单与脚本以下是我实际在用的一个简化版应急脚本思路你可以按自己环境改#!/bin/bash # 磁盘告警应急定位脚本需root echo 1. df -h 各分区使用情况 df -h echo echo 2. df -i inode使用情况 df -i echo echo 3. 根分区目录占用排行不跨挂载点 du -x -h --max-depth2 / 2/dev/null | sort -hr | head -20 echo echo 4. 大于500M的大文件 find / -xdev -type f -size 500M -exec ls -lh {} \; 2/dev/null | head -20 echo echo 5. 已删除但仍被进程占用的文件 lsof -nP L1 2/dev/null | awk $7 1048576 {print $1, $2, $7, $10, $11} echo echo 6. LVM快照检查 lvs 2/dev/null echo echo 7. journal日志占用 journalctl --disk-usage 2/dev/null5.2 脚本执行后的判断路径跑完脚本后按下面的优先级去判断首先看第2步的df -i如果IUse%超过95%直接跳到inode场景处理。其次看第5步如果lsof L1列出了大文件说明有已删除文件被占用这是收益最大、见效最快的一步处理完通常立刻能释放几个G甚至几十个G。再看第3步和第4步锁定是哪个目录在涨清日志还是清缓存。如果前五步都没异常查LVM快照、查是否误挂载了别的分区、查是不是别的机器NFS挂载导致写入一步步缩小范围。这个脚本最大的价值不是省去你敲命令的时间而是保证处理流程不会被当时的紧张情绪打乱。我亲眼见过同事在磁盘告警时不加-x跑du被跨挂载点的虚高数字误导花了一个小时清错方向最后才发现是删掉的日志文件被Java进程死死攥住。6. 告警恢复后的三步收尾别让同一个坑绊倒你两次把空间清理出来、服务恢复正常之后很多人就关掉终端下班了。但我建议多花十分钟做三件事这套收尾动作帮我避开了无数二次告警。第一确认告警的真凶到底是谁。把这次定位到的根因记录到值班文档里是日志rotate的问题、是inode小文件爆炸、还是快照膨胀写清楚关键词和排查命令。下一次再告警时直接按历史记录优先查效率翻倍。第二落地监控。df -h和df -i的告警阈值都要有建议数据分区80%告警、90%紧急inode分区85%告警。同时加上journal日志的监控很多临时目录的膨胀其实早有前兆。用Prometheus的node_filesystem_avail_bytes和node_filesystem_files_avail指标就能实现几分钟的事。第三修复会反复出问题的配置。日志类应用全量检查logrotate的create/copytruncate策略临时目录加定时清理任务Docker宿主机定期执行docker system pruneLVM快照设置生命周期管理别让快照无限增长。我个人的习惯是每季度主动抽查一次测试机的df -i和快照情况用低成本演练保证真出问题时手上不慌。磁盘告警本质上是迟早会来的事区别只是你有没有提前准备好一套能快速定位的流程。把上面这7个坑每一条都验证一遍下一次告警对你来说就不是惊吓而是按清单打勾的例行公事。