腾讯云服务器磁盘空间不足排查:日志、inode 与容器镜像
正文
磁盘告警弹出来的那一刻,很多人的第一反应是重启,第二反应是冲进 /var/log 删日志。我见过太多这样的现场:重启或删日志确实能让报警消失一阵子,可根因没找到,过几天又原样回来。腾讯云服务器磁盘空间不足这件事,难的从来不是清理动作,而是判断到底是谁把空间吃掉。 是数据量、是 inode、是没关掉的句柄,还是默默膨胀的容器镜像,处置方式完全不同。不管你是在官网自购,还是通过腾讯云代理开户,拿到机器后这套判断顺序都一样。
我的习惯是先把问题拆成三个维度来看:容量对不对得上,看 df 与 du;文件数量有没有爆掉,看 inode;有没有被进程占着、肉眼又看不见的文件,看句柄。这三条覆盖了我遇到过的绝大多数磁盘告警。很多人一上来就删文件,往往删了不该删的,真正的大头却站在原地没动。
磁盘告警先别重启:df 与 du 为什么对不上
df -h 看的是文件系统层面的容量,它直接读超级块,反映的是"这个挂载点还剩多少"。du -sh 是逐个目录遍历、把文件大小加起来,反映的是"这些文件一共多大"。两者统计口径不同,所以经常出现一个满、一个不满的迷惑现场。
最典型的两种偏差。一是已删除但句柄未释放的文件,du 已经看不到它,df 却还记着它的数据块。二是挂载点被覆盖,某个目录下其实挂了另一块云硬盘,du 沿目录树累加时会把不同设备的容量混在一起算。
排查习惯上,我一般先 df -h 找出使用率最高的挂载点,再 df -i 看 inode,然后 du -xhd1 / 逐层下钻。这里的 -x 表示不跨文件系统,能避免把挂载进来的数据盘、临时盘算进系统盘,很多"账算不平"的情况就是这么来的。云服务器 CVM(行业里常叫 ECS)的系统盘和数据盘是分开的,CBS 云硬盘扩容后如果没做文件系统在线扩容,df 仍会显示旧容量,这也是常见坑。
还有几个细节会干扰判断。ext4 默认给 root 保留 5% 的空间,df -h 里那部分对普通用户不可用,看着没满却写不进;du 默认对硬链接只算一次,而 --apparent-size 算的是逻辑大小,跟实际占用的块数可能差很多;稀疏文件和带压缩的文件系统,也会让两个数字拉开距离。想看得更直观可以装 ncdu,交互式下钻比反复敲 du 省事得多。记住 df 读超级块、du 走目录树,口径不同就注定会有偏差,先接受偏差,再去解释它到底差在哪。
inode 耗尽:文件不算大,却一个字节都写不进去
这个问题的现象很有辨识度:df -h 看着还有空间,可新建文件、写日志、启动进程全都报 "No space left on device"。这时候要看 df -i,如果 IUse% 到了 100% 或非常接近,那就是 inode 耗尽。inode 是文件系统给每个文件和目录分配的索引节点,容量再大,节点总数也是有限的。
吃 inode 的通常是海量小文件:session 文件、缓存碎片、没清理的临时文件、邮件队列、大量小图片缩略图。定位可以先用 find / -xdev -type f | wc -l 看规模,再按目录统计文件数量找出"文件最多的目录"。清理时要注意,删除小文件比删除大文件慢得多,海量 inode 的目录建议先停写入、分批删,直接 rm -rf 怼上去可能把 IO 压满,连带把正常业务拖慢。inode 和容量是两把不同的尺子,只盯 `df -h` 永远查不出这个问题。
要根治得从产生源头下手:给 session 和缓存设置过期清理策略,把邮件队列、临时目录挂到数据盘并配定期清理任务,日志一律接入轮转而不是无限追加。ext4 的 inode 总数在格式化那一刻就定死了,后期调整非常麻烦,所以分区规划阶段就要给 inode 留足余量;xfs 是动态分配 inode,相对不容易耗尽,但单个目录里堆几十万个文件,同样会把 ls 和删除操作拖得很慢。清理时按目录批量删,配合 ionice 降低 IO 优先级,尽量避开业务高峰,能把对线上读写的影响压到最小。
删了文件空间不释放:句柄没关的典型现场
这是让最多人上头的一种:df -h 显示盘满,du -sh 却怎么也算不出对应的大文件,重启之后空间又"自己"回来了。原因通常是进程还持有已删除文件的句柄——你 rm 掉的只是目录项,文件的数据块要等所有打开的句柄都关闭之后才真正回收。日志进程、数据库、被覆盖的配置文件最常见。
定位命令是 lsof +L1 或 lsof | grep deleted,它会列出链接计数为 0 但仍在被打开的文件,能看到对应进程 PID 和文件大小。确认是哪个进程后,规范做法是重启该服务让它释放句柄;如果业务不能停,可以对句柄做截断,先确认对应的 fd 再操作。切忌只 `rm` 不处理句柄,那等于空间被一个看不见的幽灵文件一直占着。
我经手过的一个客户,日志轮转脚本只改了文件名,主进程却没收到重新打开日志的信号,磁盘就被这样一个看不见的文件顶了好几天,最后靠重启服务才降下来。
判断句柄是否真的还在,有个很直观的办法:ls -l /proc/进程PID/fd 会看到指向已删除文件的符号链接,名称后面带 "(deleted)" 字样,盯一会儿确认它不消失,就说明进程确实还攥着它不放。释放句柄的优先级记住即可:让服务自己重载大于截断句柄,截断句柄大于重启进程,能不动重启就别动。
日志失控与 logrotate:把增长关进笼子
日志是系统盘的头号消耗源。journalctl 托管的 systemd 日志、Nginx 的 access.log 和 error.log、应用自己写的日志,如果不做切割,单文件能涨到几十 GB。先量一下体量:du -ah /var/log | sort -rh | head,systemd 的日志用 journalctl --disk-usage 看总量。
处置分两层。第一层是即时回收:systemd 日志用 journalctl --vacuum-size=500M 或 --vacuum-time=7d;普通日志不要直接 rm,用 truncate -s 0 或 : > file 清空,保留 inode 和句柄,避免又踩上一节那个坑。第二层是根治:用 logrotate 做轮转。在 /etc/logrotate.d/ 下给服务写配置,典型参数是 daily、rotate 7、compress、missingok、notifempty,关键还要有 copytruncate,或者在 postrotate 里给进程发重新打开日志的信号,比如 kill -USR1。copytruncate 适合不方便发信号的场景,但它有拷贝窗口,极端情况下会丢一点日志;进程能发信号就优先用信号。轻量应用服务器 Lighthouse 这类入门机型系统盘偏小,日志策略更要提前配好。
配好之后别干等到第二天验证,先用 logrotate -d /etc/logrotate.d/你的服务 做一次 dry-run,看它这一轮会怎么切、会不会报权限错;真要立刻验证切一次,用 logrotate -f 强制触发。目录属主和 su 指令没配对,是轮转悄悄失败的头号原因,日志该涨还是涨,表面上看配置明明在。systemd 这边,日志上限写在 /etc/systemd/journald.conf 的 SystemMaxUse,配合 journalctl --vacuum-size 做手动回收。还要注意,daily 只是建议频率,真正的切割边界由 size、maxsize 这类条件决定,配了 daily 并不等于单文件永远不超标。
容器镜像与镜像层:悄悄吃掉系统盘的黑洞
在 CVM 上跑 Docker,/var/lib/docker 会随着镜像、容器可写层、构建缓存一起膨胀。很多人只 docker rm 删容器,镜像和缓存原封不动,盘自然越来越紧。先用 docker system df 看四类占用:镜像、容器、本地卷、构建缓存。
清理策略按保留诉求来:docker image prune 清悬空镜像,docker image prune -a 连同未被容器引用的镜像一起清,执行前要确认没有依赖旧镜像的容器;docker builder prune 清构建缓存,docker system prune 则是组合拳。镜像层是共享的,同一基础镜像的多个版本会共用底层,删除时按引用关系回收,所以"删了镜像但空间没怎么变"往往是因为还有容器或上层镜像在引用它。除了 Docker,还要留意旧 tag 的镜像、/var/lib/containerd,以及 k8s 场景下的 /var/lib/kubelet。容器环境的磁盘治理,重点是"层"和"缓存",而不是单个镜像文件。
真正稳妥的做法,是在规划阶段就把 Docker 的数据根目录挪到数据盘:改 /etc/docker/daemon.json 里的 data-root,或者把有状态数据用 volume、bind mount 落到 CBS 云硬盘上,别全堆在系统盘。清理时 docker volume prune 要格外小心,匿名卷里可能还存着数据库文件或用户上传内容,删了就是真丢数据;docker system prune -a --volumes 这种一把梭的命令,除非是纯构建机,否则别在生产上随手执行。养成定期看一眼 overlay2 目录大小的习惯,比等到报警再动手主动得多。
把上面几类占用放在一起对照,排查时能少走很多弯路:
排查维度 现象特征 关键命令 典型误判 处置动作
容量口径 df 满而 du 小 df -h 与 du -sh 以为有重复文件 核对挂载点与统计范围
inode 耗尽 df -h 未满但无法写入 df -i 只看容量不看节点 清理海量小文件
句柄未释放 df 高且 du 找不到 lsof +L1 反复 rm 无效 重启或截断持有进程
日志失控 单文件持续增长 du 与 journalctl 看用量 直接 rm 大文件 配置 logrotate 轮转
容器镜像层 docker 目录膨胀 docker system df 删容器不删镜像 清理悬空镜像与缓存
云盘容量 df 与云盘规格不符 控制台云硬盘监控 误判系统盘故障 完成文件系统在线扩容
FAQ
**Q:df -h 显示磁盘满,但 du -sh /* 加起来对不上,是什么原因?**
A:先怀疑三件事:删除后句柄未释放,用 lsof +L1 查;目录被其他挂载点覆盖,用 findmnt 或 mount 核对;以及 CBS 云硬盘扩容后文件系统没做在线扩容。这三类都会让容量口径对不上。
Q:`df -h` 还有空间却提示 No space left on device,怎么办?
A:看 df -i。如果 inode 使用率接近 100%,就是小文件过多导致的 inode 耗尽。按目录统计文件数量,优先清理 session、缓存、临时文件这类海量小文件。
Q:删了大文件,`df` 却不降,必须重启吗?
A:不一定。先用 lsof 找到持有句柄的进程,优先让服务重载或重启;不能停服时可以对 /proc/<pid>/fd/<n> 做截断。关键是把句柄释放掉,而不是反复 rm。
Q:logrotate 的 `copytruncate` 和 `postrotate` 该怎么选?
A:进程能响应信号就优先 postrotate,比如 kill -USR1,语义最干净、不丢日志;不方便发信号时才用 copytruncate,并接受可能的少量日志丢失。
Q:有没有办法防止磁盘再次被写满?
A:两条主线:一是让增长可控,日志统一走 logrotate 和 journald 上限,容器数据挪到数据盘;二是把监控打开,对系统盘使用率和 inode 使用率同时设告警,别只盯容量不盯节点。定期跑一次清理任务,比出
本文由不代表本站立场,转载联系作者并注明出处。
