服务器报警「磁盘空间不足 90%」的那一刻,你只有三件事要做:先看哪里满了,再找谁占的,最后安全地清理。本文用一台真实服务器演示完整的排查与清理流程:df宏观定位 →du逐层下钻 →find抓大文件 → journald/logrotate 管日志 →lsof揪出"删了却没释放"的隐藏文件 → Docker 占用的收尾,最后给出防止磁盘再满的预防措施。
排查总览:一张流程图记住全部套路
磁盘满了先别慌,按这个顺序走,90% 的问题十分钟内定位:
df -h # ① 宏观:哪块分区满了(Use% 接近 100%)
du -h --max-depth=1 / # ② 中观:哪个目录最大(逐层下钻)
find / -xdev -type f -size +1G # ③ 微观:直接抓出大文件
journalctl --disk-usage # ④ 别忘了 systemd 日志这个"隐形大户"
lsof +L1 | grep deleted # ⑤ 终极坑:删了但被进程占住,空间没释放核心心法:先查宏观再查微观,先定位再动手删。直接 rm -rf 全盘乱删,轻则误删业务数据,重则删掉正在被写入的日志导致服务异常。
宏观定位:df -h 看挂载点
第一步永远是 df。它看的是文件系统层面的占用——每个挂载点用了多少、还剩多少:

截图里这台服务器只有一个数据盘 /dev/vda2 挂在根 / 上,40G 用了 8.9G(24%),看起来很健康。实战中看到 Use% 到 90% 就该开始处理了——大多数应用在磁盘 100% 满时不是报错,而是直接崩或写坏数据。
几个读 df 输出时的要点:
- 一堆
tmpfs是内存文件系统,占的是内存不占磁盘,可以直接忽略。 Size是文件系统总容量,Avail是对普通用户可用的空间——注意Used + Avail往往小于Size,因为 ext4 会为 root 预留 5%(防止磁盘满时系统连日志都写不了),可用tune2fs -m 1 /dev/vda2把预留比例调到 1%。- 如果提示
df: /xxx: Permission denied,通常只是没权限访问某些目录,不影响整体判断。
逐层定位:du 黄金组合命令
df 只知道哪个分区满了,接下来用 du 找出分区里哪个目录最大。黄金组合是这条:
du -h --max-depth=1 / 2>/dev/null | sort -hr | head -8拆开看:
| 部分 | 作用 |
|---|---|
du -h --max-depth=1 | 只统计一级子目录,-h 人类可读 |
2>/dev/null | 丢弃权限不足的报错,保持输出干净 |
sort -hr | 按人类可读格式(1.2G > 800M)逆序排序 |
head -8 | 只看前 8 名 |
在这台真实服务器上的输出:

