Linux服务器磁盘空间告警的最佳应对策略

描述

问题背景

磁盘空间告警是 Linux 服务器最常见的报警之一。很多人的第一反应是登录服务器直接 rm -rf 找大文件删掉。但实际场景中,"磁盘满"往往有三种不同情况:

磁盘空间真的满了(df -h 显示 100%)

inode 耗尽了(df -h 显示还有空间,但系统报 No space left on device)

文件已删除但空间未释放(df 显示满,但 du 统计不到大文件)

不分清楚这三种情况就盲目删文件,可能删了半小时空间也没释放,或者删了不该删的文件导致业务异常。

本文按排查顺序逐一讲解 8 个关键命令,覆盖空间排查、inode 排查、已删文件句柄排查、IO 性能排查,最后给出生产环境清理的标准流程。

一、排查前的准备工作

在开始删除任何文件前,先做两件事:

 

# 记录当前磁盘状态,以便事后回溯
$ df -h > /tmp/disk_before_$(date +%s).txt
$ df -i >> /tmp/disk_before_$(date +%s).txt

# 确认挂载点对应的业务
$ mount | grep <挂载点>
$ ls -la <挂载点> | head

 

如果有多块数据盘(如 /data、/var/log 独立分区),先确认哪个分区满了,再针对性排查。

二、8 个命令逐一详解

命令 1:df -h —— 查看各挂载点使用率

 

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        40G   38G   16M  100% /
/dev/vdb1       200G  120G   80G   60% /data

 

关键检查点:

**Use% = 100%**:该分区空间已满,需要立即处理

**Use% = 90~99%**:预警范围,需要排查哪些目录在持续增长

关注 Avail 列而非 Used 列:Avail 是实际可用空间(非 root 用户可用),Used 可能包含预留块(默认 5%)

预留块含义:mkfs.ext4 默认保留 5% 给 root 用户,所以非 root 用户看到 Avail 会比预期少。数据盘可以用 tune2fs -m 1 /dev/vdb1 调低到 1%。

命令 2:df -i —— 检查 inode 是否耗尽

 

$ df -i
Filesystem      Inodes  IUsed   IFree IUse% Mounted on
/dev/vda1      256000 255900     100  100% /

 

典型陷阱:df -h 显示还有空间,但系统报 No space left on device,此时 df -i 很可能显示 IUsed = 100%。

inode 耗尽的原因是小文件太多——每个文件(不管大小 1 字节还是 1GB)都需要一个 inode。一个 40GB 的分区如果满了 255 万个小文件,inode 就会耗尽,实际空间可能只用了 10%。

快速定位小文件目录:

 

# 统计各目录的文件数量(递归深度 1)
$ for dir in /*/; do echo -n "$dir: "; find "$dir" -xdev -type f 2>/dev/null | wc -l; done
# -xdev 限制在同一文件系统,避免统计到挂载点内

# 更精确的方式——逐级排查
$ find / -xdev -type f | awk -F/ '{$NF=""; print $0}' | sort | uniq -c | sort -rn | head -10

 

命令 3:du -sh —— 逐层定位大目录

 

# 查看根目录下各一级目录的大小
$ du -h --max-depth=1 / | sort -rh | head -10
6.2G    /usr
4.1G    /var
2.8G    /opt
1.5G    /home
1.2G    /root
...

# 发现 /var 大后,再深入
$ du -h --max-depth=1 /var/ | sort -rh | head -10
3.5G    /var/log
500M    /var/lib
...

# 直到定位到具体目录
$ du -sh /var/log/nginx/
1.2G    /var/log/nginx/

 

du 递归统计所有子目录,大目录下逐层深入很快就能找到"罪魁祸首"。

常用变体:

 

# 只看当前目录下各子目录大小,不递归更深
$ du -sh */ .[!.]*/

# 用 time 限制范围(只看修改时间早于 N 天的文件)
# 统计 30 天前的日志总量
$ find /var/log -name "*.log" -mtime +30 -type f -exec du -ch {} + | tail -1

 

命令 4:lsof | grep deleted —— 查找已删除但未释放的文件

这是最容易被忽略但实际非常常见的场景。当一个文件被 rm 删除后,如果有进程仍然持有这个文件的句柄,磁盘空间不会立即释放。

 

# 检查所有已删除但仍有进程引用的文件
$ sudo lsof | grep deleted
COMMAND    PID   USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
java      3456   root   23w   REG  202,1 2147483648  12345 /var/log/app/access.log (deleted)
nginx     2321   www    5w    REG  202,1 1073741824  23456 /var/log/nginx/access.log (deleted)

 

关键信息解读:

SIZE/OFF 列:文件的实际大小(单位字节),这里是 2GB 和 1GB

NAME 列末尾的 (deleted) 标记说明文件已被删除但句柄未释放

