Linux服务器磁盘空间占满的排查和清理实操详细记录

测试环境说明

服务器:LuckVM 香港 CN2 节点(1 核 1G / 70GB NVMe) 系统:Debian 12 相关软件:Nginx 1.22、MySQL 8.0、Docker 24.0

前两天站点后台一直报 MySQL 写入失败,Nginx 也开始返回 500,登录服务器一查才发现磁盘直接 100% 占满,连日志都写不进去了。这种问题排查起来其实不难,但新手最容易踩的坑是「明明 rm 删了文件,df 看磁盘还是满的」,这篇文章就把完整的排查和清理流程记下来,从定位到根治一步步写清楚。


一、第一步:先确认到底是哪块盘满了

不要上来就乱删文件,先用 df 看清楚是哪块分区满了、还剩多少:

df -h

输出重点看 Use% 那一列,比如:

Filesystem Size Used Avail Use% Mounted on
/dev/vda1 70G 68G 1.2G 100% /

如果根分区是 100%,那基本就是系统盘被写满了。

⚠️ 一个经常被忽略的点:磁盘没满但服务写不了文件,很可能是 inode 用完了。inode 记录文件名和元数据,小文件(比如大量 session 文件、缓存碎文件)堆积会先把 inode 占满,空间却看着还有。所以还要补一条:

df -i

如果 IUse% 到了 100%,那就是小文件太多,处理思路和大文件占满完全不同(后面第六节会讲)。


二、第二步:用 du 逐层定位到底是哪个目录大

确定是根分区满了之后,从根目录逐层往下找,一层一层缩小范围:

# 看根目录下每个一级目录的大小,从大到小排序取前20个
du -h --max-depth=1 / 2>/dev/null | sort -rh | head -20

找到最大的那个目录(比如 /var),再继续往下一层挖:

du -h --max-depth=1 /var 2>/dev/null | sort -rh | head -20
du -h --max-depth=1 /var/log 2>/dev/null | sort -rh | head -20

这样几层下来,基本就能锁定是哪个目录在吃盘。我用这套方法很快就定位到了 /var/log 和 /var/lib/docker 两个目录,占了 50 多个 G。


三、第三步:用 ncdu 可视化找大文件(最直观)

如果不想一层层敲 du,直接装个 ncdu,交互式界面,按目录大小排序,方向键就能翻,找大文件特别快:

apt update && apt install -y ncdu
ncdu /

进去之后按 d 可以直接删除选中项,比手动敲 rm 安全(会二次确认)。⚠️ ncdu 扫整个根目录会有点慢,磁盘 I/O 吃紧时先扫 /var 这种重点目录:

ncdu /var


四、第四步:用 find 精确定位超过指定大小的大文件

如果你只想揪出「单个大文件」,用 find 按体积过滤最快:

# 找出全盘大于 500M 的文件,按大小列出
find / -type f -size +500M -exec ls -lh {} \; 2>/dev/null | sort -k5 -rh

排查大文件时记得排除 /proc、/sys 这类虚拟文件系统,不然会报一堆权限错误,上面加了 2>/dev/null 就是屏蔽这些报错。


五、大坑:文件 rm 删了,df 看磁盘还是满的

这是我这次踩的最大的一个坑。用 du 看目录明明没多大,du 加起来远小于 df 显示的已用空间,那多半是有进程还在持有已经被删除的文件。

原理是:Linux 里 rm 只是把文件的目录项删掉,如果还有进程(比如正在写日志的 Nginx、MySQL)持有这个文件的句柄,文件数据要等进程关闭句柄后才真正释放。这时候表现在磁盘一直满着,但你又找不到这个文件。

用 lsof 揪出来:

# 找出所有被删除但还被进程占用的文件
lsof -nP 2>/dev/null | grep '(deleted)'

输出里可以看到是哪个进程、占了多大的已删除文件。处理方式有两种:

  1. 最稳妥:重启持有该文件的进程,句柄一释放空间就回来了:

systemctl restart nginx
systemctl restart mysql

  1. 不想重启服务:直接清空该文件描述符(慎用,操作前务必确认 PID 和 fd 号没写错):

# 假设 lsof 显示 PID 是 1234,fd 是 5
truncate -s 0 /proc/1234/fd/5

我这次是 MySQL 的慢查询日志被 rm 之后没重启进程,磁盘一直不释放,重启完 MySQL 空间立刻回来了 20G。


六、常见的几个「吃盘大户」和对应清理方法

1. Nginx 访问日志和错误日志

访问量大的站点,access.log 几天就能涨到几个 G:

# 查看日志大小
ls -lh /var/log/nginx/

# 直接清空(比 rm 好,不会丢句柄)
truncate -s 0 /var/log/nginx/access.log
truncate -s 0 /var/log/nginx/error.log

