磁盘还有空间却写不进去:Linux 排查的六个顺序
先说在前面
线上最常见的报警不是「磁盘满了」,而是「磁盘明明还有 30G 却报 No space left on device」。这类问题不是磁盘坏了,而是看到的数字和实际限制不是一回事:df 看的是文件系统块,du 看的是目录占用,而写入还会受 inode、配额、已删除文件、挂载点遮挡等好几层约束影响。下面按排查顺序排一下,从最快能确认的开始。
1. 先分清 df 和 du 为什么对不上
df -h 读的是文件系统的超级块统计,du -sh 是遍历目录累加文件大小。两者差得离谱时,八成不是「算错了」,而是文件还占着空间、但目录里已经找不到它了(见第 3 条)。另外 du 统计的是块分配,大量小文件会让它和「逻辑大小」差不少。
df -hT # 先看是哪个文件系统、什么类型
du -sh /var/* 2>/dev/null | sort -h | tail -20
du -x --max-depth=1 / # -x 不跨文件系统,避免把挂载点算进来
加 -x 这一步很关键:不加的话 du / 会顺着挂载点跑到数据盘甚至 NFS 上,得出的结论完全不可信。
2. df 没满,但 inode 用完了
每个文件至少占一个 inode,几十万个小文件(缓存碎片、session 文件、邮件队列、容器镜像层)就能把 inode 耗光,此时 df -h 显示还有空间,写入照样失败。
df -i # IUse% 到 100% 就是它
for d in /var /tmp /home; do echo "$d $(find $d -xdev -type f | wc -l)"; done
找到目录后别急着 rm -rf,先确认这些小文件的产生者,否则清完几小时又会满一遍。真要批量删,用 find ... -mtime +7 -delete 比 rm -rf * 安全得多——后者在文件数太多时会直接报参数列表过长。
3. 文件删了,空间没回来
这是「df 说满、du 说没占」最典型的原因:进程还开着某个文件的句柄,此时 rm 只是解除了目录引用,磁盘块要等最后一个句柄关闭才释放。日志文件被轮转后忘记让服务重新打开,就会撞上这个。
lsof +L1 # 列出所有链接数为 0 但被占用的文件
lsof | grep deleted
# 稳妥做法:截断而不是重启服务
: > /proc/<pid>/fd/<fd>
logrotate 配 copytruncate 或在 postrotate 里给进程发 USR1/HUP,就是为了避开这个坑。生产环境别一上来就重启服务,先截断再排期重启。
4. 挂载点把原有文件盖住了
某个目录里本来堆了几十 G 数据,后来把新盘挂到了同一个路径,老数据没删也看不见,空间却一直被占着。du 默认会跟着新挂载点走,所以怎么算都对不上。
findmnt -o TARGET,SOURCE,FSTYPE,SIZE,USED
mount | grep ' /data '
确认是遮挡后,可以用 mount --bind / /mnt/root 从另一个视角看被遮住的原始目录,处理完记得 umount /mnt/root。这种问题在临时盘改挂永久盘时特别常见。
5. 容器场景:日志和 overlay2
Docker 默认的 json-file 日志驱动没有大小上限,一个刷异常日志的容器几天就能吃满 /var/lib/docker。另外 docker system df 和宿主机 df 统计口径不同,镜像层、悬空卷都要单独看。
docker system df -v
du -sh /var/lib/docker/containers/*/*-json.log | sort -h | tail
# daemon.json 全局限制,改后需重启 docker,新建容器才生效
{ "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" } }
清理顺序建议:先 docker system prune 清悬空对象,再单独确认匿名卷;prune -a 会删掉所有未被使用的镜像,生产机上要慎用。
6. 配额和保留块
ext4 默认给 root 保留 5% 的块,普通用户写到 95% 就会收到「无空间」,但 df 看着还有余量。云主机上还可能有项目配额、目录配额:
tune2fs -l /dev/vda1 | grep -i 'reserved block'
repquota -a # 用户/组配额
xfs_quota -x -c 'report -h' /data # XFS 用这条
数据盘确认不需要 root 保留时,tune2fs -m 1 /dev/vdb1 能立刻腾出几个百分点;系统盘别乱改,那是给日志和救援留的。
小结:一条能直接照着跑的顺序
df -hT 定位文件系统 → df -i 排除 inode → du -x --max-depth=1 找大目录 → lsof +L1 查已删未释放 → findmnt 排除挂载遮挡 → 容器机再看 docker system df -v 和日志大小 → 最后查配额与保留块。按这个顺序走,绝大多数「还有空间却写不进去」都能在五分钟内定位,不用一上来就扩容——扩容解决不了 inode 用尽,也解决不了被占用的句柄。
- 点赞
- 收藏
- 关注作者
评论(0)