Linux 磁盘满了怎么排查:df 与 du 不一致、inode 耗尽、空间不释放

一、结论先给

"磁盘满了"分三种完全不同的病,用错药方会白折腾:

现象 判断命令 病名
df -h 100%,du -sh 加不出这么多 lsof | grep deleted 已删除文件被进程占用
df -h 还有空间,但报 "No space left on device" df -i inode 耗尽(小文件太多)
df -h 满且 du 也对得上 du -h --max-depth=1 | sort -hr 真的有大文件,删或扩

第一条命令永远是先分清是哪一种:

df -h && echo "--- inode ---" && df -i

👉 事后要做容量规划,用 磁盘容量计算器 按码率/天数/路数直接算需要多大盘(监控录像场景尤其好用)。

二、标准排查流程(五分钟定位)

2.1 第一步:确认是哪个挂载点满

df -hT

输出看三列:Use%、Avail、Mounted on。注意 /dev/shm、tmpfs 也算,有些程序写临时文件到 /dev/shm 会把内存占满。

一个易忽略的点:df 显示的 Avail 已经扣掉了 ext4 给 root 保留的 5%。所以看到 Use% 100% 但 Avail 还有几个 G,那是"普通用户满了,root 还能写",服务进程通常已经写不进去了。

2.2 第二步:找出占空间的大目录

# 只看一级子目录,快速定位
du -h --max-depth=1 / 2>/dev/null | sort -hr | head -15

# 定位到某个目录后再往下钻
du -h --max-depth=1 /var 2>/dev/null | sort -hr | head -10

# 直接找超过 1G 的文件
find / -type f -size +1G -exec ls -lh {} \; 2>/dev/null | awk '{print $5, $9}' | sort -hr | head -20

du 扫根目录可能很慢(几十万文件时几分钟起步)。更快的交互式替代品:

ncdu /        # 需要先装:yum install -y ncdu / apt install -y ncdu

ncdu 支持方向键进目录、d 直接删,生产环境排障效率比 du 高一个量级。

2.3 第三步:按嫌疑顺序查四大元凶

du -sh /var/log 2>/dev/null      # 应用日志
du -sh /var/cache 2>/dev/null    # 包管理器缓存
du -sh /var/lib/docker 2>/dev/null   # Docker
du -sh /tmp /var/tmp 2>/dev/null     # 临时文件

三、df 与 du 差几十个 G:已删除文件被占用

原理:Linux 里 rm 只是删掉目录项(链接)。如果某个进程还打开着这个文件,它的 inode 和数据块就不会释放,空间占着但你看不到文件名。

# 查看被删除但仍在被占用的文件(关键列: SIZE 和 COMMAND)
lsof | grep deleted

# 更精确,只看真正被删的
lsof +L1

# 按占用大小排序,看谁是元凶
lsof | grep deleted | awk '{print $7, $9, $1, $2}' | sort -rn | head -20

输出类似:

nginx     1234  root  5w  REG  253,1  21474836480  0  /var/log/nginx/access.log (deleted)

意思是 nginx(PID 1234)正握着一个 20GB 的已删除日志。

3.1 三种释放方式

方式 命令 适用场景
优雅重启服务(推荐) systemctl restart nginx 有维护窗口时
重新加载(不中断服务) nginx -s reload、kill -HUP <pid> 支持 reload 的服务
直接截断文件(不停机) : > /proc/1234/fd/5 不能重启时

截断的写法要精确:

# 把该 fd 指向的文件清空(注意是 fd 号 5,不是文件名)
: > /proc/1234/fd/5

# 或者用 truncate
truncate -s 0 /proc/1234/fd/5

3.2 根因与预防

这种情况 99% 是因为有人用 rm 删了正在写的日志,而不是用 truncate 清空,或者日志轮转没让进程重新打开文件。

正确做法:

# ✅ 清空正在写的日志:用截断,inode 不变,进程继续写
: > /var/log/nginx/access.log
# 或
truncate -s 0 /var/log/nginx/access.log

