一、结论先给
"磁盘满了"分三种完全不同的病,用错药方会白折腾:
| 现象 | 判断命令 | 病名 |
|---|---|---|
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 命令速查。
九、常见误区
- 用
rm删正在写的日志:空间不释放,还会让服务继续往已删 inode 写。truncate才是正解。 - 只看
df -h不看df -i:inode 满时df -h完全正常,会误导排查方向。 rm -rf /var/log/*:删掉的是目录项,正在写的日志文件句柄还在,且可能破坏 logrotate 状态。- 第一次用
ncdu就按d删:先确认目录内容,误删无可挽回。 - 磁盘满才想起来清理:应该配监控(>80% 告警)+ logrotate + Docker 轮转三件套。
- 扩容不做
growpart:云盘扩完了,df还是原大小,以为扩容失败。
十、几条纪律
- 磁盘使用率 80% 告警、90% 严重告警,别等 100% 才处理。
- 所有会持续增长的东西(日志、binlog、Docker 日志、备份)必须配轮转或保留策略,没有策略就是定时炸弹。
- 清空日志用
truncate/: >,删除旧日志用find -mtime -delete。 - 清理前先确认:这个文件是谁在写、删了会不会影响业务、有没有备份。
- 生产环境删除操作,先
-print看清单,确认后再-delete,不要一步到位。 - 容量规划前置:按"日增量 × 保留天数 × 1.3"预留。
十一、延伸阅读
👉 相关在线工具:磁盘容量计算器 · Linux 命令速查 · 日志分析,免登录、纯前端。