Linux 磁盘空间排查与清理实战:从 df 到 journald 的完整救火流程
> 服务器报警「磁盘空间不足 90%」的那一刻,你只有三件事要做:**先看哪里满了,再找谁占的,最后安全地清理**。本文用一台真实服务器演示完整的排查与清理流程:`df` 宏观定位 → `du` 逐层下钻 → `find` 抓大文件 → journald/logrotate 管日志 → `lsof` 揪出"删了却没释放"的隐藏文件 → Docker 占用的收尾,最后给出防止磁盘再满的预防措施。## 排查总览:一张流程图记住全部套路磁盘满了先别慌,按这个顺序走,90% 的问题十分钟内定位:```bash 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`。它看的是**文件系统层面**的占用——每个挂载点用了多少、还剩多少:![宏观定位:df -h 查看各挂载点使用率](https://blog.astarry.cn/usr/uploads/2026/08/1690008909.png)截图里这台服务器只有一个数据盘 `/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` 找出分区里**哪个目录最大**。黄金组合是这条:```bash 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 名 |在这台真实服务器上的输出:![逐层定位:du 黄金组合命令找出大目录](https://blog.astarry.cn/usr/uploads/2026/08/2894157208.png)``` 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` 更直接:```bash # 找根目录下(不跨其他文件系统)所有超过 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 占用并立即清理```bash # 查看日志当前占用 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` 里设好上限,让它永远涨不上去:```properties # /etc/systemd/journald.conf [Journal] SystemMaxUse=200M # journal 总占用上限 200M SystemKeepFree=1G # 至少为系统保留 1G 空闲 SystemMaxFileSize=50M # 单个日志文件上限 MaxRetentionSec=1month # 最多保留 1 个月 ```改完重启 journald 生效:```bash sudo systemctl restart systemd-journald ```### 传统文本日志交给 logrotate`/var/log/*.log`(Nginx、应用自写日志等)由 logrotate 管理,Ubuntu 默认每天通过 cron 触发一次。给某个服务配轮转,在 `/etc/logrotate.d/` 下建个配置文件即可:```ini # /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 模式模拟一遍,零风险验证配置是否正确:```bash sudo logrotate -d /etc/logrotate.d/myapp # -d 只打印执行计划,不真正执行 ```## 隐藏杀手:文件删了,空间却没释放这是磁盘排查里最反直觉的一个坑:**`rm` 删掉一个大文件后,`df -h` 显示占用一点没降**。原因:Linux 中文件被删除只是解除了目录项与 inode 的链接(unlink),但只要还有进程持有该文件的打开句柄,文件内容就还占着磁盘块。日志、数据库、缓存这类长期运行的程序最常中招。### 用 lsof +L1 揪出"死而不僵"的文件`lsof` 是"list open files"的缩写,`+L1` 表示只列出**链接数小于 1**(即已从目录中删除)的打开文件:```bash lsof +L1 | grep deleted ```下面这张截图是实测演示:先打开一个文件句柄、再把它 `rm` 掉,然后 `lsof +L1` 立刻抓到了它:![隐藏杀手:lsof +L1 揪出已删除但未释放的文件](https://blog.astarry.cn/usr/uploads/2026/08/3460008154.png)``` 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`)。### 释放空间的三条路```bash # ① 首选:重启占用它的服务,句柄自然释放 sudo systemctl restart nginx# ② 确认进程用途后强杀(会中断业务,谨慎) sudo kill -9 # ③ 不重启也不杀进程:直接把句柄指向的文件清空 echo > /proc//fd/ # FD 是 lsof 输出第 4 列,如 9w 的 9 ```方案③最优雅:文件内容立即归零、空间立即释放,进程还活着。日常里遇到"日志文件被删了但空间没降",基本就是日志轮转和应用没配合好(`postrotate` 漏了通知),重启一次服务即解决。## Docker 占用:一个被反复低估的大户装了 Docker 的服务器,`/var/lib/docker` 动辄几十 G。别急着 `rm -rf /var/lib/docker`——那是把容器和镜像全毁了。先用 `docker system df` 看清占用构成:```bash 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`(精确过滤)两种粒度:```bash # 清理已停止的容器 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`、缓存目录)最容易爆:```bash df -i # 若 IUse% 接近 100%,用 du --inodes 逐层找小文件大户 du -h --inodes /var/* 2>/dev/null | sort -hr | head -5 ```### 2. 监控 + 阈值告警最简单的方案,一条 cron 每天检查一次,超过阈值就发邮件(或接你自己的告警渠道):```bash #!/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 ``````bash # 配合 crontab -e,每天 8 点检查 0 8 * * * /usr/local/bin/disk-check.sh ```### 3. 日志上限从源头封死把上文的 `journald.conf` 上限、`logrotate` 轮转规则在每台服务器初始化时就配好——**日志和 Docker 缓存是最容易失控的两个变量**,提前设上限,后面就不用三天两头救火。## 小结排查磁盘满,本质就四步:1. **`df -h`** 确定哪个分区满了; 2. **`du -h --max-depth=1 | sort -hr | head`** 逐层下钻到具体目录,`find -size +1G` 抓大文件; 3. **`journalctl --vacuum-size` / logrotate** 管好日志,`docker system prune` 管好容器缓存; 4. **`lsof +L1`** 揪出删了没释放的隐藏文件,重启服务或清空句柄释放空间。把这套流程和预防措施配好,「磁盘报警」这个最经典的运维事故,从此不再让你半夜爬起来。## 参考资料- [systemd journald.conf 官方手册](https://man7.org/linux/man-pages/man5/journald.conf.5.html) - [journalctl 官方手册](https://man7.org/linux/man-pages/man1/journalctl.1.html) - [lsof 官方手册](https://man7.org/linux/man-pages/man8/lsof.8.html) - [logrotate 官方手册](https://man7.org/linux/man-pages/man8/logrotate.8.html) - [docker system prune 官方文档](https://docs.docker.com/engine/reference/commandline/system_prune/) - [【运维救火】服务器磁盘满了?教你快速定位并清理(阿里云开发者社区)](https://developer.aliyun.com/article/1701991) - [如何处理 df 和 du 输出不一致的问题(阿里云官方帮助文档)](https://www.alibabacloud.com/help/zh/alinux/support/what-do-i-do-if-the-df-and-du-command-outputs-are-inconsistent) - [Linux 下文件删除后空间未释放的典型原因及解决方案(腾讯云)](https://cloud.tencent.com.cn/developer/article/2607224)