
磁盘空间告警后的7个深坑du和df对不上、inode耗尽、已删文件不释放怎么办磁盘空间告警这种事每个运维和开发都遇到过。告警邮件弹出来的时候你手头可能正在开会、写代码、或者半夜被电话叫醒。赶紧登上服务器敲一遍df -h看起来空间确实快满了但等你顺着目录一路执行du -h --max-depth1准备找出元凶的时候三层目录都没看完数据却对不上——明明du统计出来的总量远小于df展示的已用空间。这种“怎么算都少了一块”的感觉真的很折磨人。更麻烦的是空间满了之后还不一定马上能定位到问题。inode耗尽、被进程占用的已删文件、日志轮转失效、分区预留空间……这些坑不会同时出现但每次总有一两个会冒出来。我这些年处理过的磁盘告警少说也有几十次踩过的坑基本都齐了。今天就把七个最典型、最容易让人卡壳的问题一次性讲透顺便把每个问题的排查思路和常用命令整理出来希望能帮你下次少走弯路。1. 深坑一du和df对不上到底该信谁du和df是最常用的两个磁盘统计命令但很多人在告警后第一反应就是拿它们互相验证。第一个坑往往就是这么出现的——两个数字相差巨大甚至一个显示空间告急另一个看起来还挺宽敞于是整个人都懵了。1.1 统计维度完全不同df统计的是文件系统的块设备使用情况它直接读取文件系统的元数据统计的是“文件系统视角”的块占用。也就是说一个文件只要分配了块不管它是普通文件、目录、还是已经被删除但依然被进程占用的残留块df都会把它算进去。du则是从目录树的文件项开始遍历逐个文件统计字节数再把目录下的所有文件加起来。它统计的是“文件视角”的实际数据大小。用生活类比一下df等于你在小区门口数车位只要画了线、占了位置就算数du等于你挨家挨户数每一辆车实际长度。如果一个车位里停着一辆货车但只按引擎盖大小收费两边数字自然就对不上。同理文件系统里有大量块被分配但又不属于任何文件时du是数不到的df则会照实统计。1.2 排查思路和实操遇到du和df结果不一致先别急着怀疑工具坏了。按我习惯的顺序来首先确认是不是文件系统不一致导致的统计滞后。如果用的是 ext4 这类日志文件系统系统崩溃或者异常断电后逻辑块组可能处于不一致状态df读取时会参考超级块里的计数器这个数字可能还没来得及恢复准确。可以先执行sync再试一次df。其次重点排查是否存在已经删除但仍然被进程占用的文件。这是最常见的元凶。# 找到被删除但仍有进程打开的文件 lsof | grep -i deleted或者用更高效的方式find /proc/*/fd -lname * (deleted) 2/dev/null每出现一行结果就代表有一个已经被删除但还在占用磁盘块的文件。还有一个容易忽略的点df默认显示的“已用”包含了文件系统元数据inode表、块位图、日志等的占用而du完全不统计这些。如果一个文件系统上有大量小文件inode表本身可能就占掉几百MB甚至几个GB两边的差额会非常明显。所以我的通用判断是df代表真实容量消耗du代表可见文件大小。真正占空间但又看不见的部分才是你排查的重点方向。2. 深坑二inode耗尽明明还有空间却写不进去文件很多刚接触服务器运维的人第一次遇到磁盘满第一反应就是删文件。删完一看df -h可用空间确实还有几十GB但写新文件的时候照样报No space left on device设备上没有空间。这种“明明有空间却什么都写不了”的状态十有八九就是 inode 耗尽了。2.1 inode是什么为什么重要inode 是文件系统里保存文件元数据的数据结构文件名、文件大小、权限、时间戳、数据块位置等信息都记录在 inode 里。每个文件或目录至少占用一个 inode。文件系统在格式化的时候就会按比例分配一部分空间给 inode 表。对比一下当前状态df -h 显示df -i 显示能否写入新文件空间满使用率100%使用率正常不能inode满使用率正常使用率100%不能双满使用率100%使用率100%不能排查 inode 是否耗尽命令很简单df -i看/dev/vda1这类挂载点的 IUse% 那一列如果接近 100%基本就坐实了。2.2 快速定位和清理方案inode 耗尽的本质是有海量小文件堆积。这些文件可能分布在邮件队列、临时目录、缓存目录、或者是某个程序异常写入的日志目录里。我最常用的定位命令是# 统计每个一级目录下的文件数量按数量排序 for dir in /home /var /tmp /usr /opt; do echo $dir find $dir -xdev -type f 2/dev/null | wc -l done更精准的单目录定位方式find / -xdev -type f 2/dev/null | awk -F / {print $2} | sort | uniq -c | sort -rn这个命令按根目录下的二级目录统计文件数量哪类目录的文件特别多一眼就能看出来。定位到具体目录后清理思路分两步如果能确认文件无用直接删除。删除时要记得先确认没有进程占用否则可能踩到下一个坑。如果文件不能删考虑把数据转移到大容量的独立分区或者启用文件系统压缩特性ext4 可以开启 inline_data把小块数据放进 inode 里。这里得特别提醒inode 耗尽之后连删除文件这个操作本身都可能失败。因为删除文件也需要在目录里创建/修改条目如果目录所在的文件系统 inode 已经满了某些操作会受限。遇到这种情况最稳妥的办法是先腾出少量 inode比如删除一个包含大量小文件的临时目录或者清空某个缓存文件夹再继续后续清理。3. 深坑三删了文件空间却没释放罪魁祸首是占用进程这个坑我刚开始排查磁盘问题时也踩过。明明用rm删掉了一个几十GB的大日志文件再看df -h可用空间字节都没变化。那一刻真有点怀疑人生。3.1 删除不等于释放Linux 和 Windows 的文件删除逻辑不同。在 Linux 下rm只是把文件从目录项中移除但如果某个进程仍然持有该文件的文件描述符file descriptor这个文件占用的数据块就不会被真正释放。数据块要等所有引用它的描述符都关闭后才会归还给文件系统。最常见的是 tail、服务进程的重定向输出、或者某个崩溃后遗留的进程仍然持有已删除日志文件或临时文件的句柄。你删掉的只是文件名真正占空间的还是那个隐藏在进程里的僵尸文件。用个通俗的比喻某间办公室的电话号码被注销了但里面的设备还在被一个联系不上的员工占着物理空间腾不出来。3.2 查找并处理占用进程排查命令前面提过了这里再说细一点。# 列出所有删除但仍被占用的文件及对应的进程PID lsof L1这个命令的输出中SIZE字段会显示文件占用的实际字节数PID就是占用进程的进程号。如果文件已经被删除NAME一栏通常会带(deleted)标记。如果没有 lsof可以用 proc 文件系统ls -la /proc/[0-9]*/fd/* 2/dev/null | grep deleted这个命令会列出所有进程打开的、但已经删除的文件描述符。结合ls -lia看 inode 号可以判断具体是哪个文件。找到进程后怎么处理取决于业务性质如果是临时任务产生的进程直接kill进程空间会立刻释放。如果是核心服务不能随便重启可以先确认这个进程是否还有存在的必要。比如有些服务处于空转状态但持有的句柄却不释放这种情况可以咨询业务方确认后重启服务。我常见的一种场景是tail -f某个日志日志轮转后把旧文件删了tail进程还开着旧句柄或者 Java 服务把日志输出重定向到文件而日志文件被 logrotate 移走后Java 进程的旧句柄还占着。这类问题重启进程通常立刻见效。4. 深坑四排查效率太低从告警到定位花了几个钟头其实前面三个坑虽然经典但如果排查命令用得熟练每个都只需几分钟就能定位。真正消耗时间的是排查思路混乱东敲一下命令、西翻一个目录最后探测半天还停留在猜测阶段。4.1 先看df再跑du但别一头扎进全盘扫描我见过不少同行的第一反应是du -sh /*这种全盘扫描。遇到大目录多的服务器这个命令跑个十几分钟都正常磁盘告警本来就紧急时间根本耗不起。正确的顺序应该是# 第一步先确认到底哪块文件系统满了 df -h # 第二步直接进入挂载点根目录按目录深度逐层下钻 cd /var du -h --max-depth1 | sort -hdu的参数里--max-depth1表示只看当前目录下一级子目录的占用sort -h按人类可读的大小排序直接把最大的目录顶到最上面。比起一层一层地ls效率要高一个量级。如果文件系统的挂载点在某个目录下比如/data是单独分出来的分区那/var下统计出来的数据就不会包含/data里的内容。这时候要特别注意du默认不会跨越文件系统边界如果不加-x参数它还是会统计进去加了-x后则会忽略其他文件系统挂载点。要区分来看。4.2 高效定位目录的命令组合下面这组命令我几乎是刻在肌肉记忆里的# 查看当前目录下所有子孙目录一级的大小排名 du -h --max-depth1 /某个挂载点 2/dev/null | sort -hr | head -20 # 只看当前目录下的文件大小排名 ls -lhS | head -20 # 一步到位找单个超过N大小的文件 find /某个挂载点 -xdev -type f -size 1G 2/dev/null -exec ls -lh {} \; | sort -k5 -h最后一条find命令的威力在于不用一层层点进去翻直接筛选出大于1GB甚至10GB的大文件往往几秒钟就能定位到罪魁祸首。为了高效运维顺手给磁盘使用率设置个监控告警阈值也很有必要。比如空间超过80%告警一次超过90%告警一次超过95%直接升级到紧急事件。inode 使用率超过80%同样要告警这样能提前处理而不是等满了再头痛。5. 深坑五日志和临时文件无限膨胀轮转策略形同虚设大文件的来源十次有八次是日志和临时文件。尤其是没人打扫的日志目录一旦积累到GB级别磁盘告警几乎是必然的。但很多人处理完之后过两天又开始膨胀这说明问题的根源不在某个文件而在日志轮转机制上。5.1 常见的日志膨胀场景第一种是应用进程把标准输出写进同一个文件但 logrotate 只轮换了日志应用进程依然持有旧日志的句柄前面那类问题。这种情况轮转后就白白占用了旧文件即使被重命名也无法释放。第二种是应用自己生成了大量调试日志轮转规则没有针对这些文件名做配置。比如某些 Java 中间件会生成gc.log、dump.log它们可能按日期生成新的文件但旧文件没有定期清理。第三种是临时文件目录里堆积了大量会话文件、上传文件、缓存文件比如 PHP 的 session 目录、Java 的临时目录、Nginx 的 proxy_temp 目录。一旦程序异常退出旧的临时文件就成了无主垃圾永远留在磁盘上。5.2 如何设计日志策略一套基本合理的日志轮转配置以 logrotate 为例长这样/var/log/myapp/*.log { daily rotate 14 compress delaycompress missingok notifempty create 644 app app sharedscripts postrotate /bin/kill -HUP $(cat /var/run/myapp.pid) endscript }几个参数都值得解释一下rotate 14保留14份日志按天轮转也就是留有14天的历史。compressdelaycompress压缩上上个周期的日志而不是最新那份这样最新日志还能被程序继续写入。postrotate里的kill -HUP通知进程重新打开日志文件这一步是解决句柄占用不释放的关键。没有这一步轮转后旧文件可能还继续增长。sharedscripts多个日志文件共享一个postrotate脚本减少重复执行。对于应用自己生成的日志比如 Java 程序可以写一个独立的 logback 或 log4j2 配置按文件大小和日期双重切割。线上系统最怕日志文件无规律膨胀所以我的原则是所有会产生日志或临时文件的目录都应该有一条明确的清理规则要么归 logrotate 管要么归应用内部的滚动机制管。另外监控上可以加一条对日志目录的最高文件数和最新文件年龄做告警防止临时文件堆积但没人发现的情况。6. 深坑六分区预留空间和快照悄悄吃掉容量排查磁盘空间时还有一种隐蔽情况df显示文件系统已经使用了 100%可你顺着目录算了半天怎么也找不到对应大小的文件。这种时候十有八九是不可见空间被占用了。6.1 分区预留空间ext 系列文件系统在格式化的时候会默认预留 5% 的块给 root 用户使用。这个预留空间用于防止文件系统碎片化、以及 root 在磁盘剩满时仍能登录系统进行维护。对大多数分区来说5% 不算多但如果分区特别大比如 100TB 的数据盘5% 就是 5TB这个数字相当可观。查询预留空间大小tune2fs -l /dev/vda1 | grep -i reserved如果确认业务盘不需要预留这么多空间可以调小tune2fs -m 1 /dev/vda1这个命令把预留比例从 5% 改成 1%对容量紧张的数据盘来说是立竿见影的。注意不要设置为 0否则一旦写满root 可能都无法登录执行排查。6.2 快照和逻辑卷备份另一个吞空间的隐形大户是 LVM 快照。LVM 快照的实现方式是 CoW写时复制快照创建时几乎不占空间但随着原卷数据被修改快照为了保留旧数据会逐渐占用新空间。如果运维忘记了某个快照的存在等快照存储池满的时候原卷的写入也会被阻塞。查看逻辑卷和快照lvs看LV列凡是从某个卷衍生出来的快照卷名字通常带snap字样而且Data%会持续增长。如果快照已经完成了它的使命比如备份已经做完直接移除lvremove /dev/vgprod/proddata_snap这里要特别小心移除快照是不可逆操作务必确认快照内容不再需要。有时候不是 LVM 快照而是云平台的云盘快照这些快照容量一般不体现在服务器df里但会体现在账单和云监控里。这种我建议直接在云控制台排查一下有没有创建时间久远但一直保留的自动快照策略。7. 深坑七视觉盲区挂在“眼前”的容量盲区有时候磁盘分区本身没满但你明明看到一个文件系统满了怎么排查都找不到大文件。这多半是挂载点层级混乱造成的错觉。7.1 挂载点边界的容量误判比如你执行df -h看到/根分区满了于是到/下面去找。如果此时某个子目录比如/data是一个独立分区那么/data下的文件是不会计入根分区使用率的。如果你执行du -sh /的时候带了-x参数它就会自动跳过其他文件系统挂载点但如果你没有加-x它会把不同文件系统里的文件全统计进去导致数字看起来和df不一致。反过来的误判更常见某个挂载点下面的内容特别多但df显示的是挂载点所属分区的用量你去看挂载点目录时可能只看到本分区的几个文件却忽略了下面还套着其他挂载点。可以用mount或者findmnt查看完整挂载树findmnt -R /这个命令把根分区及所有子挂载点以树状形式列出来一眼就能看出哪些目录是独立的文件系统边界。7.2 快速梳理挂载点容量结合df -hT和du我通常按下面的逻辑排查先用df -hT看所有文件系统的类型和挂载点。对每个接近满的文件系统执行du -h --max-depth1 -x 挂载点-x参数确保只统计本文件系统内部的数据。对定位到的大目录继续逐层下钻。另外一个替代工具ncdu也值得推荐。它是du的交互式 TUI 版本可以快速浏览目录占用量、按大小排序、甚至直接删除光标所在文件。没有的话先安装apt install ncdu # Debian/Ubuntu yum install ncdu # CentOS/RHEL第一次跑的时候它会扫描整个目录树之后的操作就像浏览文件管理器一样顺畅。对于大目录探底效率比纯命令行高不少。8. 故障处理完成之后还有一件收尾的事空间清理完磁盘告警解除不代表这件事就到此结束。如果只是删文件—看 df—确认没满大概率过一段时间又会重复告警。我习惯再把每个坑对应的职责梳理一遍把运维变成可维护的流程。以软件项目作类比磁盘故障就像只改 bug 不改设计问题当然会复现。能彻底解决问题的方式是把容量管理和日志策略管理作为系统日常的一部分而不仅是出告警时的被动反应。具体可以做三件事把磁盘使用率、inode 使用率、分区剩余 inode 数、日志目录大小做成常规监控项配合告警阈值提前发现潜在风险。为常用目录建立 crontab 定时清理任务。比如临时目录超过7天就清空日志目录超过30天就压缩并删除旧档。定期检查测试环境或非核心业务的挂载快照、预留空间、无用挂载点尽早发现异常消耗。对于真的没法通过清理短时间释放容量的路径比如数据持续增长的数据盘需要提前规划扩容方案。扩容方式取决于存储架构——传统分区支持在线扩容LVM 可以加 PV 再扩展 LV云盘通常也会支持快照扩容。关键是要在告警之前把扩容流程跑通否则真到容量打满的时候业务中断时间就变成不可控的了。我在实际处理中最大的体会是磁盘告警的难点从来都不在删除文件这一个动作上而是在于如何快速判断谁在占空间、为什么占、怎么预防它再占。把上面这七个坑在心里过一遍遇到告警就不慌思路顺下来二三十分钟肯定能定位到根因。下次再看到 du 和 df 打架记住第一条——先检查被删除但未释放的文件这一条就帮你在 80% 的场景里少走很多弯路。