# ❌ 别用 rm 删正在写的日志
rm -f /var/log/nginx/access.log    # 空间不释放,服务还会往已删的 inode 里写

配好 logrotate 并使用 copytruncate 或 postrotate 通知进程重开文件:

# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    create 0640 nginx nginx
    sharedscripts
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
    endscript
}

四、inode 耗尽:空间还有,就是写不进去

现象:df -h 显示只用了 60%,但应用报 No space left on device,创建文件失败。

df -i

IUse% 到 100% 就是 inode 耗尽。每个文件至少占一个 inode,ext4 格式化时 inode 总数就固定了,后期加磁盘空间也没用(除非重做文件系统)。

4.1 定位哪个目录文件最多

# 统计各目录的 inode 使用数(靠文件计数近似)
for d in /*; do echo "$(find $d -xdev -type f 2>/dev/null | wc -l)  $d"; done | sort -rn | head

# 找文件数量最多的子目录(往下钻)
find /var -xdev -type f 2>/dev/null | awk -F/ '{print $1"/"$2"/"$3}' | sort | uniq -c | sort -rn | head

4.2 常见元凶

目录 成因
/var/spool/postfix/maildrop cron 任务输出没重定向,每次执行攒一封邮件
/var/spool/clientmqueue sendmail 队列堆积
PHP/Java session 目录 session 没清理,几百万个小文件
/usr/share/... 碎文件 某些程序产生海量小缓存

cron 产出邮件是最容易反复踩的:

# 查看是不是这个
ls /var/spool/postfix/maildrop | wc -l

# 根治:crontab 每条任务都重定向输出
0 2 * * * /opt/backup.sh >/dev/null 2>&1

# 或在 crontab 顶部设置
MAILTO=""

清理(先确认这些邮件没用):

find /var/spool/postfix/maildrop -type f -delete
find /var/spool/clientmqueue -type f -delete

⚠️ 文件量极大时 rm -rf 会卡死或报 Argument list too long,用 find -delete 或 xargs:

find /path -type f -name '*.tmp' -mtime +7 -print0 | xargs -0 -n 500 rm -f

五、四大元凶的标准清理手法

5.1 journal 日志(systemd)

# 看 journal 占了多少
journalctl --disk-usage

# 只保留最近 200MB
journalctl --vacuum-size=200M

# 只保留最近 7 天
journalctl --vacuum-time=7d

# 永久限制(改完重启 systemd-journald)
sed -i 's/#\?SystemMaxUse=.*/SystemMaxUse=500M/' /etc/systemd/journald.conf
systemctl restart systemd-journald

5.2 应用日志

# 找 7 天前且已压缩的旧日志
find /var/log -type f -name "*.gz" -mtime +30 -delete
find /var/log -type f -name "*.log.[0-9]*" -mtime +14 -delete

