磁盘空间爆满是运维最常见的线上故障之一。当日志、上传文件或数据库临时文件不受控增长,df -h 突然报警 No space left on device,应用会写不进日志、上传接口直接失败,严重时数据库因无法落盘而崩溃。本文从一次真实告警出发,讲清”磁盘满了”的标准排查链路与止血方法。
一、先分清两种”满”:block 耗尽 vs inode 耗尽
很多人一看到”磁盘满”就狂删文件,却怎么都腾不出空间,根因是没分清磁盘到底”满在哪”。Linux 文件系统有两个独立的配额维度:数据块(block)和索引节点(inode)。block 存的是文件内容,inode 存的是文件名、权限、时间戳等元数据。二者任一耗尽都会报”空间不足”,但排查手法完全不同。
| 维度 | 查看命令 | 典型症状 | 常见元凶 |
|---|---|---|---|
| block 耗尽 | df -h Use% 到 100% | 写文件报错 No space left on device | 大日志、Docker 镜像层、上传目录 |
| inode 耗尽 | df -i IUse% 到 100% | 删了文件仍满、小文件极多 | 海量碎片缓存、session 文件、邮件队列 |
如果 df -h 显示已用 100% 而 df -i 正常,那就是本文要解决的 block 爆满;若反过来 df -i 先满,则是 inode 耗尽,排查思路见《磁盘 inode 耗尽排查实录》。先确认维度,能省掉一半无效操作。
二、第一步:用 df 确认哪块盘满了
不要急着删,先用 df -h 看全局,定位是哪一块挂载点吃满了。线上服务器常见挂载点有 /、/var(日志)、/data(业务数据)、/home。重点看 Use% 列:
# 人类可读地查看各挂载点使用率
df -h
# 只看使用率超过 90% 的分区
df -h | awk 'NR==1 || $5+0 >= 90'
# 查看具体某个目录所在的文件系统
df -h /var/log
如果 df -h 已经 100%,但 du 统计的总量远小于分区大小,跳到第五节——多半是有文件被删除了但进程还握着句柄,空间没真正释放。
三、第二步:用 du 逐层定位”谁”最胖
锁定挂载点后,用 du 从根往下钻,找出最占空间的目录。加 -x 参数可避免跨文件系统统计(比如不算到 /proc、/mnt 里),结果更准:
# 统计根目录下各一级目录占用,按从大到小排序(只看本文件系统)
du -hx / 2>/dev/null | sort -rh | head -20
# 只看 /var 下最胖的 10 个目录
du -hx /var 2>/dev/null | sort -rh | head -10
# 交互式浏览(需安装 ncdu),比 du 直观得多
ncdu /
实战经验:du -h --max-depth=1 /var 这种一级一级往下看也很顺手。看到 /var/log 或 /var/lib/docker 异常大,基本就锁定元凶了。注意 sort -rh 依赖 GNU coreutils,macOS 上是 sort -rn。
四、爆满元凶清单(按出现频率)
排障几百次后,爆满原因高度集中。下面这张表按线上出现频率排序,排查时对照着看,能快速缩小范围:
| 排名 | 元凶 | 位置线索 | 处理方式 |
|---|---|---|---|
| 1 | 应用/服务日志未轮转 | /var/log/*.log 单文件几 GB | logrotate + 紧急 truncate |
| 2 | Docker 镜像层与容器日志 | /var/lib/docker | docker system prune |
| 3 | core dump 文件 | /var/crash、工作目录 core.* | 限制 ulimit、清理 |
| 4 | 上传/导出目录失控 | /data/uploads、临时导出 | 归档到对象存储 |
| 5 | 数据库临时文件/慢查询产物 | /var/lib/mysql、tmpdir | 优化慢查询、清 tmp |
| 6 | 残留备份与同步副本 | *.tar / *.bak 散落 | 移到独立备份盘 |
Nginx、PHP-FPM 这类常驻服务最容易被忽略的是访问日志——一次大促或爬虫来袭,access.log 一晚上能涨几十 GB。关于 Nginx 的部署与日志路径,可回顾《Nginx 反向代理实战》。
五、df 与 du 对不上的真相:被进程占用的已删除文件
这是最经典的”诡异现场”:df -h 说满,du 算出来却只有一半。原因是 Linux 里”删除”只是把目录项摘掉,只要还有进程打开着这个文件(握着文件描述符),磁盘块就不会释放。常见于:日志正在写入时你 rm 了它,或 Java/Python 进程仍持有已删文件的句柄。
# 找出所有"已删除但仍被占用"的文件(占着空间不释放)
lsof +L1 2>/dev/null | grep -i deleted
# 或更直接:看 SIZE/OFF 列很大的 deleted 项
lsof 2>/dev/null | grep deleted | awk '{print $1, $2, $7, $9}'
# 定位到具体进程后,重启或发 SIGHUP 让它释放
# 例如让 Nginx 重新打开日志文件(不中断服务)
kill -USR1 $(cat /var/run/nginx.pid)
找到占用进程后,最稳妥的做法是让进程重新打开日志(如 logrotate 的 postrotate 发 USR1),而不是直接 kill。重启进程会短暂中断服务,能用信号解决就别重启。
六、紧急止血三板斧
线上已报警、接口开始报错时,先止血保活,再谈根治。三板斧按”安全→有效”排序:
# ① 清空大日志(保留文件inode,服务无需重启,比 rm 安全)
truncate -s 0 /var/log/nginx/access.log
# ② 删除明确无用的文件(确认不再需要的旧包、core、备份)
rm -f /var/crash/core.* /tmp/2024*.tar
# ③ 临时挂载一块大云盘救急(阿里云/腾讯云控制台挂新盘后)
mkfs.ext4 /dev/vdb && mount /dev/vdb /data2
mv /var/lib/docker /data2/docker && ln -s /data2/docker /var/lib/docker
切记:正在被进程写入的日志不要用 rm 删,删了空间也不释放(见第五节);用 truncate -s 0 才是正解。Docker 场景下,把整个 /var/lib/docker 迁移到大盘再软链,是常用的”搬家”止血法。
七、根治:用 logrotate 让日志自己瘦身
止血只是救火,根治必须让日志自动轮转压缩。几乎所有 Linux 发行版都自带 logrotate,关键是给业务日志补一份配置:
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
daily # 每天轮转
rotate 7 # 保留 7 份
size 100M # 或单文件超 100M 即转
missingok # 文件不存在也不报错
notifempty # 空文件不转
compress # 轮转后 gzip 压缩
delaycompress # 延迟一轮再压,方便正在写的进程
copytruncate # 拷贝后清空原文件,进程无需重启
}
配置写好后务必先用调试模式跑一遍,确认不会误删线上日志:logrotate -d /etc/logrotate.d/myapp(dry run 只打印不执行);确认无误再去掉 -d 正式运行。Java、Go 应用若自己管日志,记得打开按大小滚动(如 Logback 的 SizeAndTimeBasedRollingPolicy),别把轮转全甩给操作系统。
八、Docker 场景专项:镜像层与悬空容器
容器环境里磁盘爆满八成来自 Docker 自己:一是容器日志(json-file 驱动不限制大小),二是堆积的悬空镜像与构建缓存。先看 Docker 把空间花在哪:
# 查看 Docker 各部分占用
docker system df
# 清理:悬空镜像、停止的容器、未使用的网络与构建缓存
docker system prune -f
# 连带删除所有未被容器引用的镜像(谨慎,会重新拉取)
docker system prune -a --volumes -f
# 限制容器日志大小(在 /etc/docker/daemon.json)
# {
# "log-driver": "json-file",
# "log-opts": { "max-size": "50m", "max-file": "3" }
# }
容器日志失控是重灾区,务必在 daemon.json 里给 max-size 上锁,否则一个疯狂打日志的容器几天就能写爆系统盘。编排层面可参考《Docker Compose 一键编排》里的卷与日志约定。
九、监控预警:别等满了才看
等 df 报警已经晚了,应当在使用率到 80% 时就预警。最简方案是写个 cron 脚本,超过阈值就发通知(邮件/Webhook/钉钉):
#!/bin/bash
# 磁盘使用率预警,超过 85% 触发
THRESHOLD=85
df -P -h | awk 'NR>1 {gsub("%",""); print $5, $6}' | while read use mount; do
if [ "$use" -ge "$THRESHOLD" ]; then
curl -s -X POST "$WEBHOOK" \
-d "{\"text\":\"⚠️ 磁盘告警 $mount 已用 ${use}%\"}" >/dev/null
fi
done
更完整的做法是接 Prometheus + node_exporter,用 node_filesystem_avail_bytes / node_filesystem_size_bytes 画趋势图,在”还有 3 天填满”之前就介入。趋势比快照重要——一条稳步上升的曲线,比某次突发的 100% 更有预警价值。
十、复盘检查清单
每次磁盘爆满处置完,按这份清单过一遍,能避免下次重蹈覆辙:
- 确认是 block 满还是 inode 满(
df -hvsdf -i)。 - 用
du -hx逐层定位最大目录,记录元凶类型。 - 查
lsof +L1看是否有已删除但被占用的文件。 - 止血优先用
truncate,避免误rm正在写的日志。 - 给对应日志补 logrotate 或应用内滚动配置并 dry-run 验证。
- Docker 环境配置
max-size并定期prune。 - 接入 80% 阈值的磁盘使用率预警。
磁盘空间爆满从不是”偶然事件”,而是监控缺失与日志管理粗放的必然结果。把上面七步做成常态化巡检,这类半夜告警会少一大半。




