云服务器磁盘写满实战:从 df 到 lsof 的八步定位法

举报
yd_238266496 发表于 2026/10/29 09:39:46 2026/10/29
【摘要】 线上机器磁盘写满,是运维里最容易让人手忙脚乱的一类故障:告警只说「使用率 100%」,数据库可能已经写不进 binlog,Nginx 也拒绝再写 access.log。这篇按我实际处理过的一次 40GiB 系统盘写满事故,整理出一套可以照着敲的八步定位法,命令都在 Ubuntu 22.04 上验证过。 先立一条原则:先看空间,再删东西清磁盘最大的风险不是删得慢,而是删错。所以我习惯先花两分钟...

线上机器磁盘写满,是运维里最容易让人手忙脚乱的一类故障:告警只说「使用率 100%」,数据库可能已经写不进 binlog,Nginx 也拒绝再写 access.log。这篇按我实际处理过的一次 40GiB 系统盘写满事故,整理出一套可以照着敲的八步定位法,命令都在 Ubuntu 22.04 上验证过。

先立一条原则:先看空间,再删东西

清磁盘最大的风险不是删得慢,而是删错。所以我习惯先花两分钟把「谁在占空间」问清楚,再动手。整个过程分三层:

  1. 文件系统层:df
  2. 目录层:du
  3. 进程层:lsof

三层都过一遍,基本不会误删。

第一步:df -hT 看清设备与文件系统类型

df -hT

输出里三个信息最关键:挂载点、容量、文件系统类型。比如:

Filesystem     Type      Size  Used Avail Use% Mounted on
/dev/vda1      ext4       40G   40G     0 100% /
tmpfs          tmpfs     909M     0  909M   0% /dev/shm
/dev/vdb1      ext4      100G   12G   83G  13% /data

重点看 Mounted on 是 / 的那一行。如果 /data 还很空而 / 满了,说明问题不在数据盘,而在系统盘上的日志、缓存或容器层——这时候清理思路完全不同,不要急着动数据盘。

顺带确认一下:文件系统类型是 xfs 还是 ext4,会影响到后面的 du 参数习惯和 inode 数量策略,记下来。

第二步:df -i 排除 inode 耗尽

这一步最容易被跳过,但坑最深。

df -i

如果看到:

Filesystem      Inodes  IUsed   IFree IUse% Mounted on
/dev/vda1      2621440 2621440      0  100% /

那就不是「文件太大」,而是「文件太多」。此时 du 会告诉你目录只有几百 MB,但新文件就是写不进去,报 No space left on device。这类问题在小文件密集的场景特别常见:

  • PHP 的 session 目录 /var/lib/php/sessions
  • 邮件队列 /var/spool/postfix/maildrop
  • 应用把每个请求写成一个小文件做缓存
  • 大量未轮转的按天日志

定位办法是找目录里的文件数量:

for d in /var /tmp /home /opt; do
  echo -n "$d: "; find "$d" -xdev -type f 2>/dev/null | wc -l
done

-xdev 很重要,它保证不跨文件系统,不会把挂载进来的数据盘也算进来。

第三步:du 分层下钻,定位到具体目录

确认是空间问题后,从根开始一层层往下钻。关键是两个参数:-x 不跨文件系统,--max-depth=1 只看一层。

du -xh --max-depth=1 / 2>/dev/null | sort -rh | head -15

找到最大的目录后,把它当新的根继续钻:

du -xh --max-depth=1 /var 2>/dev/null | sort -rh | head -15
du -xh --max-depth=1 /var/log 2>/dev/null | sort -rh | head -15

我那次事故钻到第三层就定位了:/var/lib/docker/overlay2 占了 31G。原因是一个调试容器把 stdout 重定向到了容器内的文件,日志既没轮转也没落到宿主机,全堆在镜像层里。

再补一个找大文件的快捷命令:

find / -xdev -type f -size +500M -printf '%s\t%p\n' 2>/dev/null | sort -rn | head

第四步:lsof 找「已删除但仍被占用」的文件

这是最经典的陷阱:du 显示总共只占 20G,df 却显示 40G 全满。原因是某个进程还持有已被删除文件的句柄,空间不会释放。

lsof -nP +L1 2>/dev/null | head -20