8.9G /
6.7G /var
2.1G /usr
114M /boot
33M /root
15M /tmp
6.1M /etc
1020K /run结论很明确:/var 占了全盘 75%,下一轮就该 du -h --max-depth=1 /var | sort -hr | head 继续下钻,直到定位到具体目录。大多数"莫名其妙满了"的案子,最后都指向 /var/log、/var/lib/docker、/var/cache 这三处。
配合 find 直接抓大文件
如果只想快速找到超过某个大小的大文件(不关心目录聚合),用 find 更直接:
# 找根目录下(不跨其他文件系统)所有超过 1G 的文件
find / -xdev -type f -size +1G -exec ls -lh {} \;
# 按大小排序看最靠前的 10 个
find / -xdev -type f -exec du -h {} + 2>/dev/null | sort -hr | head -10-xdev 很关键:让 find 停留在根所在的文件系统,不进入 /proc、/sys、其他挂载点,既快又不会把虚拟文件系统的大小算进来。
清理日志:journald 与 logrotate
日志是磁盘杀手排行榜的常驻冠军。Ubuntu 18.04+ 默认用 systemd 的 journald 收集日志,存在 /var/log/journal/,它默认可以占用所在文件系统最多 10% 的空间——40G 的系统盘就意味着最多 4G。
查看 journal 占用并立即清理
# 查看日志当前占用
journalctl --disk-usage
# 立即把日志总量压到 100M 以内(超过的部分删除)
journalctl --vacuum-size=100M
# 只保留最近 7 天的日志,按时间清理
journalctl --vacuum-time=7d
# 清掉所有已归档的旧日志(只留当前正在写的)
journalctl --vacuum-files=2三者的区别:按大小、按时间、按文件数量,按需取用。--vacuum-size=100M 是最常用的"一键瘦身"。
治本:在 journald.conf 里设上限
应急清理只是治标,更重要的是在 /etc/systemd/journald.conf 里设好上限,让它永远涨不上去:
# /etc/systemd/journald.conf
[Journal]
SystemMaxUse=200M # journal 总占用上限 200M
SystemKeepFree=1G # 至少为系统保留 1G 空闲
SystemMaxFileSize=50M # 单个日志文件上限
MaxRetentionSec=1month # 最多保留 1 个月改完重启 journald 生效:
sudo systemctl restart systemd-journald传统文本日志交给 logrotate
/var/log/*.log(Nginx、应用自写日志等)由 logrotate 管理,Ubuntu 默认每天通过 cron 触发一次。给某个服务配轮转,在 /etc/logrotate.d/ 下建个配置文件即可:
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
daily
rotate 30 # 保留 30 份
compress # 压缩旧日志
missingok
notifempty
create 0640 root adm
postrotate # 通知应用重开日志文件(以 Nginx 为例)
/bin/kill -USR1 `cat /var/run/nginx.pid 2>/dev/null` 2>/dev/null || true
endscript
}改完先用 debug 模式模拟一遍,零风险验证配置是否正确:
sudo logrotate -d /etc/logrotate.d/myapp # -d 只打印执行计划,不真正执行隐藏杀手:文件删了,空间却没释放
这是磁盘排查里最反直觉的一个坑:rm 删掉一个大文件后,df -h 显示占用一点没降。
原因:Linux 中文件被删除只是解除了目录项与 inode 的链接(unlink),但只要还有进程持有该文件的打开句柄,文件内容就还占着磁盘块。日志、数据库、缓存这类长期运行的程序最常中招。
用 lsof +L1 揪出"死而不僵"的文件
lsof 是"list open files"的缩写,+L1 表示只列出链接数小于 1(即已从目录中删除)的打开文件:
lsof +L1 | grep deleted下面这张截图是实测演示:先打开一个文件句柄、再把它 rm 掉,然后 lsof +L1 立刻抓到了它:

COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
sleep 23882 root 9w REG 253,2 10 0 654559 /tmp/deleted-demo.txt (deleted)看 NLINK 那列是 0、名字后面挂着 (deleted),这就是"已删除但空间未释放"的实锤。SIZE/OFF 列是它占的字节数——实战中这里出现几十 GB 很常见(比如被 Tomcat 打开的 catalina.out)。
释放空间的三条路
# ① 首选:重启占用它的服务,句柄自然释放
sudo systemctl restart nginx
# ② 确认进程用途后强杀(会中断业务,谨慎)
sudo kill -9 <PID>
# ③ 不重启也不杀进程:直接把句柄指向的文件清空
echo > /proc/<PID>/fd/<FD> # FD 是 lsof 输出第 4 列,如 9w 的 9方案③最优雅:文件内容立即归零、空间立即释放,进程还活着。日常里遇到"日志文件被删了但空间没降",基本就是日志轮转和应用没配合好(postrotate 漏了通知),重启一次服务即解决。
Docker 占用:一个被反复低估的大户
装了 Docker 的服务器,/var/lib/docker 动辄几十 G。别急着 rm -rf /var/lib/docker——那是把容器和镜像全毁了。先用 docker system df 看清占用构成:
docker system df
# TYPE TOTAL ACTIVE SIZE RECLAIMABLE
# Images 12 4 3.2GB 1.1GB (34%)
# Containers 5 2 12.3MB 5.6MB (45%)
# Local Volumes 3 1 800MB 500MB (62%)
# Build Cache 26 0 2.4GB 2.4GB (100%)再按需清理,全部都有 -a(清理未使用的全部)和 --filter(精确过滤)两种粒度:
# 清理已停止的容器
docker container prune
# 清理悬空镜像(无标签且未被引用的)
docker image prune
# 清理构建缓存(CI 反复构建后经常在这里藏几十 G)
docker builder prune --filter "until=24h"
# 一键清理以上全部 + 未使用的网络
docker system prune -af --volumes注意 --volumes 会连未使用的数据卷一起删——如果数据卷里是数据库数据,千万别带这个参数,建议先 docker volume ls -f dangling=true 确认里面没有要保留的东西。
预防:让磁盘永远满不了
清理完这一次,更重要的让问题不复发。三件套安排上:
1. Inode 也要看
磁盘有空间却创建不了文件?那是 inode 耗尽了。df -i 看 inode 使用率,小文件特别多的目录(如 /var/spool、缓存目录)最容易爆:
df -i
# 若 IUse% 接近 100%,用 du --inodes 逐层找小文件大户
du -h --inodes /var/* 2>/dev/null | sort -hr | head -52. 监控 + 阈值告警
最简单的方案,一条 cron 每天检查一次,超过阈值就发邮件(或接你自己的告警渠道):
#!/bin/bash
# /usr/local/bin/disk-check.sh
THRESHOLD=85
USE=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$USE" -ge "$THRESHOLD" ]; then
echo "磁盘使用率已达 ${USE}%,请及时清理" | mail -s "[告警] 磁盘 ${USE}%" root
fi# 配合 crontab -e,每天 8 点检查
0 8 * * * /usr/local/bin/disk-check.sh3. 日志上限从源头封死
把上文的 journald.conf 上限、logrotate 轮转规则在每台服务器初始化时就配好——日志和 Docker 缓存是最容易失控的两个变量,提前设上限,后面就不用三天两头救火。
小结
排查磁盘满,本质就四步:
df -h确定哪个分区满了;du -h --max-depth=1 | sort -hr | head逐层下钻到具体目录,find -size +1G抓大文件;journalctl --vacuum-size/ logrotate 管好日志,docker system prune管好容器缓存;lsof +L1揪出删了没释放的隐藏文件,重启服务或清空句柄释放空间。
把这套流程和预防措施配好,「磁盘报警」这个最经典的运维事故,从此不再让你半夜爬起来。
评论 (0)
暂无评论,快来抢沙发吧!