Linux下“no space left on device”排查指南:inode、Docker与日志清理
2026/9/17 6:02:22 网站建设 项目流程

1. 先定个调:这个报错和“磁盘满了”不完全是一回事

凌晨两点,手机连着震了好几下。测试环境的监控告警弹出来:某台机器上的docker容器起不来了,报错信息就一句——“no space left on device”。我打开电脑,先敲了df -h,结果愣住了:根分区明明还剩十几个G,/home也还有空余,怎么看都不像“磁盘满”该有的样子。当时第一反应是“机器被黑了?还是磁盘坏了?”但试了试touch /tmp/test.txt,一样报同样的错误。

后来才知道,这个报错在Linux系统里根本不只代表“磁盘剩余空间不足”这一种情况。你面对的可能是文件系统块空间耗尽,可能是inode索引节点耗尽,可能是进程占用已删除文件导致空间不释放,也可能是Docker的overlay2目录把某个挂载点给塞爆了。不同原因,表现相近,排查路径却完全不同。

用一句话总结我的教训:df -h只是第一层侦查,它正常不代表文件系统真的还能写入。真正定位这个问题,需要你把视线从“还剩多少空间”转移到“空间到底被谁占着、为什么删了还不释放、文件系统还能不能创建新文件”这几件事上。这篇文章就想把这个排查链条完整捋一遍,包括我在生产环境踩过的坑、用过的命令、以及最后沉淀下来的预防手段。如果你是刚接触Linux运维、或者已经被Docker的报错折磨过几回的人,这篇应该能帮你省下半夜爬起来翻文档的时间。

2. 先别急着清理:两个命令先确定你面对的是哪一类“满”

2.1 排查前先区分:是块空间满,还是inode满

Linux文件系统里有两个很容易混为一谈的“容量”概念。

第一个是块空间,也就是数据本身占的位置。你存了一张照片、拉了一个镜像、写了一份日志,这些内容都要落在磁盘的block块上。df -h看的就是这个,单位是G、M,直观好懂。

第二个是inode(索引节点),你可以把它理解成文件系统给每个文件发的“户口本”。文件名、文件权限、所有者、数据块地址这些元数据都记在inode里。重点是:在绝大多数文件系统(ext4、xfs)里,inode数量是在格式化时就固定好的,不随磁盘空间动态增加。一个inode对应一个文件(目录、软链、socket也算),哪怕每个文件只有1字节,只要文件数量超过inode上限,系统就再也建不出新文件了。

我打个比方。块空间像一个仓库的货架,inode是货架上的编号标签。货架还剩一大半空位,但标签已经全部贴完了,新货就进不来。对应到系统上,就是df -h还有余量,但touch一个空文件都会告诉你“no space left on device”。

2.2 两个命令,五秒钟分清局面

遇到这个报错,第一步永远是这两个命令:

df -h df -i

df -h看块空间使用率,df -i看inode使用率。如果df -i显示/dev/vda1的IUse%已经跑到100%,那问题就清晰了——不是磁盘块不够,是文件数量爆炸了。如果df -i正常,df -h也正常,那就要考虑是不是某个进程占用了已删除文件(后面专门讲)。

这里给一张对照表,方便你按症状快速归类:

现象可能原因下一步动作
df -h显示100%,创建文件报错块空间不足du定位大目录,清理日志/镜像/缓存
df -i显示100%,块空间正常inode耗尽按目录统计文件数量,找小文件温床
两个都正常,但仍然报错进程占用已删除文件lsof +L1找deleted状态文件
Docker目录撑爆了挂载点overlay2/容器日志膨胀docker system df看镜像/容器/卷占用

这套判断逻辑我建议你记下来,因为它能帮你省掉80%的无头绪排查。我第一次遇到inode耗尽时,还在反复删大文件、清/tmp,忙活了半个多小时才意识到方向错了——真正的问题在几百公里外的一台NFS挂载目录里,几百万个session小文件把inode吃了个精光。

3. 用df -i看到100%之后:小文件是怎么把inode吃光的

3.1 inode是怎么被“小文件海”耗尽的

