1. 从一次磁盘告警说起:问题定位的完整思路
做运维的人,谁没被磁盘空间告警半夜叫起来过?我印象最深的一次,是某个周五晚上十一点多,监控平台连发三条告警,/data 分区使用率从 92% 一路冲到 98%,接着业务侧就有人打电话说导出报表卡住了。说实话,磁盘空间不足这种问题,不像服务宕机那样让人眼前一黑,但它特别磨人——因为"空间不够"只是一个结果,背后的原因五花八门,有时候你以为删掉几个大文件就完事了,结果第二天又满了,甚至越删越满。
这篇文章想跟你聊的,不是单纯丢几条命令让你照着抄,而是把我这些年处理磁盘空间问题的一套完整思路整理出来:从接到告警开始怎么快速定位,到排查过程中有哪些命令组合比单个命令好用得多,再到那些容易让人误判的隐藏细节,最后聊一聊怎么在清理完之后做长期规划,不让同一块石头绊倒两次。适合刚入门、遇到磁盘告警心里没底的运维新人,也适合已经处理过几次但总感觉"差点意思"的初级系统工程师。
先说我踩过的第一个坑。刚接触服务器那会儿,我一看到磁盘空间不足就执行 rm -rf 到处删,删完看 df -h 确实下降了,以为大功告成。但没过几天告警又来了,而且更奇怪的是,明明删了十几个 G 的文件,空间却没有像想象中那样完全还回来。后来才明白,磁盘空间不足的排查不是"删文件"这么简单,它至少包含三层:空间确实被文件占满了、文件被删了但空间没释放、不是空间满而是 inode 满了。这三层如果分不清楚,后面做的全是无用功。
所以在这篇文章里,我会把每一层都拆开来讲,配合我实际用过的命令和案例,尽量让你看完之后,再遇到磁盘告警能有一套自己的排查节奏,而不是像无头苍蝇一样乱撞。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先分清"空间满"还是"inode 满":两个 df 命令看出门道
2.1 常规空间检查,为什么 df -h 不是万能的
处理磁盘问题,绝大多数人第一反应是敲 df -h,这个习惯没问题,但只做这一件事就远远不够。df -h 输出的是文件系统当前被占用的块大小,单位是 G 或 M,它能告诉你"总体快满了",却没法告诉你"是不是有大量碎片级小文件把空间吃光了"。
举个例子。某次我在一台跑着 NFS 缓存服务的机器上排查,df -h 显示 / 分区用了 99%,但用 du -sh /* 从根目录往下找,怎么加都加不出那么多,总觉得对不上账。后来看了一眼 df -i 才发现,Inodes 使用率已经 100%,文件系统里堆满了数以百万计的小文件——都是程序产生的缓存碎片,每个才几 KB 甚至更小,但它们把 inode 表撑爆了,系统照样报"磁盘空间不足"。
这就是我一开始说的第二层问题:空间还有盈余,但 inode 已经耗尽,系统同样无法写入。 inode 这个概念,你可以把它理解成一本账本,每个文件或者目录都要占一行记录,账本满了就算货架上有地方摆货也进不了新货。所以,遇到底层告警的时候,我建议你把这两个命令当成固定搭档:
bash复制df -h
df -i
df -h 看的是容量,df -i 看的是容量背后的索引节点。两者一对比,问题属于哪一种就立刻清楚了:如果 df -i 显示已经 100%,但 df -h 还有不少剩余,那你要找的是"小文件密集区",而不是"大文件",排查思路完全不同。
2.2 inode 满了怎么进一步定位
一旦确认是 inode 耗尽,普通的大文件扫描工具就不适用了,因为你得找的是"文件数量最多"的目录,而不是"占用空间最多"的目录。这里我惯用的思路是分两步走。
第一步,先用 find 统计根目录下各个一级子目录的文件数量,比如:
bash复制for dir in /*; do echo -n "$dir: "; find $dir -xdev -type f | wc -l; done
这个命令会遍历根目录下每个一级子目录,统计它们各自包含的文件数。注意其中的 -xdev 参数,它表示不跨文件系统——这样做是为了避免统计到挂载点下面的其他分区,导致数字失真。
第二步,根据统计结果锁定嫌疑最大的目录,继续往下钻。比如发现 /var 下面文件数量异常,就逐层展开 /var/spool、/var/tmp、/var/log,一层层找。我处理过的典型场景里,邮件队列 /var/spool/mqueue、定时任务产生的临时文件、程序异常的缓存目录,都是小文件堆积的高发地。
3. 空间确实被占满了:如何快速揪出"空间大盗"
3.1 最实用的组合拳:du + sort + head
排除了 inode 问题,确认是普通空间占满之后,下一步就是找到大文件或者大目录的位置。这一步看起来简单,但很多人会用错命令——比如直接对整个根目录执行 du -sh /,如果磁盘上文件数量很多,这可能会跑几分钟甚至更久,而且数据非常笼统,帮助不大。
我更推荐从小向大逐级排查的方式。进入根目录,执行:
bash复制du -h --max-depth=1 / | sort -rh | head -20
注意我用的是 --max-depth=1,意思是只看当前层级的目录总大小,不再往下递归统计每一层。加上 sort -rh,按人类可读的容量从大到小排序,再取前二十行。这一步可以快速给出一个清单,让你立刻知道哪个一级目录最吃空间,比如可能是 /data,也可能是 /var 或者 /home。
拿到结果后,锁定最大的目录继续往下钻。比如 /data 最大,就进到 /data 再跑一次同样的命令:
bash复制du -h --max-depth=1 /data | sort -rh | head -20
重复这个过程,直到定位到具体的可疑目录或者文件。这种"逐层下钻"的办法,效率比从整个文件系统做全量扫描高得多,而且不容易被零散的小文件干扰。
3.2 大文件直接找:find 的一次性扫描
有些场景下,大文件就明晃晃地躺在某个目录里,不需要一层层往下找,直接用 find 一口气扫出来反而更快。比如我想找出整个 /data 分区下所有超过 1G 的文件:
bash复制find /data -xdev -type f -size +1G -exec ls -lh {} \;
这里的 -xdev 同样是为了限定在当前文件系统内查找,避免扫进其他挂载点。-size +1G 表示文件大小大于 1G。如果你怀疑是日志文件撑爆了磁盘,可以按文件名模式过滤,比如:
bash复制find / -xdev -name "*.log" -mtime +7 -size +100M -exec ls -lh {} \;
这条命令会找出最近七天没有修改过、大小超过 100M 的日志文件。这类文件往往是程序运行很长时间都没做切割、轮转,越攒越大。找到之后,先确认是什么程序在写它、能不能清理,再决定是删除、压缩还是配置 logrotate。
3.3 空间"神秘消失":别忘了被删除但还被进程占用的文件
接下来的场景,很多人第一次遇到时会非常头疼——明明 du 加起来所有文件的大小远小于 df 报告的使用量,空间却就是不够用。这种情况最常见的元凶之一,是文件已经被删除,但某个进程仍然持有该文件的句柄。
Linux 下文件被删除,并不代表磁盘上对应的数据块立即被释放,只有当所有打开这个文件的进程都关闭句柄之后,空间才会真正归还。这就像你家里有一箱旧报纸,你把它丢进了楼下回收站,但还有个人牢牢攥着箱子不撒手,那这箱报纸的空间就相当于还占着。
排查办法是 lsof:
bash复制lsof | grep deleted
如果输出里有大量被标记为 deleted 的文件,说明果然有进程占着已删除的文件。常见于:程序不断打开日志文件写入,日志被 logrotate 切割后旧句柄没释放;或者某些服务临时写了文件之后没有关闭 fd。处理办法通常是重启对应的进程或者服务,让它重新加载文件描述符。
我遇到过最典型的一次,是 Java 应用长期运行,日志文件被 logrotate 改名之后,Java 进程还握着旧文件句柄,导致实际空间越占越多,du 却显示不出这些"隐形文件"。重启应用后,空间瞬间释放了几十 G,问题立刻解决。所以当你发现 du 跟 df 对不上账时,别急着怀疑工具出 bug,先查一下 lsof。
4. 磁盘空间不足的典型病因:日志、缓存、容器和临时文件
4.1 日志文件:最容易被忽视的"慢性杀手"
日志导致磁盘空间占满,在运维里大概能排在病因榜首。多数服务默认的日志策略是只写不转,或者虽然有 logrotate 但配置不到位,久而久之 /var/log 下面的文件就能吃掉几十甚至上百 G。
我遇到过的几个高频场景:
/var/log/journal目录,使用 systemd 的机器上 journald 默认日志上限是文件系统容量的 10%,如果机器磁盘大,10% 可能是很大一笔空间。可以通过journalctl --disk-usage查看占用,用journalctl --vacuum-size=500M或者journalctl --vacuum-time=7d清理。- Nginx、Tomcat、MySQL 等应用的 access log、error log,没有开启定时切割,单个日志文件长时间不重启就一直往同一个文件里写。
- 程序自己打印的 debug 日志,代码里没有按天或按大小滚动,一旦线上环境忘关了 debug 级别,日志量直接爆炸。
对这种"慢性杀手",单纯清一次是没用的,关键是配好 logrotate。下面是一个我常用的配置模板,放到 /etc/logrotate.d/ 下面,以应用日志为例:
code复制/path/to/your/app/logs/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
dateext
postrotate
/usr/bin/kill -USR1 $(cat /var/run/yourapp.pid)
endscript
}
这里的逻辑是:日志每天切割一次,保留最近七天,旧日志压缩存储。delaycompress 的意思是今天切割下来的日志先不压缩,等到明天再压缩,防止刚刚切换句柄时程序还在往旧文件里写。postrotate 是切割后执行的脚本,常见做法是给进程发送信号让它重新打开日志文件,但具体信号视应用而定,有的程序用 USR1,有的用 reopen_logs 接口,需要查对应文档。
4.2 临时文件与缓存:tmp 目录和程序缓存吃满空间
另一个常见病因是临时文件和程序缓存。Linux 系统下 /tmp 目录和 /var/tmp 目录天生就是给程序丢临时文件的地方,正常情况下系统会定期清理,但有些程序清理不及时,或者崩溃之后残留临时文件没人管,时间一长就会攒出大量垃圾。
我处理过的一个案例是某台构建机上,持续集成工具每次构建都会在 /tmp 下解压大量依赖包,正常情况下构建结束会清理,但有几次任务被强制杀掉,临时目录残留了几十个 G 的中间产物。后来我写了一个定时任务,每天凌晨清理 /tmp 下超过三天的文件:
bash复制find /tmp -xdev -type f -mtime +3 -delete
find /tmp -xdev -type d -empty -delete
第一个 find 删除三天前的普通文件,第二个 find 删除空目录。注意我加了 -xdev,防止误删到挂载的其他分区。这里的 -delete 比 -exec rm {} \; 更快、更安全,因为它不会出现文件名以 - 开头被 rm 误判为参数的问题。
程序缓存方面,除了常见的包管理工具缓存(比如 apt、yum 缓存),还有一个经常被忽略的:Java 应用或者大数据组件会产生大量中间缓存文件。这类缓存有时候很有迷惑性——你看目录名字好像挺正经,比如 data、snapshot,实际上全是临时产生的可重建数据,删掉之后程序能自动重建,但如果没人敢删,就会一直堆下去。
4.3 容器与镜像:Docker 磁盘占用大户
现在绝大多数服务器都已经容器化,Docker 相关的磁盘占用经常不知不觉吃掉整个分区。df -h 一看,/ 满了,你翻遍传统目录也没发现大文件,很可能问题就出在 Docker 的默认数据目录 /var/lib/docker 上。
排查先从最简单的入手:
bash复制docker system df
这个命令会告诉你镜像、容器、构建缓存、本地卷分别占了多少空间。我见过不少案例,输出里 Build Cache 占了几十个 G——这是 Docker 构建时留下的缓存层,对生产环境来说既占空间又没价值,清理也安全。
清理操作要区分场景。如果只是镜像多了,但容器都在正常运行且没有需要保留的已停止容器,可以用:
bash复制docker image prune -a
这条命令会删除所有未被正在运行的容器使用的镜像。如果连构建缓存和已停止容器也想一块儿清理,可以执行:
bash复制docker system prune -a --volumes
一定要特别注意,--volumes 会把未使用的数据卷一并删除,如果你的容器里有需要保留的数据,千万别顺手加这个参数。我个人的习惯是,生产环境上只做 docker image prune -a 和 docker builder prune -f,不动数据卷。
容器日志是另一个隐形占用点。Docker 默认把每个容器的 stdout 日志写到 /var/lib/docker/containers/<容器ID>/*-json.log 文件里,如果容器内程序打印大量日志,又没限制日志文件大小,这个文件能涨到几个 G。建议在 Docker daemon 的配置文件里加 log rotate 参数:
json复制{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "5"
}
}
加完之后重启 docker 服务,新创建的容器才会生效。对存量容器,该清理的还是要手动清一下。
4.4 备份文件和安装包:看起来能做但不能乱删的对象
备份文件和安装包也是磁盘告警的常见贡献者。很多业务方习惯把备份直接放在本机 /backup 或 /data/backup 目录,一放就是几个月甚至几年,几个版本的完整备份叠加起来瞬间吃掉几个 T。
这类文件处理时要有耐心,千万别一刀切。我的建议是:先确认备份文件的来源和用途,再看系统里是否还有更早的副本,确认无用的再清理。如果有异地备份或对象存储,最好推动业务方把重要备份转移到其他地方,本机只保留最近几份。至于安装包、源码包、压缩包,这类文件大部分属于"下载以后就没再看过"型,用 find 找出大文件后,跟相关人员确认一下就能删除。
5. 从 Word 提示"内存或磁盘空间不足"反推运维排查思路
5.1 应用层报错,root cause 不一定在应用本身
加这一个话题,是因为近期后台搜索"word 内存或磁盘空间不足"的热度很高,说明很多人被这类应用报错折磨过。在运维视角里,这类报错恰恰是"应用层现象、系统层根因"的典型代表。
Windows 下的 Word 报"内存或磁盘空间不足",主要有几类原因:系统盘剩余空间太小,Word 无法在临时目录创建自动恢复文件;%TEMP% 临时目录被塞满,或者当前用户对临时目录没有写权限;虚拟内存(页面文件)配置过小或所在盘空间不足。作为运维人员,看到这类报错,第一反应应该是去查目标机器的系统盘分区。
查的思路跟在 Linux 上类似:确认 C 盘可用空间、核对用户临时目录大小、检查页面文件所在盘。很多时候你会发现,用户装了无数软件,C 盘早就红得发紫,Word 不过是被压垮的最后一根稻草。
5.2 针对 Word 报错的常用处置手段
既然这个热词这么火,我也顺手把处理 Word 报错的常用手段列一下,供桌面运维参考。
- 清理 C 盘临时文件夹:Win+R 打开运行,输入
%temp%,全选删除里面的文件,删除不了的跳过即可,多半是被占用的正在使用的文件。 - 查看 Word 自动恢复文件的位置:在 Word 选项 -> 保存里,可以看到自动恢复文件的保存路径,如果这个路径指向一个空间很小或权限受限的分区,改到其他盘可以缓解问题。
- 检查虚拟内存配置:右键"此电脑" -> 属性 -> 高级系统设置 -> 性能设置 -> 高级 -> 虚拟内存更改,确认页面文件大小和所在盘空间是否充足。
- 清理浏览器缓存、系统更新缓存,也是释放 C 盘空间的常规操作。
这些操作本身不复杂,但我特别想强调的是:不要只在应用层面救火,要给用户做好使用环境的长期规划。比如新装机的时候就把临时目录、更新缓存、应用数据迁移到数据盘,从源头上避免 C 盘被一个月塞满的尴尬。
5.3 从桌面端回到服务器端:同一思路的迁移
其实把 Word 报错背后的排查思路迁移到服务器端,就是我一直强调的:报错的是上层应用,根因往往在下层资源。服务器上的数据库连接失败,可能是磁盘满了导致临时表无法创建;Web 服务返回 500,可能是日志把分区爆了导致无法写会话文件。所以运维排查的时候,永远不要让"应用报错信息"牵着鼻子走,而是先确认系统底层最基础的几项资源:CPU、内存、磁盘空间、inode、句柄数。基础资源没问题,再去翻应用日志。
这也是为什么磁盘问题看似简单,却值得写一篇文章专门聊——它是一切上层服务正常运行的地基。
6. 清理之后怎么办:监控、告警与长期容量规划
6.1 配置有效的磁盘监控与告警阈值
磁盘空间问题的正确处理姿势,不应该是一天到晚等告警然后手动清理,而是配置好监控和告警,让问题在变成事故之前就被发现并止住。
监控方面,zabbix、prometheus + node_exporter、腾讯云/阿里云的云监控,都可以做磁盘使用率监控。这里我特别想提醒一个常见误区:很多人只监控了空间使用率百分比,但没监控 inode 使用率。如果你的某个分区里全是小文件,空间不到 50% 就可能 inode 已经爆满了,照样出事。
所以监控指标至少要有两个:
- 分区空间使用率,比如超过 80% 触发警告,超过 90% 触发严重告警。
- 分区 inode 使用率,同样建议设置 80% 和 90% 两档阈值。
另外,告警收到之后一定要有响应流程。我见过很多团队,告警配置得挺好,但是因为没有明确的处理流程,告警邮件静静躺在收件箱里没人管,直到业务挂了才想起来看。建议在告警通知里附上简要的排查指引,谁收到、怎么查、多久内响应,都要有明确规定。
6.2 从根源上减少"垃圾文件"的产生
监控只是兜底,真正省心的方法是让垃圾文件尽量少产生。可以从几个角度入手。
第一,日志集中化管理。别让每台机器的日志都在本地无限积累,用 logstash、filebeat、fluentd 之类的工具把日志统一收走,本地日志做短期保留即可。这样即使磁盘告警,损失也有限。
第二,完善日志切割策略。不只是系统日志,所有产生日志的应用都应该纳入 logrotate 管理。排查告警的时候,顺手把新发现的应用日志加到配置里,是运维应该有的一种"顺手"习惯。
第三,规范临时文件清理。前面提到的 /tmp 和 /var/tmp 清理定时任务,建议直接写进初始化脚本里。还有构建系统、CI/CD 流水线,也要做好构建产物和缓存的清理策略,避免一次性任务不断累积垃圾。
第四,存储分层。对于备份、日志、老旧数据这类"冷数据",不要一股脑堆在系统盘,可以规划独立的备份盘、归档盘,或者直接上对象存储和低频存储。热数据、温数据、冷数据分开存放,既便宜又不容易把生产环境打满。
6.3 容量预测与扩容的时机判断
监控和清理都做得不错之后,还有最后一个长期话题:什么时候该扩容。磁盘空间不是无限清偿债务,如果你遇到的应用是天生快速增长的——比如数据库数据、视频文件、日志仓库——那靠清理只能延缓告警时间,解决不了根本问题。
我常用的做法是:从监控系统里拉出最近三个月该分区的容量走势,按月增长率估算未来六个月的占用情况。如果按趋势推算,三个月内就会再次触达 90% 阈值,那就要提前启动扩容流程了。扩容和清理不矛盾,可以一边扩容一边推动业务方做归档清理,两条腿走路。
扩容时要考虑的也不只是空间大小,还有文件系统类型、挂载方式、是否需要在线扩容。xfs 文件系统支持在线扩容,但只支持扩,不支持缩;ext4 在线扩容限制较少;如果用的是 LVM 逻辑卷,扩展流程要先扩 PV 再做 lvextend,最后 resize 文件系统。具体操作步骤要提前在测试环境演练,不要在业务高峰期临时抱佛脚。
7. 实战中的几个关键心得,少走弯路少背锅
整个排查流程聊完,最后分享几个我在实战中积累的个人心得吧。这些细节不算什么高深技术,但每一条都是从真实的故障处理经历里换来的。
第一,删除文件之前一定要确认这是什么、谁在用、多久没动过了。 尤其是那些时间戳比较久远的大目录,别因为空间告警就急着动,先查进程、查依赖、查 mtime,宁可多花十分钟确认,也不要赌运气。我有一位同事就曾把一个正在被数据库使用但路径看起来很像"临时文件"的目录给清理了,最后不得不从备份里恢复,教训相当深刻。
第二,生产环境的清理操作,能不用 -f 就不用 -f。 比如 rm -rf 这种命令,建议只在确认要删除的目标绝对没问题的时候才用。可以用 mv 把文件先移动到一个回收目录,观察几天没问题再执行删除。虽然多占了一点空间,但给误操作留了回退的机会,这个缓冲在运维场景里往往能救命。
第三,监控告警一定要配"趋势",不要太依赖阈值。 磁盘使用率今天是 80%,这个数字本身说明不了什么,但如果连续几周每周涨 5 个百分点,那就意味着三个月后一定会爆。提前做容量评估和治理,远好过等到告警了再连夜处理。
第四,清理过后记得验证应用是否恢复正常。 磁盘空间释放之后,不要只看 df -h 顺眼了就收工,要实际去触发一下出问题的业务,确认服务能正常写文件、正常响应请求。很多磁盘问题会连带造成进程异常退出、文件损坏、缓存失效等次生问题,必须要有个收尾验证的动作。
我个人现在遇到磁盘告警时的处理节奏,基本稳定为:先 df -h 和 df -i 双查确认问题类型,再 lsof | grep deleted 排除隐形占用,然后用 du 逐层下钻定位大目录,最后根据根因做清理和后续配置优化。整个过程快的话十几分钟就能收工,慢的话也就是多钻两层目录的事。整套流程跑得越熟练,越能体会到——磁盘问题本身不难,难的是排查时保持清醒,不凭感觉乱删东西,一步一步按方法论走完。希望这篇文章能帮你在下次磁盘告警响起的时候,少一些慌乱,多一份笃定。