+L1 表示「链接数小于 1」,也就是文件在目录里已经不存在了,但还有进程打开着。输出里的 SIZE/OFF 列就是被占住的字节数。

如果系统里没装 lsof,用 /proc 也能查:

for p in /proc/[0-9]*; do
  ls -l "$p/fd" 2>/dev/null | grep -i deleted
done

处理办法只有一条:重启持有该文件的进程。rm 再多次都没用,因为 inode 还被引用着。

systemctl restart myapp
df -h /

注意一个常见误操作:直接 kill -9 进程。如果是数据库或消息队列,先确认能否安全重启,别为了回缩 2G 空间把服务搞坏。

第五步:容器场景单独算账

如果机器上跑了 Docker,du 看到的数字和你以为的往往不一致,因为 overlay2 里有大量硬链接,du 会重复统计也可能低估。用 Docker 自己的账本更准:

docker system df -v

常见三类占用:

  • 悬空镜像(<none> 标签):docker image prune -f
  • 停止的容器和它们的可写层:docker container prune -f
  • 未使用的 volume:docker volume prune -f,执行前务必确认没有容器依赖

我一般不会一上手就跑 docker system prune -a --volumes,因为它会删掉所有未运行容器引用的镜像和卷,回滚成本很高。先看 df -v 的具体清单,再针对性地清。

另外提醒一句:容器日志的默认驱动是 json-file,默认不限制大小。给 /etc/docker/daemon.json 加上限比事后清理有效得多:

{
  "log-driver": "json-file",
  "log-opts": { "max-size": "50m", "max-file": "3" }
}

改完 systemctl reload docker,只对新创建的容器生效,老容器要重建。

第六步:journald 与业务日志的限额

用 systemd 的系统,/var/log/journal 很容易悄悄涨到几个 G:

journalctl --disk-usage
journalctl --vacuum-size=200M

永久生效要改 /etc/systemd/journald.conf:

[Journal]
SystemMaxUse=500M
SystemMaxFileSize=50M

业务日志交给 logrotate,一个能直接用的模板:

/var/log/myapp/*.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
}

copytruncate 适合不支持重新打开日志文件的程序,代价是轮转瞬间可能丢几行。能 reopen 的程序尽量用 postrotate 发信号,更可靠。

第七步:按安全顺序清理

清理顺序我固定成这个,风险从低到高:

  1. 包管理器缓存:apt-get clean、journalctl --vacuum-size=200M
  2. 明确的旧日志、旧备份:确认文件名带日期且已上传到对象存储
  3. 容器悬空镜像、停止的容器
  4. 应用缓存目录:先停服务再删,避免半删状态
  5. 最后才考虑扩盘或迁移数据

每一步之后都重新 df -h / 看增量,确认这一步真的起作用。如果清理了 5G 但 df 几乎没变,说明还有被占用的已删除文件,回到第四步。

第八步:让它别再发生

故障处理完,补三件事:

第一,加监控。磁盘使用率 80% 告警、90% 严重告警,inode 使用率单独告警一条,很多人只配了前者。

第二,日志治理。确认 journald 限额、业务日志轮转、容器日志上限三处都配了,这三处覆盖了小规格云服务器上 90% 的「莫名涨满」。

第三,拆分磁盘。系统盘只放系统和日志,业务数据放到独立数据盘,挂到 /data 并写进 /etc/fstab(建议用 UUID 而不是 /dev/vdb1,避免设备名漂移)。这样即使日志失控,也只影响系统盘,不会连带业务数据一起出事。

踩坑清单

  • du 不跨设备统计,查根目录忘了 -x 会把数据盘算进来,白忙一场。
  • df 满了但 du 不大,八成是已删除仍被占用的文件,去查 lsof +L1。
  • 只看 df -h 不看 df -i,inode 耗尽会被误判成「空间没满啊」。
  • rm -rf 大目录时如果目录里文件上百万,删除本身就很慢,用 find ... -delete 分批更可控。
  • 清理 /tmp 前确认没有服务在用,很多程序把 socket 放在那里。
  • 扩盘之后别忘了 growpart 和 resize2fs,只扩了云盘容量不扩文件系统,df 完全不会变。

这套流程跑熟了,一次磁盘告警从发现到恢复大概十分钟。真正花时间的从来不是敲命令,而是忍住别在第一分钟就 rm -rf。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。