暗色模式

Linux 磁盘空间排查与清理实战:从 df 到 journald 的完整救火流程

技术教程
2026-08-04
24
0
服务器报警「磁盘空间不足 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。它看的是文件系统层面的占用——每个挂载点用了多少、还剩多少:

宏观定位:df -h 查看各挂载点使用率

截图里这台服务器只有一个数据盘 /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 名

在这台真实服务器上的输出:

逐层定位:du 黄金组合命令找出大目录

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 立刻抓到了它:

隐藏杀手: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 -5

2. 监控 + 阈值告警

最简单的方案,一条 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.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 揪出删了没释放的隐藏文件,重启服务或清空句柄释放空间。

把这套流程和预防措施配好,「磁盘报警」这个最经典的运维事故,从此不再让你半夜爬起来。

参考资料

发表评论

暂无评论,快来抢沙发吧!