一旦确认是inode耗尽,接下来的核心问题就变成:到底是哪个目录冒出了海量小文件。生产环境里最常见的几类温床:

  • 容器运行时目录/var/lib/docker/overlay2下每个镜像层、容器层都是一堆目录和文件。镜像更新的层数越多,容器销毁重建越频繁,小文件就越堆积如山。Docker长时间不清理,这里能轻松囤出几十万甚至上百万个文件。
  • 应用session文件:PHP的session默认落盘到/var/lib/php/sessions,session过期回收配置不当,一天能产生几十万个文件。
  • 日志切分碎片:有些应用不按天滚动日志,而是把日志按请求切成大量小文件;还有systemd-journald的journal文件,长时间不轮转也会产生大量文件。
  • 邮件队列/var/spool/mqueue或Postfix队列里有大量未发送的小邮件,每封就是一个文件。
  • 临时目录/tmp/var/tmp被乱七八糟的程序当缓存目录使用,文件只写不删。

这些场景有个共同特点:单个体积很小,但数量极其庞大。所以你拿du -sh去查,每个目录看起来都只有几百M,完全不起眼,但它们加在一起却能把inode吃干净。这就是为什么inode问题容易让你“查了个寂寞”——大文件排查法在这里失效了。

3.2 用两个命令快速盘出“文件大户”

inode耗尽的场景下,du看体积没用,得按“文件数量”来统计。我常用的命令是这俩:

# 从根目录开始,按层统计各目录的文件数量 for d in /*; do echo -n "$d: "; find "$d" -xdev | wc -l; done # 深入某个指定目录,找到最底层的一级 for d in /var/lib/docker/*; do echo -n "$d: "; find "$d" -xdev | wc -l; done

find | wc -l思路很朴素:数一遍目录树里到底有多少个条目。加上-xdev是为了不跨文件系统,避免把其他挂载点里的文件也算进来。如果你嫌一层层手工点太慢,也可以用du --inodes一步到位:

# 按inode数量排序,找出当前目录下哪个子目录文件最多 du --inodes -d 1 / | sort -rn | head -20

du --inodes在GNU coreutils里是原生支持的,直接按inode使用数做汇总,不用自己写循环,省事得多。跑完基本就能看到/var/lib/docker/var/lib/php/sessions这种目录突兀地排在前面。

3.3 inode耗尽的临时缓解与根治思路

找到“文件大户”之后,操作上要分两步走。

临时缓解是先把inode使用率降下来。如果是session文件或临时缓存文件,可以直接清掉一部分老文件;如果是overlay2目录,就要用docker system prune或手动清理悬空镜像(后续展开)。这一步的目标是让系统先恢复写入能力,别让业务继续挂着。

根治思路则要看文件产生的源头。比如session文件,你得去排查session.gc_probabilitysession.gc_maxlifetime配置,调大清理概率;比如容器日志碎片化,你得在应用侧或docker日志驱动上做轮转配置。只清一次不解决上游,过一周又满了——这种“清了又满、满了又清”的循环我见得太多。

注意:如果你确认是/var/lib/docker下的文件数量爆炸,并且历史镜像很多,直接手动删某个overlay2子目录容易把镜像搞坏。宁可花时间跑docker image prunedocker system prune,也别去rm -rf底层目录结构。

4. 精准定位:从大目录一路查到被占用空间的完整链路

4.1 块空间满的场景:du逐层下钻的笨办法

如果df -i正常、df -h显示某个分区已经100%,那就要老老实实拿du逐层下钻。我习惯的路径是从挂载点根目录开始:

du -h -d 1 / | sort -hr | head -20

-d 1表示只看一层子目录,sort -hr按人类可读的容量倒序排列。看到/var最大,就再往下一层:

du -h -d 1 /var | sort -hr | head -20 du -h -d 1 /var/lib | sort -hr | head -20

一层层追下去,最终会定位到某个具体的目录,比如/var/lib/docker/var/log、或者某些应用的data目录。这个办法虽然朴素,但最可靠——它不会因为文件类型不同而漏判,所有占空间的东西都会暴露出来。

实际排查中,有几个目录特别值得优先怀疑:

目录常见膨胀原因
/var/log各类应用日志、nginx访问日志不轮转
/var/lib/docker镜像层累积、悬空镜像、容器可写层扩大
/var/lib/mysqlbinlog日志、慢查询日志、临时表空间
/tmp程序写临时文件不清理、core dump文件
/home用户目录里的大文件、构建产物

4.2 deleted files:删除后空间却一直不释放的“幽灵占用”

这是我个人认为Linux磁盘排查里最玄学、也最容易被忽略的一种情况。现象是:df -h显示某个分区还是满的,但你明明已经把大文件rm掉了,空间却一点没降下来。

