服务器磁盘满了怎么查:df / du / inode / 日志轮转的可执行排查顺序

服务器磁盘满了怎么查:df / du / inode / 日志轮转的可执行排查顺序

234
234
2026-10-05 / 0 评论 / 86 阅读 / 4278 字 / 正在检测是否收录...
这篇是服务器运维笔记,和 DNS 无关。站内的加密 DNS 服务在dns.23-4.cn。

小服务器的根分区通常很小。我手上这台香港的小机器就是 1.2 GB 的根分区,日常只剩 300 多 MB。这种机器上「磁盘满」不是小概率事件,而是迟早的事——而且它挂起来的样子往往不像磁盘问题:nginx 报 500、PHP 写不了 session、数据库锁不上,你却在查代码。

这篇是一份按顺序执行的排查清单,从「确认是磁盘」到「找到元凶」再到「防止复发」。

一、先确认是不是磁盘,别急着查别的

df -h

看三件事:

  1. 哪个挂载点是满的(Use% 接近或达到 100%);
  2. 是不是根分区满(/),而不是数据分区——根分区满会连带影响 /tmp、日志、session,表现最杂;
  3. Available 是不是真的接近 0。
# 记录一个基线,后面所有操作都拿它对比
df -h /

还有一个容易被忽略的:inode 用满。

df -i

inode 满了的表现和磁盘满了几乎一样(写不进去文件),但 df -h 会显示还有空间。大量小文件(session 文件、缓存碎文件、邮件队列)是典型原因。

二、找到是谁占的

不要一上来 du -sh /*,那会遍历整个文件系统、很慢,还可能把挂载的外部存储也扫进去。用 -x 限制在同一个文件系统:

# 第一层:看哪个顶层目录大
du -x -h --max-depth=1 / 2>/dev/null | sort -h

# 锁定嫌疑目录后,再往里钻一层
du -x -h --max-depth=1 /var 2>/dev/null | sort -h
du -x -h --max-depth=1 /var/log 2>/dev/null | sort -h

sort -h 让结果按人类可读单位排序,一眼就能看到最大的。

小服务器上最常见的四个元凶:

目录原因处理
/var/log日志没有轮转,或轮转配置没生效配 logrotate,或直接限制大小
/var/lib/nginx/tmp 或类似nginx 把大的上游响应缓冲到磁盘见下面第四节
/var/cache/apk(Alpine)包管理器的下载缓存apk cache clean
/var/lib/docker镜像和容器层堆积docker system prune(确认后再执行)

三、最阴的一种:文件被删了,空间没释放

这是排查里最容易卡住的情况:du 加起来明明只有 200 MB,df 却说用掉了 1 GB。

原因是有进程还持有已删除文件的句柄。文件在目录里已经看不见了,但 inode 没释放,空间就一直占着。

# 找出所有被删除但仍被占用的文件
lsof +L1 2>/dev/null | head -30

# 没有 lsof 时的替代做法
ls -l /proc/*/fd 2>/dev/null | grep deleted | head -30

输出里会看到类似 nginx 1234 ... 5u REG ... 500000000 /var/log/nginx/access.log (deleted)——一个 500 MB 的已删除日志。

处理方式只有两种:

  • 重启那个进程(释放句柄,空间立刻回来);
  • 或者不要重启,用 truncate 清空当前文件(如果进程还在往里写新文件的话)。

注意:rm 一个正在被写的日志文件不会释放空间,只是把它变成「不可见的占用」。正确做法是 truncate -s 0 file.log 或让 logrotate 用 copytruncate。

四、nginx 把响应缓冲到了磁盘

nginx 的 error.log 里会出现这种警告:

[warn] an upstream response is buffered to a temporary file
/var/lib/nginx/tmp/fastcgi/1/98/0000002981 while reading upstream

这不是错误,但它说明上游(PHP-FPM)返回的响应超过了内存缓冲区,nginx 把它落盘了。如果上游返回很大的响应(比如一个几百 KB 的 HTML、或者一个大的 JSON 接口),这些临时文件会持续产生。

两个方向:

# 1. 让缓冲区能装下正常的响应(按你的实际响应大小调)
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;

# 2. 给临时目录设上限,避免无限增长
fastcgi_max_temp_file_size 64m;
fastcgi_temp_path /var/lib/nginx/tmp/fastcgi 1 2;

另外要确认临时目录会被清理。nginx 正常情况下会自己删,但如果进程被强杀过,残留文件会留在那里:

du -sh /var/lib/nginx/tmp/* 2>/dev/null
find /var/lib/nginx/tmp -type f -mtime +1 2>/dev/null | head

五、日志轮转:一次配好,长期省心

# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
    daily
    rotate 7
    maxsize 50M          # 单个文件超过 50M 就轮转,不等第二天
    missingok
    notifempty
    compress
    delaycompress
    copytruncate         # 关键:不重启进程也能释放空间
}

maxsize 和 copytruncate 这两行是关键:

  • maxsize 让「一天涨 2 GB」这种突发情况当天就被截住;
  • copytruncate 避免「轮转后进程还拿着旧句柄」的问题——也就是第三节那个坑。

Alpine 上 logrotate 不在默认安装里,要单独装:

apk add logrotate
# 确认 cron 里有这一行,否则 logrotate 根本不会跑
grep logrotate /etc/crontabs/root /etc/periodic/daily/* 2>/dev/null

最容易犯的错是配好了 logrotate 但没验证它真的执行过。 用 logrotate -d(dry-run)先看一遍:

logrotate -d /etc/logrotate.d/myapp

六、给根分区留出安全垫

在小分区上,与其等它满了再救,不如主动留垫:

  1. 把日志目录单独挂载(或软链到数据盘),别让日志吃掉根分区;
  2. 给关键服务设置自己的上限:journald 的 SystemMaxUse、docker 的 log-opts max-size、nginx 的 temp 上限;
  3. 加一条最简单的告警,超过 85% 就提醒:
#!/bin/sh
# /etc/periodic/daily/check-disk
THRESHOLD=85
USED=$(df / | awk 'NR==2 {gsub("%","",$5); print $5}')
if [ "$USED" -ge "$THRESHOLD" ]; then
    echo "root filesystem ${USED}% full on $(hostname)" | \
        mail -s "disk warning" you@example.com
fi
  1. 在改动前后都记一次 df -h /。这样你能明确知道「这次操作多占了多少」——尤其是往服务器上传东西的时候。

七、完整流程回顾

df -h                      # 1. 确认哪个分区满
df -i                      # 2. 顺便看 inode
du -x -h --max-depth=1 /   # 3. 找出大目录
lsof +L1                   # 4. 查「已删除但被占用」的文件
# 5. 处理:清缓存 / 轮转日志 / 重启持有句柄的进程
# 6. 配 maxsize + copytruncate,防止复发
# 7. 加一个 85% 的告警

顺序很重要:先量,再改。跳过第 3、4 步直接去删东西,很容易删掉不该删的,而真正的元凶还在那儿。


相关阅读

0个赞
取消