线上服务突然开始报错。
不是CPU打满。
不是内存爆了。
也不是数据库挂了。
日志里反复出现一句:
No space left on device第一反应当然是:
磁盘满了?
结果你马上执行:
df -h却发现最主要的磁盘分区只用了60%左右。
明明还剩40%空间,甚至还有几十GB可用。
这时候就很容易怀疑:
监控是不是错了?
系统是不是抽风了?
容器是不是误报?
如果这时直接让 ChatGPT、Codex:
“帮我清理一下磁盘。”
很可能一上来就查错方向。
因为Linux里的:
“No space left on device”并不一定只代表磁盘容量真的100%用满。
真正需要先搞清楚的是:
系统到底缺的是磁盘Block、inode、可写挂载空间,还是被某个进程占着没有真正释放。
一、先给核心判断:磁盘空间还有,不代表“还能创建文件”
Linux文件系统真正能不能继续写,不只取决于:
还有多少GB。
还取决于:
- inode是否耗尽;
- 当前目录所在挂载点是否已满;
- 删除文件是否仍被进程占用;
- 容器自己的Overlay空间是否已满;
- 临时目录或日志目录是不是单独分区;
- 文件系统是否进入异常状态。
所以看到:
No space left on device
第一件事不是继续盯着df -h。
而是要回答:
到底是“容量没了”,还是“文件系统资源没了”?
二、第一种高频情况:inode已经用完了
这是最经典的一类。
文件系统除了存储数据块,还要为每一个文件维护inode。
inode可以理解成:
文件的元数据入口。
比如:
权限。
所有者。
时间。
数据块位置。
如果一个磁盘里出现海量小文件,就可能出现一种很反直觉的情况:
磁盘容量还剩很多,但inode已经100%用完。
比如:
日志目录里每天生成几十万个小文件。
缓存目录不断创建临时文件。
某个程序每个请求都写一个独立文件。
结果磁盘用了60%。
但inode已经:
100%。
这时候系统再创建新文件,就可能直接报:
No space left on device
哪怕还有几十GB容量。
三、最快先看df -i
遇到这种问题,我通常会在df -h之后马上执行:
df -i重点看:
IUse%
如果某个挂载点已经:
100%
那基本就已经找对方向了。
例如:
Filesystem Inodes IUsed IFree IUse% /dev/sda1 6553600 6553598 2 100%这时候真正缺的不是:
GB。
而是:
inode。
所以清理策略也不能只找“大文件”。
你真正应该找的是:
哪个目录里有海量小文件。
四、为什么“找大文件”有时候完全没用?
很多人的清理习惯是:
找几个10GB日志 删掉这种方法解决的是:
Block空间。
但inode耗尽时,真正的问题可能是:
某个目录下面有:
300万个2KB文件。
总容量不大。
但每个文件都要占一个inode。
于是你可能删掉一个10GB文件,磁盘多出10GB。
结果:
No space left on device
仍然存在。
因为:
inode一个都没释放多少。
所以如果df -i已经接近100%,排查重点应该从:
“谁占空间最大”
切到:
“谁的文件数量最多”。
五、第二种高频情况:文件已经删了,但空间没有真正释放
这个场景也特别容易让人懵。
你发现一个日志文件占了20GB。
直接:
rm app.log
然后再看:
df -h
结果空间几乎没变。
很多人会以为:
Linux没删成功。
其实很可能是:
文件目录项删了,但进程仍然持有这个文件描述符。
比如Java进程还在持续写:
app.log
你把文件名删了。
文件在目录里已经看不到。
但只要进程还握着fd,
对应的数据块就不会真正释放。
于是会出现:
文件没了。
du也找不到。
但:
df空间还是被占着。
六、这种deleted-but-open文件怎么查?
一个非常实用的办法就是:
lsof +L1或者查包含:
(deleted)
的文件。
如果看到类似:
java 12345 app 6w REG ... 20G /logs/app.log (deleted)那就很明显了。
文件虽然被删了,
但Java进程还在占。
真正释放空间通常需要:
正确让程序重新打开日志文件。
重载日志。
或者在可控情况下重启对应进程。
这也是为什么:
“删文件”不等于“空间已经释放”。
七、第三种情况:你看的不是出问题的那个挂载点
这也是线上特别常见的误判。
你执行:
df -h
看到:
/
还有40%。
于是觉得:
磁盘肯定没满。
但真正报错的目录可能是:
/var/log
/tmp
/data
/var/lib/docker
这些路径可能挂载在完全不同的文件系统上。
比如:
根分区还有40%。
但/var/log单独挂载的分区已经100%。
应用写日志时一样会报:
No space left on device
所以不能只看:
“机器总磁盘还剩多少”。
要看:
报错目录实际属于哪个Filesystem。
八、最快的方法:先确定报错路径属于哪个挂载点
比如报错发生在:
/data/upload/tmp
那就直接围绕这个路径检查。
不要泛泛地看整个机器。
你真正要确认的是:
这个目录所在Filesystem的:
容量。
inode。
挂载参数。
状态。
否则很容易出现:
你一直清理/
但真正满的是/data
这种低效排查。
九、第四种情况:容器宿主机有空间,但容器自己的写层满了
Docker、Kubernetes场景里,这个问题更容易出现。
你看宿主机磁盘:
还有很多空间。
但容器内部却已经写不动。
原因可能是:
Overlay文件系统。
容器可写层。
emptyDir。
临时卷。
日志层。
某个特定PVC
已经触碰自己的限制。
特别是:
应用大量写/tmp
或者不断产生日志、临时文件、缓存。
从宿主机总容量看:
还有空间。
但容器当前真正能用的那一层已经满了。
所以容器环境里一定要区分:
Host Disk
和:
Container Writable Layer / Volume
这两个不是一回事。
十、Kubernetes里还要注意emptyDir和Ephemeral Storage
很多服务会把临时文件写到:
/tmp
或者挂一个:
emptyDir
看起来简单方便。
但如果:
文件不断增长。
程序不清理。
或者一个请求会生成大量临时内容,
就可能消耗Pod的Ephemeral Storage。
最后你看到:
宿主机磁盘没满。
Persistent Volume也正常。
但Pod照样因为临时存储问题异常。
所以K8s排查时不能只看:
PVC。
还要看:
Ephemeral Storage使用情况。
十一、还有一种很容易被忽略的情况:日志轮转“看起来有,实际上没生效”
很多服务都配置了日志轮转。
比如:
每天一个文件。
保留7天。
超过大小自动切分。
理论上应该很安全。
但线上经常出现:
轮转规则没匹配到。
程序自己又持有旧文件。
压缩逻辑失败。
旧日志根本没删。
最后目录里堆了:
几千个文件。
甚至几十万个文件。
所以如果一个日志目录突然把inode吃满,不能只问:
“有没有logrotate?”
而要继续确认:
它到底有没有真正执行成功。
十二、最快排查顺序,我建议直接按这7步走
以后遇到:
“磁盘还有空间,却报No space left on device”
可以直接按这个顺序。
第一步:锁定报错目录
先确认到底哪个路径写失败。
第二步:看这个路径对应的Filesystem
不要只看根分区。
第三步:df -h
看Block空间。
第四步:df -i
看inode。
第五步:lsof +L1
检查deleted-but-open文件。
第六步:统计大目录和小文件数量
确认是大文件吃容量,还是小文件吃inode。
第七步:如果在容器里,再看Overlay、emptyDir、Ephemeral Storage和PVC
这套顺序比:
“一上来找大文件删”
有效得多。
十三、具体怎么修,要看是哪一类问题
如果是:
inode耗尽
清理海量小文件。
优化临时文件和日志生成策略。
必要时调整文件系统设计。
如果是:
deleted-but-open
找到持有文件的进程。
正确触发日志重开、reload,或者在可控情况下重启进程。
如果是:
某个单独挂载点满了
清理对应挂载点。
不要去清别的分区。
如果是:
容器写层满了
检查写路径是否应该迁移到Volume。
减少容器层写入。
设置合理清理机制。
如果是:
日志轮转失效
修轮转规则。
确认程序和logrotate的协作方式。
十四、为什么不建议直接“rm -rf 一堆目录”?
因为线上空间问题最容易引发第二次事故。
比如误删:
正在使用的数据。
缓存索引。
运行时文件。
数据库临时目录。
应用依赖文件。
所以真正修之前应该先回答:
这些文件是谁创建的?
现在谁还在使用?
删除以后能不能自动重建?
是否影响正在运行的服务?
如果只是为了快速腾空间,最好也先明确:
哪一类文件是安全可删除的。
而不是见目录大就删。
十五、修完以后怎么验证?
不能只看:
df -h降下来了。
至少还要一起看:
df -h是否恢复;df -i是否恢复;- deleted-but-open是否清零;
- 报错目录是否重新可写;
- 日志是否继续正常轮转;
- 文件数量是否还在异常增长;
- 容器临时存储是否稳定;
- 重启后问题会不会再次出现。
真正的目标不是:
“现在能写了。”
而是:
这个资源耗尽路径已经被切断。
十六、一个很典型的现场
比如你看到:
根分区:
60%。
第一反应:
磁盘还有40%。
但继续查:
df -i
发现:
/var/log
inode已经100%。
再统计文件数:
某个服务每秒生成几十个独立小日志。
一天就是几百万个文件。
这时候根因就很清楚:
不是磁盘容量不够。
也不是Kubernetes有问题。
而是:
文件数量把inode提前耗尽了。
这类问题如果不先看inode,
再怎么删几个大文件都解决不了。
十七、这种Linux磁盘排查,Plus和Pro怎么判断?
如果你平时只是偶尔让 ChatGPT、Codex:
分析一次磁盘告警。
看看df -h、df -i。
帮你读一下lsof。
辅助判断Docker、K8s写层问题。
这种任务比较集中,Plus通常已经够用。
关键还是把Evidence给完整:
报错路径。
Filesystem。
Block使用率。
inode使用率。
容器信息。
日志目录。
文件数量。
这些信息比只给一句:
“服务器磁盘报错了。”
有用得多。
如果你的日常已经变成:
持续排查Linux、Docker、Kubernetes、JVM、数据库、网络问题。
一次线上事故需要同时分析:
系统日志。
容器日志。
磁盘。
inode。
文件句柄。
Pod事件。
应用代码。
还要让Codex继续改清理逻辑、日志策略、容器配置并做回归,
这种高频、多轮、长时间工程排障已经成为日常,
那Pro会更适合。
所以Plus还是Pro,不看:
“这个磁盘问题难不难。”
而是看:
ChatGPT、Codex每天是不是已经持续参与完整的线上排障链路。
偶发排查、短任务:
Plus通常够用。
持续系统治理、多轮分析和验证:
再考虑Pro。
最后
磁盘明明还有40%空间,
为什么服务却突然报:
No space left on device?
因为Linux里的:
“没空间”不一定只代表GB用完了。
还可能是:
inode没了。
错误挂载点满了。
文件删了但进程还占着。
容器写层满了。
临时存储耗尽。
日志轮转失效。
所以以后遇到这类问题,不要只盯着:
df -h
更完整的排查应该是:
Block → inode → Mount → Open File → Container Layer → Log Rotation
一旦把这几个层级拆开,
很多看起来特别矛盾的:
“磁盘明明还有空间,为什么却写不进去?”
其实很快就能定位。
真正的问题往往不是:
磁盘到底还剩多少GB。
而是:
当前这个写入路径,到底还剩多少真正可用的文件系统资源。
持续分享 Codex、大模型开发与 AI 编程实战内容。
长期深度使用各类代码大模型,也整理了稳定的Plus/Pro会员订阅渠道,有需要可自取。