原因在于——进程仍然持有已经被删除文件的文件句柄。Linux下,文件被删除之后,如果某个进程还在通过句柄读写它,那么这个文件的数据块并不会真正释放,得等进程关闭句柄或退出才行。常见场景有:

  • 运行中的Java/Python进程,日志文件被rm了但进程没有重新打开文件描述符;
  • MySQL等数据库的临时文件或undo log被误删;
  • 某些服务的buffer/cache文件,被keep打开着。

排查方法就是靠lsof

# 查找所有处于deleted状态但仍被打开的文件 lsof -nP | grep deleted

正常情况下列表可能就几条,但空间满的时候,你往往会在这里看到几个“幽灵文件”,占用好几十G,位置显示/var/log/xxx.log (deleted)。找到占用进程的PID之后,有两种处理方式:

  • 如果你能停服务——直接重启或者让进程重新加载配置,句柄释放,空间自动回来;
  • 如果服务不能停——可以用cat /dev/null > /proc/PID/fd/文件描述符路径的方式把文件截断,注意不是删掉,而是清空内容。这个操作要小心,确认好fd路径再执行。

提示:rm掉日志文件之后,如果服务还能正常写日志,可能会冒出“No such file or directory”的错误。正确做法是先kill -USR1kill -HUP让进程重新打开日志文件,而不是简单粗暴直接删。

4.3 用ncdu把排查效率提上去

如果是第一次排查这种问题,或者机器上的目录结构比较深,手工一层层du确实累。我的经验是可以直接装一个ncdu(NCurses Disk Usage),交互式排查效率翻倍:

# CentOS / Ubuntu yum install -y ncdu # 或 apt install -y ncdu # 在根目录下扫描,进入交互界面后可以用方向键逐层浏览 ncdu -x /

ncdu把整个目录树按占用大小排好,上下键切目录,d键还能直接在界面里删文件。它和du的本质是一样的,只是把“逐层命令”变成了“视觉浏览”,更大程度上减少了漏看、错看的情况。如果你经常要处理这类问题,强烈建议装一个。

5. docker场景专项:overlay2是怎么把空间和inode一起耗光的

5.1 docker的存储结构为什么这么容易出问题

Docker之所以经常成为“no space left on device”的重灾区,和它的存储驱动机制有关。默认的overlay2驱动,会把镜像分成一层一层的只读层,容器运行时再叠一个可写层。每次docker build产生新的镜像层,每次docker pull拉取新镜像,都会在/var/lib/docker/overlay2下铺设大量目录和文件。

时间一长,这个目录会有两个趋势:

  • 体积变大:镜像层和容器可写层累积,加上历史镜像不清理,几十G甚至上百G都有可能;
  • 文件数量爆炸:每个镜像层都有独立的目录、diff目录、link文件等,镜像越多文件数越多。

这就导致一个问题:有时候df -h/var/lib/docker挂载点还有空间,但df -i已经告急;有时候反过来,文件数不多,但块空间被一堆悬空镜像吃光了。两种“满”都可能由Docker引发,排查时一定把两个指标都看了。

判断Docker具体占了多少空间,用这个命令最直接:

docker system df

它会列出镜像、容器、数据卷、构建缓存这几类分别占用了多少空间,以及有多少是“可回收”的。如果IMAGE列显示一堆悬空镜像(<none>),容器列有大量已退出但没删除的容器,BUILD缓存也囤了一堆,那清理空间的空间就很大。

5.2 容器日志无限膨胀是隐形杀手

很多人排查Docker磁盘占用,注意力全放在镜像上,忽略了容器日志。Docker默认的日志驱动是json-file,每个容器的标准输出都会落成宿主机上的日志文件,路径在/var/lib/docker/containers/<容器ID>/<容器ID>-json.log

如果应用本身打日志没有节制、而且没有配置日志轮转,这个json文件能涨到好几个G。更麻烦的是,这个文件被容器进程持续打开,就算你手工rm了它,空间也不会释放——这就是前面说的“幽灵占用”在Docker场景下的典型表现:du看不到大文件,df却依然满着。

我处理过最极端的一次,一个跑Java服务的容器,json日志文件涨到了30多G,节点差点挂掉。后来在/etc/docker/daemon.json里加了日志轮转配置,才彻底解决:

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

max-size表示单个日志文件最多50M,max-file表示最多保留5个文件。改完配置后重启docker生效。要注意:这个配置只对之后创建的容器生效,已经存在的容器还是老配置,最稳妥的办法是重建容器,或者至少在部署新容器时把参数传上:

docker run \ --log-driver json-file \ --log-opt max-size=50m \ --log-opt max-file=5 \ your-image

