运维知识
悠悠
2026年9月9日

磁盘空间告警后的7个深坑:du和df对不上、inode耗尽、已删文件不释放怎么办

坑一:du和df对不上——已删除文件未释放空间

这是最经典的问题,也是新手最容易懵的场景。

df显示磁盘快满了,你用du -sh /*逐层排查,加起来远远不到df显示的数字。差的那部分空间去哪了?

根因:Linux删除文件时,如果有进程还在持有这个文件的文件描述符(fd),内核只会删除目录项(unlink),但不会释放实际的磁盘块。du按文件名统计,已删除的文件它看不到;df读的是文件系统的superblock,统计的是实际占用的block数。

排查命令

# 找出所有被删除但仍被进程持有的文件
lsof +L1

# 只看大于100M的
lsof +L1 | awk '$7 > 104857600 {print $1, $2, $7, $9}'

输出大概长这样:

COMMAND   PID      SIZE   NAME
java      12345    52428800000  /var/log/app/service.log (deleted)
nginx     6789     2147483648   /var/log/nginx/access.log (deleted)

解决方案

方案一:最干净的办法——重启持有文件的进程:

# 确认进程PID后重启
systemctl restart your-service

方案二:不能重启?可以清空文件内容而不是删除文件。对已删除但未释放的文件,通过/proc文件系统截断它:

# 找到进程PID和fd编号
ls -l /proc/12345/fd/ | grep deleted

# 截断文件释放空间(不会影响进程运行,进程后续写入从0开始)
: > /proc/12345/fd/77

方案三:生产环境写日志别用rm删,用truncate或重定向清空:

# 正确姿势——清空文件内容但保留文件
truncate -s 0 /var/log/app/service.log
# 或者
cat /dev/null > /var/log/app/service.log
# 或者更简洁
: > /var/log/app/service.log

踩坑记录:之前一个同事写了个crontab定时清日志的脚本,用的rm -f然后touch新建。结果日志框架(log4j)还持有旧文件的fd,疯狂往已删除的文件里写,磁盘空间根本不释放。改成copytruncate方式的logrotate之后问题消失。


坑二:inode耗尽——磁盘还有空间但无法创建文件

df -h显示还有大把空间,但写文件报No space left on device

第一次遇到这个问题的时候,我反复确认了三遍磁盘空间,以为系统出了bug。

排查命令

# 检查inode使用率
df -i

# 输出示例
Filesystem      Inodes   IUsed   IFree IUse% Mounted on
/dev/sda1      3276800 3276800       0  100% /

IUse% 100%,inode用完了。

根因:每个文件(包括目录、软链接)占用一个inode,inode总数在格式化文件系统时就固定了(ext4默认每16KB分配一个inode)。如果系统里有海量小文件——比如PHP的session文件、邮件队列、缓存临时文件——inode可能比磁盘空间先耗尽。

定位哪个目录吃掉了inode

# 统计各目录的文件数量(注意这个命令在文件多的时候会比较慢)
find / -xdev -type f | cut -d "/" -f 2-3 | sort | uniq -c | sort -rn | head -20

如果你大概知道是哪个分区,可以缩小范围:

# 统计/var下每个子目录的文件数
for dir in /var/*/; do echo "$(find "$dir" -xdev -type f | wc -l) $dir"; done | sort -rn | head -10

常见元凶

  • /var/spool/postfix/maildrop/ —— 系统cron任务产生大量未投递邮件
  • /tmp/ 下的session文件或缓存文件
  • /var/lib/php/sessions/ —— PHP session文件堆积
  • 某些应用按时间戳创建大量小文件的目录

解决方案

# 批量删除(文件太多rm会报参数过长,用find)
find /var/spool/postfix/maildrop/ -type f -delete

# 如果find也卡住,用ls配合xargs分批删
ls -f /var/spool/postfix/maildrop/ | xargs -n 1000 rm -f

预防措施

# 格式化时指定更多inode(适合存大量小文件的分区)
mkfs.ext4 -i 4096 /dev/sdb1   # 每4KB分配一个inode,是默认的4倍