FD(File Descriptor)列:23w 表示第 23 号文件描述符,打开方式为写入

解决方法(按推荐顺序):

安全方式——通知进程重载文件句柄(大多数日志框架支持):

 

$ kill -USR1      # Java / log4j / syslog-ng
$ nginx -s reopen      # Nginx
$ systemctl restart rsyslog

 

强制方式——如果进程不响应 USR1 信号或不是日志类文件:

 

# 确认进程是否可以重启
$ systemctl restart 

# 或直接 kill 进程(确认不影响业务后)
$ kill -9 

 

临时释放——清空文件内容而非删除文件,不依赖信号:

 

$ : > /proc//fd/

 

这不会关闭进程的文件描述符,而是将文件内容截断为 0,立即释放磁盘空间。适用于不能重启进程的生产环境。

为什么会出现这种情况:

最常见的场景是 logrotate。很多默认的 logrotate 配置使用 create 指令,流程是:mv access.log access.log.1 -> create access.log(新建空文件)。新文件有了新 inode,但旧文件的句柄仍然被进程持有,旧文件的磁盘空间一直占用直到进程重启。

正确的做法是 logrotate 配置 copytruncate:

 

/var/log/nginx/*.log {
    daily
    rotate 30
    copytruncate    # 复制内容后截断原文件,不改变 inode
    compress
    delaycompress
    missingok
    notifempty
}

 

命令 5:find 查找大文件

 

# 查找根目录下大于 100MB 的文件
$ find / -xdev -type f -size +100M -exec ls -lh {} ; 2>/dev/null | sort -k5 -rh | head -20

# 查找特定目录下大于 1GB 的文件
$ find /var/log -type f -size +1G -exec ls -lh {} ; 2>/dev/null

# 查找最近 7 天内没有修改过的大文件(适合清理历史归档)
$ find /data/archive -type f -size +500M -mtime +7 -exec ls -lh {} ; 2>/dev/null

 

参数解释:

-xdev:限制在同一文件系统内,不搜索挂载的其他分区

-size +100M:大于 100MB 的文件(单位:k/M/G)

-exec ls -lh {} ;:对每个结果执行 ls

sort -k5 -rh:按第 5 列(文件大小)反向排序

2>/dev/null:过滤权限不足的错误信息

命令 6:ncdu —— 交互式磁盘分析

ncdu(NCurses Disk Usage)是 du 的交互式替代品,对逐层排查大目录效率更高:

 

# 安装
$ apt install ncdu      # Debian/Ubuntu
$ yum install ncdu      # CentOS/RHEL

# 使用
$ ncdu /

 

操作方式:

方向键上下移动

Enter 进入目录

d 删除选中的文件/目录

q 退出

ncdu 扫描速度比 du 快(尤其在大目录下),而且可以实时看到各目录大小排序,不需要反复敲命令。

命令 7:iostat -xz —— 分析磁盘 IO 性能

当磁盘使用率没有 100% 但业务响应仍然很慢时,可能是 IO 性能达到上限而非空间不足:

 

$ iostat -xz 1 5
Linux 5.15.0-91-generic ... 08/15/2025  _x86_64_ (32 CPU)

Device   r/s     w/s    rkB/s    wkB/s  rrqm/s  wrqm/s  %rrqm  %wrqm r_await w_await aqu-sz  %util
vda    3200.0  4500.0 128000.0 360000.0    0.0     0.0   0.00   0.00   15.20  92.30  256.0  99.8

 

关键指标:

指标 含义 HDD 警戒线 SSD 警戒线
%util 磁盘忙碌百分比 > 80% 对 SSD 参考价值有限
r_await / w_await 读写平均延迟 > 20ms > 2ms
aqu-sz 平均队列长度 > 1 > N(并行度)
r/s + w/s IOPS 机械盘 ~200 取决于 SSD 型号

对 NVMe SSD 的说明:%util 对多队列设备(NVMe)不准确,可能显示 100% 但仍能处理更多 IO。更可靠的指标是 aqu-sz(平均队列长度)和 r_await / w_await(IO 延迟)。

命令 8:journalctl --disk-usage —— 检查 systemd 日志占用

systemd-journald 的日志在不加限制的情况下可能占用数 GB 磁盘空间:

 

# 查看 journal 日志占用的磁盘空间
$ journalctl --disk-usage
Archived and active journals use 3.8G.

# 清理 7 天前的日志
$ journalctl --vacuum-time=7d

# 限制 journal 最大体积为 500MB
$ journalctl --vacuum-size=500M

# 永久限制(编辑配置文件)
$ cat /etc/systemd/journald.conf
[Journal]
SystemMaxUse=500M
MaxFileSec=7day

 

修改配置后重启 systemd-journald:

 

$ systemctl restart systemd-journald

 

三、按场景组合使用 8 个命令

场景 A:空间占比高,du 能正常定位

执行顺序:df -h → du 逐级定位 → find 查找大文件 → ncdu 交互式确认

 

$ df -h                     # 定位满的分区
$ du -h --max-depth=1 /     # 逐级深入
$ find /path -type f -size +500M -exec ls -lh {} ;
$ ncdu /path                # 交互式确认

 

场景 B:df 显示满,但 du 统计不到大文件

执行顺序:df -h → sudo lsof | grep deleted → 确认后释放

 

$ sudo lsof | grep deleted | head -5
# 找到最大的(deleted)文件
$ sudo lsof | grep deleted | awk '{print $7, $NF, $2}' | sort -rn | head -10
# 对 PID 发送信号或重启服务

 

场景 C:No space left on device 但 df -h 有空间

执行顺序:df -i → find 统计文件数 → 清理小文件

 

$ df -i                      # 确认 inode 满
$ find /data -xdev -type f | wc -l              # 统计文件总数
$ find /data -xdev -type f -mtime +90 -delete   # 清理 90 天前的旧文件

 

场景 D:磁盘性能差但空间充足

执行顺序:iostat -xz → iotop -o → pidstat -d

 

$ iostat -xz 1 5             # 确认 %util 高、await 高
$ iotop -o                   # 定位 IO 密集进程
$ pidstat -d 1 5             # 按进程统计 IO

 

四、生产环境标准清理流程

不要在发现磁盘满时直接上手删文件,遵循以下流程:

 

记录现场 → 定位大目录 → 确认业务影响 → 备份 → 清理 → 验证

 

Step 1:记录现场

 

$ df -h > /tmp/disk_clean_$(date +%s).before
$ du -h --max-depth=1 /var/log | sort -rh > /tmp/disk_clean_$(date +%s).logdir

 

Step 2:定位并确认

 

# 找到大文件后确认其业务归属
$ ls -la /var/log/nginx/access.log.20250815.gz
# 与业务方确认是否可以清理

 

Step 3:备份后再清理

对于不确定的日志/数据文件,压缩备份到其他分区或存储后再清理:

 

# 备份到其他分区
$ gzip -c /var/log/nginx/access.log.20250815.gz > /backup/nginx/access.log.20250815.gz

# 确认备份成功后删除
$ rm /var/log/nginx/access.log.20250815.gz

 

Step 4:验证空间是否释放

 

$ df -h <挂载点>
$ df -i <挂载点>   # 如果之前 inode 也高的话

 

Step 5:配置长期策略

根据排查结果配置日志轮转或周期性清理:

 

# logrotate 示例:按大小轮转
/var/log/nginx/*.log {
    size 500M
    rotate 14
    copytruncate
    compress
    missingok
    notifempty
}

# crontab 定期清理任务
0 3 * * 0 find /data/tmp -type f -mtime +30 -delete

 

五、预防措施

日志轮转必须配置——没有 logrotate 的服务器迟早会磁盘满。检查是否存在未被 logrotate 覆盖的日志文件。

监控与告警:不要等到 100% 再告警,分三级告警:

Warning: 使用率 > 80%

Critical: 使用率 > 90%

Emergency: 使用率 > 95%

独立分区:将 /var/log、/data 等容易写满的目录独立分区,避免写满根分区导致系统无法正常启动。

定期巡检:每周自动扫描各分区使用率和大文件增长趋势。

六、注意事项

不要在根分区满时重启服务器——根分区没有剩余空间可能导致重启后部分服务无法正常启动。

%util 100% 不等于磁盘损坏——可能是正常的高负载,先确认 aqu-sz 和 await 是否异常。

df 和 du 的统计差异:正常情况下差异在 5% 以内。如果差异显著,优先检查 lsof | grep deleted。

挂载点覆盖:如果在非空目录上挂载新分区,原目录下的文件会被隐藏但仍占用空间。排查时注意 mount 输出。

**生产环境慎用 rm -rf**——可能误删正在写入的日志文件,导致句柄未释放反而无法释放空间。建议优先用 echo "" > file 或 truncate -s 0 file。

七、总结

磁盘满排查的核心路径:

先看 df -h 确认空间耗尽的分区和挂载点

再看 df -i 排除 inode 耗尽的情况

df 满但 du 找不到 → 执行 lsof | grep deleted

逐层 du 定位最大目录

find 和 ncdu 查找具体大文件

检查 journalctl 系统日志占用

配合 iostat 排除 IO 性能问题

磁盘满的最佳应对策略不是"发现满了再删",而是通过 logrotate、分区规划、监控预警建立一个防止磁盘写满的体系。磁盘满了之后的每一次删除,都应该有记录、有备份、有确认、有总结。

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分