5.3 三条docker清理命令的边界和代价

Docker自带的清理命令用起来有讲究,不建议一把梭。下面这张表是我自己总结的边界:

命令清理内容注意点
docker container prune已停止的容器如果容器里还有未提交的变更,删了就没了
docker image prune悬空镜像(<none>标签)不加-a只清悬空镜像,比较安全
docker image prune -a所有未被容器使用的镜像会把还在用但没跑容器的镜像也删掉,再次拉取会花时间
docker system prune -a --volumes镜像+容器+网络+数据卷+构建缓存最激进,会把所有闲置数据卷一并删除,务必先确认

生产环境的建议是:日常清理用docker image prunedocker container prune就够了,-a--volumes只在空间告急且确认无重要数据时使用。数据卷(volume)里经常藏着数据库文件,一旦被--volumes删掉,想恢复基本没戏。

6. 治标更要治本:这台机器以后怎么才能不再警报

6.1 日志轮转和核心配置:一劳永逸的基础项

排查完问题不代表结束。真正要紧的是让这类问题“少来烦你”,所以我在每次处理完no space问题的几天内,都会顺手把下面几件事补上:

第一个是系统日志轮转/etc/logrotate.conf/etc/logrotate.d/目录下配置了各系统日志的轮转策略。确认一下有没有把关键日志目录加进去,比如:

/var/log/nginx/*.log { daily rotate 14 compress delaycompress missingok notifempty dateext }

这段配置的意思是nginx日志每天切一次、保留14份、切完压缩。类似地,Java应用如果通过stdout写日志,就要靠Docker的log-opts限制;如果是文件日志,就得靠logrotate。

第二个是systemd-journald的容量上限。journald默认不会无限占用磁盘,但上限在某些发行版上偏高,需要主动收紧。修改/etc/systemd/journald.conf

SystemMaxUse=500M MaxRetentionSec=2week

改完执行systemctl restart systemd-journald。这样journal日志最多占500M的黑洞空间不会侵蚀业务分区。

第三个是监控告警要同时覆盖df -hdf -i。很多监控工具默认只看磁盘使用率,inode告警没人管,所以有时候inode满到100%还得等业务报障才发现。

6.2 一条命令级别的“脚本体检”

我习惯在服务器上保留一个快速体检脚本,排查时省得敲一串命令。下面这个是精简版:

#!/bin/bash echo "===== 块空间使用 =====" df -h | grep -v tmpfs echo "" echo "===== inode使用 =====" df -i | grep -v tmpfs echo "" echo "===== 最占空间的10个目录 =====" du -h -d 1 / 2>/dev/null | sort -hr | head -10 echo "" echo "===== deleted但未释放的文件 =====" lsof -nP 2>/dev/null | grep deleted | head -20

把这段存成disk-health.sh,需要的时候直接执行,10秒出全貌。也可以挂到crontab里凌晨跑一次,输出重定向到日志文件,早上扫一眼就够了。

6.3 我踩过几次坑之后的最终检查清单

这套问题前前后后处理过七八回之后,我给自己沉淀了一份清单,每次遇到no space相关告警就走一遍:

  1. 先看df -hdf -i,把“块满”和“inode满”分开。
  2. 确认挂载点边界:报错的目录到底落在哪个分区上,别在A分区查了半天,实际写的是B分区。
  3. 块空间满:用duncdu逐层定位,优先查/var/log/var/lib/docker/tmp
  4. inode满:用du --inodesfind | wc -l找小文件温床,重点看session目录和overlay2。
  5. df显示满但du找不到大文件:立刻跑lsof | grep deleted,找幽灵句柄。
  6. Docker场景:先docker system df看镜像和构建缓存,再查/var/lib/docker/containers/*/*-json.log的日志大小。
  7. 清理后:用df -hdf -i复验,确认使用率降下来,并记录占空间最大的TOP目录,便于下次快速定位。
  8. 事后补配置:日志轮转、journald上限、docker日志驱动、监控告警,四项缺一不可。

这套清单帮我在后来的几次事件里,基本都能在几分钟内找到根因。排查的主动权,其实就来自“你知道空间可能藏在哪几个角落”的预判——这比临时翻文档有效得多。

最后再说个实际体会。清理磁盘永远只是“灭火”,真正省心的是把“防火”做在前面。日志轮转、镜像清理频率、inode监控这三样只要舍得花半小时配置好,后面一年可能都不会再碰这个报错。别等到凌晨手机响才想起来补课,那种感觉我太熟了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询