# 监控inode使用率,加到你的监控系统里
df -i | awk 'NR>1 && $5+0 > 80 {print "inode告警: "$6" 使用率 "$5}'

坑三:日志撑爆磁盘——logrotate没生效

磁盘空间告警十有八九跟日志有关。但不少人配了logrotate,以为万事大吉了,直到磁盘还是满了。

常见翻车场景

1、logrotate配置写了但从没执行过:

# 检查logrotate最近有没有跑过
cat /var/lib/logrotate/status   # CentOS/RHEL
cat /var/lib/logrotate/logrotate.status   # 有些发行版路径不同

# 手动执行看看有没有报错
logrotate -d /etc/logrotate.d/your-app   # -d是debug模式,只显示会做什么,不实际执行
logrotate -vf /etc/logrotate.d/your-app  # -v详细输出 -f强制执行

2、应用日志不走logrotate:比如Java应用用log4j/logback自己管日志轮转,或者Docker容器的日志。

# Docker日志也会撑爆磁盘,查看容器日志大小
du -sh /var/lib/docker/containers/*/*-json.log | sort -rh | head -10

# 配置Docker日志轮转(/etc/docker/daemon.json)
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}
# 改完要 systemctl restart docker

3、logrotate用了create模式但应用还在写旧文件:

# 对不支持HUP信号重新打开日志的应用,用copytruncate
/var/log/app/*.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
    copytruncate    # 先复制再清空原文件,不需要应用配合
}

一个排查日志大文件的快捷组合

# 找出大于500M的文件
find / -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null | sort -k5 -rh | head -20

坑四:/boot分区满——内核版本堆积

这个坑相对冷门,但遇到了很烦。/boot分区通常就给500M-1G,几个内核版本就塞满了。系统更新或者装新内核直接失败。

排查

df -h /boot

# 查看已安装的内核
rpm -q kernel          # CentOS/RHEL
dpkg --list 'linux-image-*'  # Ubuntu/Debian

清理旧内核

# CentOS 7/RHEL 7 —— 保留最近2个内核
package-cleanup --oldkernels --count=2

# CentOS 8/RHEL 8+
dnf remove --oldinstallonly --setopt installonly_limit=2 kernel

# Ubuntu/Debian
apt autoremove --purge

注意:千万别手动rm/boot下的文件然后不更新grub,会导致系统起不来。

# 如果手贱删了,补救:
# CentOS/RHEL
grub2-mkconfig -o /boot/grub2/grub.cfg
# Ubuntu
update-grub

坑五:ext4预留空间——5%的隐形消耗

df显示磁盘用了95%就开始告警,你看了下容量,觉得还有十几G空间啊,怎么就告警了?

根因:ext4文件系统默认会预留5%的空间给root用户,防止磁盘100%满了之后普通用户还能写入导致系统完全无法操作。在大容量磁盘上,这5%相当可观——1TB磁盘就是50G。

查看和调整预留空间

# 查看预留块数
tune2fs -l /dev/sda1 | grep -i "reserved"
# Reserved block count:  xxxxx
# Reserved blocks uid:   0 (user root)

# 对数据盘(非系统盘),可以把预留比例调低甚至清零
tune2fs -m 1 /dev/sdb1    # 预留1%
tune2fs -m 0 /dev/sdb1    # 不预留(仅数据盘,系统盘建议至少保留1%)

# 改完立即生效,不需要重启或umount

XFS没有这个问题,XFS默认不预留空间。所以如果你看到ext4的df显示和你预期差了几十G,先查这个。


坑六:LVM扩容踩坑——扩了逻辑卷忘了扩文件系统

云服务器加了磁盘,或者虚拟机扩了盘,fdisk -l看到容量已经变大了,但df -h还是老样子。

这个问题经常出现在LVM环境下。扩容其实分三步:扩物理卷/物理磁盘 → 扩逻辑卷 → 扩文件系统。很多人做完前两步就以为搞定了。

完整的LVM在线扩容流程

# 1. 如果是新加磁盘,先创建PV
pvcreate /dev/sdb

# 把新PV加入VG
vgextend centos /dev/sdb

# 2. 如果是已有磁盘扩容(比如云盘),先让系统识别新容量
# 方法一:不重启识别新磁盘大小
echo 1 > /sys/block/sda/device/rescan

# 如果是分区(比如/dev/sda3扩容),还要用growpart扩分区
growpart /dev/sda 3

# 再扩PV
pvresize /dev/sda3

# 3. 扩逻辑卷
lvextend -l +100%FREE /dev/centos/root    # 用掉所有剩余空间
# 或者指定大小
lvextend -L +50G /dev/centos/root

# 4. 扩文件系统(关键!很多人忘了这步)

# ext4用resize2fs
resize2fs /dev/centos/root

# xfs用xfs_growfs
xfs_growfs /dev/centos/root

踩坑点

  • ext4和xfs扩容命令不一样,搞混了会报错。resize2fs只支持ext系列,xfs_growfs只支持XFS
  • XFS只能扩不能缩,别想着缩小XFS分区,它不支持
  • lvextend -r 参数可以自动扩文件系统,省一步:
# -r 自动识别文件系统类型并resize,推荐用这个
lvextend -l +100%FREE -r /dev/centos/root

怎么确认当前文件系统类型

# 方法一
df -Th

# 方法二
lsblk -f

# 方法三
blkid /dev/centos/root

坑七:tmpfs和overlay占用——df看到的不全是真实磁盘

有时候df -h列出一堆挂载点,有些其实根本不占磁盘空间(tmpfs在内存里),有些则是重叠挂载可能被重复统计。

Docker环境下尤其常见:

# 查看Docker总体磁盘占用
docker system df

# 详细信息
docker system df -v

# 清理所有未使用的镜像、容器、网络、构建缓存
docker system prune -a --volumes
# ⚠️ 谨慎使用,会删除所有停止的容器和未使用的镜像

K8s环境里的磁盘坑

# kubelet的imagefs和nodefs可能在同一个分区
# 查看kubelet的磁盘压力阈值
kubectl describe node <node-name> | grep -A5 "Conditions" | grep DiskPressure

# kubelet默认阈值:
# imagefs.available < 15% 开始驱逐Pod
# nodefs.available < 10% 开始驱逐Pod

# 清理kubelet不再使用的镜像
crictl rmi --prune

一个容易忽略的点——有些bind mount会让同一份数据在du统计时被计算两次:

# 用-x参数限制du不跨文件系统
du -shx /*

总结:磁盘告警排查速查表

现象排查命令根因解决方案
du和df对不上lsof +L1已删文件被进程持有重启进程或截断fd
有空间但创建文件失败df -iinode耗尽清理小文件,重格式化时调整inode
日志疯长find / -size +500Mlogrotate未生效检查logrotate配置和状态
/boot满df -h /boot旧内核堆积package-cleanup --oldkernels
磁盘95%但实际没那么满tune2fs -lext4预留5%tune2fs -m 1 调低预留
扩了盘但df不变lvs && pvs只扩了LV没扩文件系统lvextend -r 或手动resize
Docker/K8s磁盘暴涨docker system df镜像/容器/日志堆积docker system prune

最后几句

磁盘告警这事,说大不大说小不小。但如果排查思路不对,很容易在一个方向上死磕半小时,最后发现根因在另一个地方。

建议把df -hdf -i都加到你的日常巡检脚本里,inode问题被漏掉的概率很高。还有logrotate的状态也定期检查一下——别等到磁盘炸了才发现它半年没跑过。

我是「运维躬行录」,专注分享云计算和运维的生产实践经验。如果这篇文章对你有帮助,帮忙点个赞、转发一下。 关注公众号:耕云躬行录 个人博客:躬行笔记

文章目录

博主介绍

热爱技术的云计算运维工程师,Python全栈工程师,分享开发经验与生活感悟。
欢迎关注我的微信公众号@运维躬行录,领取海量学习资料

微信二维码