Linux 磁盘空间不足:df 和 du 排查实战
凌晨 2 点收到告警,服务挂了。SSH 上去一看,No space left on device。df 一看,根分区 100% 了。但 du 一算,明明只用了 20G,分区有 40G。坑在于:有已删除的文件还占着空间。
问题现象
df -h
# Filesystem Size Used Avail Use% Mounted on
# /dev/vda1 40G 40G 0 100% /
du -sh /*
# 加起来只有 20G
df 说用了 40G,du 说只用了 20G。差的 20G 去哪了?
原因分析
说白了,就是有文件被删除了,但进程还持有文件句柄。Linux 的机制是:文件被删除后,只要还有进程在用它,磁盘空间就不会释放。
坑在于:du 只统计当前存在的文件,不统计已删除但还被占用的文件。
排查过程
第一步:找出已删除但未释放的文件
# 查找被删除但还被占用的文件
lsof +L1
# 输出类似:
# COMMAND PID USER FD TYPE DEVICE SIZE/NODE NODE NAME
# java 12345 root 1w REG 253,1 21474836480 1234567 /var/log/app.log (deleted)
lsof +L1 查找所有已删除但还被占用的文件(links < 1)。输出显示 java 进程占用了一个 20G 的已删除文件。
第二步:确认文件大小
# 查看被删除文件的大小
ls -lh /proc/12345/fd/1
# 或者
ls -l /proc/12345/fd/ | grep deleted
# 输出:
# l-wx------ 1 root root 64 Jul 20 02:00 1 -> /var/log/app.log (deleted)
确认了,/var/log/app.log 被删除了,但 java 进程还持有它的句柄,占着 20G 空间。
第三步:释放空间
有两个方案:
方案一:重启进程(推荐)
# 重启 java 进程,释放文件句柄
systemctl restart java-app
重启后,已删除的文件句柄被释放,磁盘空间回来了。
方案二:不重启,清空文件内容
# 找到被删除文件的文件描述符
ls -l /proc/12345/fd/ | grep deleted
# 输出:1 -> /var/log/app.log (deleted)
# 清空文件内容(不删除文件,只清空)
> /proc/12345/fd/1
这个方法不需要重启进程,但坑在于:只能清空当前内容,进程继续写入还是会占用空间。
验证修复
# 重启后检查
df -h
# Filesystem Size Used Avail Use% Mounted on
# /dev/vda1 40G 20G 18G 53% /
# 确认没有已删除未释放的文件
lsof +L1
# 无输出
空间回来了,从 100% 降到 53%。
预防措施
1. 配置日志轮转
# /etc/logrotate.d/app
/var/log/app.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
copytruncate
}
copytruncate 会先复制再清空,避免进程持有已删除文件的句柄。
2. 监控磁盘使用率
# 添加 crontab 定期检查
*/5 * * * * df -h / | awk 'NR==2{print $5}' | sed 's/%//' | awk '$1>80{print "磁盘使用率超过80%: "$1"%"}' | mail -s "磁盘告警" admin@example.com
3. 清理大文件
# 找出大于 100M 的文件
find / -type f -size +100M -exec ls -lh {} \; 2>/dev/null
# 找出大于 7 天的日志文件
find /var/log -type f -name "*.log" -mtime +7 -exec ls -lh {} \;
踩坑总结
df和du结果不一致,通常是有已删除未释放的文件lsof +L1可以找出已删除但还被占用的文件- 重启进程可以释放文件句柄
- 配置日志轮转(logrotate)可以预防此类问题
- 定期监控磁盘使用率,设置告警阈值
这个问题的坑在于:df 说满了,du 说没满。理解了 Linux 的文件删除机制,排查就很清晰了。