# 清空当前日志(用截断,别用 rm)
for f in /var/log/nginx/*.log; do : > "$f"; done

5.3 Docker

docker system df                          # 先看分布
docker system prune -a --volumes          # 清理无用镜像/容器/网络/卷(⚠️ volumes 会删数据卷)

# 更常用:只清镜像和构建缓存,保留数据卷
docker system prune -a

# 单个容器日志已经很大时,直接截断(路径用真实容器 id)
truncate -s 0 /var/lib/docker/containers/<id>/<id>-json.log

Docker 日志默认无上限,跑几个月轻松到几十 G。根治要在 /etc/docker/daemon.json 配置轮转:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}
systemctl reload docker     # 或 systemctl restart docker(会重启所有容器,注意)

⚠️ 这个配置只对新建容器生效,已有容器要重建。

5.4 MySQL binlog

-- 看 binlog 占了多少、保留多久
SHOW VARIABLES LIKE 'expire_logs_days';
SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';

-- 立刻清理 3 天前的
PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 3 DAY);

-- 永久设置(MySQL 8 用秒)
SET GLOBAL binlog_expire_logs_seconds = 259200;   -- 3 天

配置文件里也改一下,避免重启失效:

[mysqld]
binlog_expire_logs_seconds = 259200

六、ext4 保留空间:白扔的 5%

ext4 默认给 root 保留 5% 空间,防止普通用户写满后系统起不来。这对系统盘有意义,对数据盘是纯浪费(4T 盘白扔 200G)。

# 查看
tune2fs -l /dev/vdb | grep -i "reserved block"

# 数据盘改成 1%(系统盘保持 5%)
tune2fs -m 1 /dev/vdb

# 改成 0(纯数据盘,谨慎)
tune2fs -m 0 /dev/vdb

⚠️ 只对 ext2/3/4 有效,XFS 没有保留空间概念(XFS 有自己的预留机制)。

七、LVM 在线扩容(云盘扩了容量之后)

云控制台把盘从 100G 扩到 500G 之后,系统里还要两步才能用上:

# 1. 让内核重新识别磁盘容量
growpart /dev/vda 1                      # 扩展分区(无分区可跳过)
pvresize /dev/vda1                       # LVM 场景:扩展 PV

# 2. 扩展 LV 和文件系统(-r 会自动调文件系统,一条命令搞定)
lvextend -r -l +100%FREE /dev/mapper/centos-root

# 非 LVM,ext4:
resize2fs /dev/vdb1
# 非 LVM,XFS(注意参数是挂载点不是设备):
xfs_growfs /data

XFS 只能扩不能缩,且 xfs_growfs 后面跟的是挂载点。这个记错会直接报错。

八、日志分析:从几万行里找出错误点

磁盘满往往伴随应用疯狂报错,日志暴涨。人工翻不现实:

# 按小时统计日志行数,看什么时候突然爆发
awk '{print substr($4,2,14)}' /var/log/nginx/access.log | cut -d: -f1-2 | sort | uniq -c | head -24

# 统计 ERROR 级别数量与最近一次
grep -c "ERROR" /var/log/app/app.log
grep "ERROR" /var/log/app/app.log | tail -5

# 抓异常堆栈 Top
grep -A5 "Exception" /var/log/app/app.log | grep -oE "[a-zA-Z0-9.]+Exception" | sort | uniq -c | sort -rn | head

👉 也可以直接把日志段粘进 日志分析工具:浏览器内解析,输出级别分布、规则命中的具体行号、异常 Top、错误高发时段和结论,日志不上传服务器。日常命令记不住查 Linux 命令速查。

九、常见误区

  1. 用 rm 删正在写的日志:空间不释放,还会让服务继续往已删 inode 写。truncate 才是正解。
  2. 只看 df -h 不看 df -i:inode 满时 df -h 完全正常,会误导排查方向。
  3. rm -rf /var/log/*:删掉的是目录项,正在写的日志文件句柄还在,且可能破坏 logrotate 状态。
  4. 第一次用 ncdu 就按 d 删:先确认目录内容,误删无可挽回。
  5. 磁盘满才想起来清理:应该配监控(>80% 告警)+ logrotate + Docker 轮转三件套。
  6. 扩容不做 growpart:云盘扩完了,df 还是原大小,以为扩容失败。

十、几条纪律

  1. 磁盘使用率 80% 告警、90% 严重告警,别等 100% 才处理。
  2. 所有会持续增长的东西(日志、binlog、Docker 日志、备份)必须配轮转或保留策略,没有策略就是定时炸弹。
  3. 清空日志用 truncate/: >,删除旧日志用 find -mtime -delete。
  4. 清理前先确认:这个文件是谁在写、删了会不会影响业务、有没有备份。
  5. 生产环境删除操作,先 -print 看清单,确认后再 -delete,不要一步到位。
  6. 容量规划前置:按"日增量 × 保留天数 × 1.3"预留。

十一、延伸阅读

👉 相关在线工具:磁盘容量计算器 · Linux 命令速查 · 日志分析,免登录、纯前端。

还有 65 个免费在线工具

纯前端实现,不用注册,数据不上传服务器。

浏览全部工具