⚠️ 对正在被 Nginx 写入的日志,用 truncate -s 0 而不是 rm,rm 之后不 reload 不会释放空间。

2. systemd journal 日志

systemd 的 journald 默认可能占用几百 MB 到几个 GB:

# 查看占用
journalctl --disk-usage

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

# 或限制总大小到 500M
journalctl --vacuum-size=500M

想长期限制,可以改配置文件(后面第七节)。

3. Docker 的镜像、容器日志

Docker 是隐性吃盘大户,镜像层、容器写出的日志都能堆很久:

# 看 Docker 各部分的磁盘占用
docker system df

# 清理无用镜像、停止的容器、构建缓存、未使用的网络
docker system prune -a

注意 docker system prune -a 会删掉所有未使用镜像,生产环境执行前先 docker images 确认一下别误删。

4. MySQL 的 binlog

MySQL 的二进制日志(binlog)是主从复制和恢复用的,如果没配过期清理,会一直累积:

# 进 MySQL 查看 binlog 列表
mysql -uroot -p

SHOW BINARY LOGS;
-- 清理指定时间之前的 binlog
PURGE BINARY LOGS BEFORE '2026-10-01 00:00:00';

MySQL 8.0.11 之后推荐直接在配置里设过期时间:

[mysqld]
binlog_expire_logs_seconds = 604800

5. 面板和备份残留

如果用了宝塔之类的面板,/www/backup、/www/wwwlogs 目录很容易忽视,里面堆着历史备份和站点日志,定期手动清一下:

du -h --max-depth=1 /www 2>/dev/null | sort -rh

6. /tmp 临时目录和下载残留

/tmp 里的临时文件、之前 wget/curl 下的大文件包,也经常被忘掉:

# 清理 7 天没动过的临时文件
find /tmp -type f -atime +7 -delete


七、从根上治理:配置日志轮转和容量限制

清完只是一时爽,不配好规则过几天又满。下面几个配置能一劳永逸。

1. 配 logrotate 自动切割 Nginx 日志

编辑 /etc/logrotate.d/nginx:

/var/log/nginx/*.log {
daily
rotate 7
missingok
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
endscript
}

含义是每天切割一次、保留 7 份、压缩旧日志,配合 kill -USR1 让 Nginx 重新打开日志文件,不会丢句柄。

2. 限制 journald 日志上限

编辑 /etc/systemd/journald.conf:

[Journal]
SystemMaxUse=500M
SystemMaxFileSize=100M
MaxRetentionSec=7day

改完重启:

systemctl restart systemd-journald

3. 限制 Docker 容器日志大小

这个一定要配,不然一个写日志的容器跑几天就能把磁盘顶满。编辑 /etc/docker/daemon.json:

{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}

改完重启 Docker(注意会影响运行中的容器,建议维护窗口操作):

systemctl restart docker

⚠️ 该限制只对新创建的容器生效,已经存在的容器需要重建才会应用。

4. 加一个磁盘告警脚本

再补一个定时检查,磁盘超过 85% 就发通知,别等满了才发现。简单写个脚本,配合 crontab 每小时跑一次:

#!/bin/bash
# /root/check_disk.sh
THRESHOLD=85
USED=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$USED" -ge "$THRESHOLD" ]; then
echo "磁盘使用率已达 ${USED}%,请及时清理" | mail -s "磁盘告警" your@email.com
fi

chmod +x /root/check_disk.sh
# 每小时检查一次
crontab -e
# 添加一行:
0 * * * * /root/check_disk.sh


八、踩坑总结

  1. 先 df 分清是空间满还是 inode 满,两者处理思路完全不同,别一上来就删大文件
  2. du 和 df 对不上,先查 lsof | grep deleted,八成是被删除但没释放句柄的文件,重启对应进程即可
  3. 正在被进程写入的日志用 truncate -s 0,别用 rm,rm 不 reload 不释放空间
  4. Docker 的日志上限一定要提前配,daemon.json 里加上 max-size,能省很多事
  5. MySQL binlog 记得设过期时间,尤其是开了主从或者频繁写入的库
  6. 清理只是应急,轮转和告警才是根治,logrotate + journald 限制 + Docker 日志限制 + 定时告警,配好之后基本不会再被磁盘问题半夜叫起来
  7. 动态扩容前先清日志,很多「磁盘不够用」其实清一轮日志就回来了,不急着加盘

这套流程我在自己的生产环境上跑过好几轮了,从定位到清理到根治,按顺序来基本都能解决。有更省事的清理脚本或者别的吃盘大户,欢迎在评论区交流。

posted @ 2026-11-03 15:35  前端已挂  阅读(2)  评论(0)    收藏  举报