VPS 磁盘空间不足排查:两台真实服务器,journald 各占 3.9G

VPS 磁盘空间不足,先看两组数字。两台 40G 系统盘,一台用了 28%,一台用了 89%,差了三倍多。可把 du 摊开,两台的 /var/log 分别是 4.8G 和 4.2G,其中 journald 各占 3.9G,在 cn-probe-1 上比整个系统本体(3.4G)还大。
样本是两台在跑的生产机,Ubuntu 24.04 LTS 和 CentOS 7,分别连续运行 103 天 18 小时 54 分、229 天 17 小时 39 分,负载 0.21/0.06/0.01 和 0.08/0.03/0.05。不是刚装好的空白机。全新安装的底噪我们没测,这篇不给那个数。
磁盘满了还是 inode 满了,先分清
VPS 磁盘空间不足时,两条命令一行结论:先看块,再看 inode。
df -hT
cn-probe-1 的根分区:
Filesystem Type Size Used Avail Use% Mounted on
/dev/sdb2 ext4 40G 11G 27G 28% /
/dev/sda1 ext4 30G 16G 13G 57% /www
cn-probe-2:
Filesystem Type Size Used Avail Use% Mounted on
/dev/vda1 ext4 40G 34G 4.2G 89% /
两台都是 40G,差别不在装了多少软件。89% 是块使用率,跟 inode 是两套账。
df -i
| 机器 | Inodes 总数 | 已用 | 可用 | IUse% |
|---|---|---|---|---|
cn-probe-1 /dev/sdb2 |
2,621,440 | 173,124 | 2,448,316 | 7% |
cn-probe-2 /dev/vda1 |
2,621,440 | 231,083 | 2,390,357 | 9% |
两台 inode 都在 10% 以下。“块没满但写不进去”这条路在这两台上不存在,不必一上来就怀疑 inode。df -i 一行就能排除它。
磁盘空间排查三步:df 看分区,du 看目录,最后才找文件
第一步的 df 已经跑完。第二步是把磁盘根目录切成一级目录,看谁大。
du -xhd1 / 2>/dev/null | sort -h
cn-probe-1 的输出,根分区合计 11G:
21M /etc
104M /boot
235M /root
3.3G /usr
6.7G /var
11G /
cn-probe-2,合计 34G:
56M /etc
148M /boot
232M /home
389M /opt
596M /.Recycle_bin
734M /root
4.0G /usr
5.8G /var
22G /www
34G /
两台的大头都在 /var 和 /usr,cn-probe-2 还多出一个 22G 的 /www。-x 常被写反。du 默认会跨进子挂载点统计,加了 -x 才是限制在同一个文件系统里。cn-probe-1 的 /www 挂在另一块 30G 盘上,不加 -x,那块盘的占用会被并进根分区的结果,du 立刻比 df 大出一整块。加上 -x,两边都是 11G,后面的判断才有意义。
不要一上来跑 du -sh /*。它会连 /proc、/sys、/dev 一起扫,再刷一屏权限报错,2>/dev/null 一吞,你连哪条失败都不知道。-d1 加 -x 加 2>/dev/null,三个一起用。
df 与 du 差 30G,需要什么条件
两台的结果对得上。
| 机器 | df 的 Used |
du 的合计 |
差 |
|---|---|---|---|
| cn-probe-1 | 11G | 11G | 对得上 |
| cn-probe-2 | 34G | 34G | 对得上 |
同时 ls -l /proc/*/fd 2>/dev/null | grep -c deleted 给出 9 和 8:两台机器上都有已删除、但仍被进程持有的句柄。数量在个位数,都没有造成 df 与 du 的可见差异。
所以“df 说满了、du 加起来差 30G”是真实存在、但需要条件的场景,不是常态。要让差值到 30G,条件很窄:
- 有一个几十 G 的文件被删掉,而进程还开着它。 inode 不释放,块不回收,
df照算,du看不见。差值约等于那个文件的体积。 - 数据盘挂载覆盖了一个已有目录。 原目录里的文件还在盘上,但被新挂载点挡住,
du走进去看到的是新盘。差值约等于被挡住那部分的体积。
常被列进“五个原因”的另外几条,量级对不上。分开看:
- ext4 保留块。 默认给 root 留 5%,40G 盘上是 2G 量级,到不了 30G。这个 5% 是 ext4 的默认值,我们没在样本机上跑
tune2fs -l验证。它解释的是“df 显示 100% 但 root 还能写”,不是 df 与 du 的差。 - inode 耗尽。 那是一种独立的满,
df -h可能还剩几十 G,但新建文件直接失败。它不产生 df 与 du 的差值。 - journald 没设上限。
du一样能统计到/var/log/journal,两边都算,不产生差异。它只是让总量变大。 - 稀疏文件。 逻辑大小大、实际占用小,这是
ls -l和du对不上的原因,不是df满而du空的原因。
一句话:差几十 G,只有 deleted 大文件和挂载覆盖做得到。其余几条值得查,但别指望它们解释 30G。
deleted 文件怎么安全释放,不重启服务
先确认有没有。这条是只读的:
ls -l /proc/*/fd 2>/dev/null | grep deleted
输出里每一行末尾带 (deleted)。文件被 rm 之后目录项没了,进程手里的 fd 还在,inode 不释放,块也不回收:du 走目录走不到它,df 按块算照样算进去。
释放的办法不是重启,是把文件长度归零:
truncate -s 0 /proc/<PID>/fd/<FD>
<PID> 和 <FD> 换成上一条命令查到的实际值。不动 inode,只把 size 置零,进程继续往同一个 fd 写,服务不中断。这条命令本文没有在生产机上执行,清理属于写操作,两台机器都保持原样。
有个坑:截断只改文件大小,进程内部的写偏移不会跟着回到 0。以追加方式打开的文件不受影响,按固定偏移随机写的进程会在文件里留下空洞,ls -l 看着还是很大,实际占用却已经下来了。看到“大小没变、df 却降了”,不是命令没生效。
顺序是三步:先 truncate,再给服务发它自己的重开信号(nginx 是 nginx -s reopen),最后才轮到重启。kill -9 不适合当首选:它不可捕获,缓冲和未落盘的请求直接丢,而 systemd 的 Restart= 会立刻把进程拉起来并重新打开日志文件。你看到空间释放了,中间断的那几十秒没人告诉你。
磁盘上最容易失控的三处:journald、apt 缓存、Docker 日志
VPS 磁盘空间不足时,先按这个顺序看三个目录。
journalctl --disk-usage
两台机器的输出:
cn-probe-1: Archived and active journals take up 3.9G in the file system.
cn-probe-2: Archived and active journals take up 3.9G on disk.
再跟一条 du 互相印证:
du -sh /var/log/journal 2>/dev/null
两边都是 4.0G。差 0.1G 不是错,是两种量法:du 按块算,journalctl 按文件实际大小算。同一个目录,两个数都该出现。
3.9G 落在 40G 系统盘上,占约 10%,是 /var 里最大的一块。
第二个被反复推荐的是 apt 缓存:
du -sh /var/cache/apt/archives 2>/dev/null
cn-probe-1 的结果是 32K。几乎没有缓存。“清 apt 缓存能腾出几个 G”在这台机器上不成立,apt-get clean 跑完 df 纹丝不动。CentOS 那台没有 apt,yum 的缓存算在 /var/cache 的 879M 里。
真正能腾出空间的是 journald 和 btmp。cn-probe-1 的 /var/log 明细里,btmp.1 是 508M,btmp 是 43M。btmp 记录的是 SSH 登录失败,不是业务日志。508M 的失败登录记录意味着一件事:这台机器长期被爆破。
Docker 的两台对比:cn-probe-1 的 overlay2 是 1.3G、containers 只有 1.2M;cn-probe-2 是 1.1G 和 227M。containers 那栏 1.2M 对 227M,说明容器日志在往 json-file 里堆,overlay2 本身没有失控。磁盘上这两项要分开看。docker system df 能按镜像、容器、卷、构建缓存四类分开列,这次没拿到这条命令的输出,只读到 overlay2 与 containers 两个目录的体积。
docker system df
下面这段是清理命令,本文没有在任何一台机器上执行,只作为写法参考:
journalctl --vacuum-size=500M
truncate -s 0 /var/log/btmp
第一条把 journal 压到 500M 以内,第二条清掉失败登录记录。两条都不可逆,跑之前想清楚要不要留证据。
一台跑了 103 天、一台跑了 229 天的机器,40G 磁盘被谁吃掉了
VPS 磁盘空间不足时,先看运行时长,这决定了下面所有数字的性质。
uptime
cn-probe-1 连续运行 103 天 18 小时 54 分,cn-probe-2 是 229 天 17 小时 39 分。下面这张表是两台机器的占用画像,每一项都来自上面的命令输出。
| 分项 | cn-probe-1 | cn-probe-2 |
|---|---|---|
系统本体(/usr + /etc + /boot) |
约 3.4G | 约 4.2G |
/var/log |
4.8G | 4.2G |
| 其中 journald | 3.9G | 3.9G |
/var/lib |
2.2G | 1.5G |
| Docker overlay2 | 1.3G | 1.1G |
/www(站点) |
不在根分区 | 22G |
/.Recycle_bin |
无 | 596M |
| swap | 1G | 1G |
| 根分区合计 | 11G | 34G |
系统本体那一行是个反例。“Linux 系统本身占 5 到 10G”在这两台机器上都不成立:一台 3.4G,一台 4.2G,都是 /usr 加 /etc 加 /boot 的实际总和。
/var/log 在 cn-probe-1 上是 /usr 之外最大的一块,比系统本体大 1.4G;cn-probe-2 上它和系统本体都是 4.2G,那台机器最大的目录是 /www。
两台的分叉点在 /www。cn-probe-1 的站点目录挂在另一块 30G 盘上,根分区只用了 11G;cn-probe-2 的 /www 就在系统盘里,22G 直接压在 40G 上,89% 是这么来的。同样是 40G,一块盘和两块盘是两种机器。/www 内部构成我们没读到,只有 22G 这个总量。
还有一个意外项:/.Recycle_bin 596M。回收站目录,删掉就回来,最容易腾也最容易被忘。cn-probe-1 没有。
想按“有没有额外数据盘”筛机器,各家商家的接入与线路 里有按配置整理的入口。
20G 磁盘够不够:从这两台倒推一个数
按上面的分项给 20G 磁盘做加法:
| 分项 | 取值 | 依据 |
|---|---|---|
| 系统本体 | 3.4G 到 4.2G | 两台实测 |
| journald(不设上限) | 3.9G | 两台都是 3.9G |
| Docker | 1.1G 到 1.3G | 两台 overlay2 实测 |
| 小计 | 约 9G | 以上相加 |
这是从两台在跑的机器倒推的,不是官方数字。不同商家 20G 套餐的磁盘实际可用空间受文件系统开销影响,各家不一样,这个数我们没有。
结论分两档:
只跑 Nginx 加 PHP 的小站,不装 Docker,20G 磁盘够。系统本体加 journald 约 7.3G 起步,还剩十二三 G,放站点文件和几个月的日志有富余。
一旦上 Docker 或者 MySQL 8,20G 磁盘就很紧。9G 只是起步,MySQL 的数据目录会持续长,Docker 的镜像和构建缓存也不会自己变小,而 journald 那 3.9G 还在原地。这种组合建议直接上 40G,别在 20G 上做压缩。
按这个判定线去圈候选,最新优惠与套餐比价 按价位排过,40G 那一档的实际差价通常比一次迁移便宜。
扩容之后 df 还是老数字
lsblk -f
输出把盘、分区、文件系统三层摊在一行里,df 卡在哪一层没动一眼能看出来。
三层依次是:分区表、LVM 逻辑卷、文件系统。商家把底层盘扩了,分区没扩,df 不变;分区扩了,LV 没扩,还是不变;LV 扩了,文件系统没 resize,依然不变。三层的容量要一路走到底,df 才更新。
这里只讲怎么看,不教改分区。改分区表、resize 文件系统都是写操作,顺序错一步就是丢数据。
预防:三条能落地的阈值
第一条,给 journald 设上限,开关是 /etc/systemd/journald.conf 里的 SystemMaxUse。这台机器的配置文件实际内容我们没读到(只读守卫拒绝了系统服务路径),不给当前值,只说怎么设:
[Journal]
SystemMaxUse=500M
改完 systemctl restart systemd-journald 生效。字段含义以官方手册为准:journald.conf 手册页。
第二条,logrotate 加 maxsize。daily 拦不住一天之内暴涨的日志,因为 logrotate 只在 cron 触发那一刻(多数发行版每天一次)才判断。size 会忽略时间周期、超了就转,但它同样只在 cron 跑起来时才生效,而且一用 size,daily 就失效了。要「每天照转 + 超限提前转」,用 maxsize:时间周期照常,一旦超过阈值不等周期也转。
第三条,磁盘和 inode 两个指标一起告警。85% 是我们自己定的操作线,不是行业标准,原因是留出一次清理或扩容的操作余量:
df -P / | awk 'NR==2 && $5+0>=85 {print "disk", $5}'
df -Pi / | awk 'NR==2 && $5+0>=85 {print "inode", $5}'
两条都是只读,扔进 crontab 每小时跑一次,输出非空就发通知。只告警不删除,磁盘余量留给人判断。
磁盘满和网络慢是两条独立的线。网站打不开时别用延迟高低判断盘满,那套指标看的是另一件事,方法在 VPS 延迟怎么测才准 里。
买之前该问商家的四件事
这四个答案都会改变上面的算法,购买前都能问清楚:
系统盘是 SSD 还是 NVMe。 不影响容量,影响 journald 刷盘时的 IO 等待。
swap 是独立分区,还是系统盘上的一个文件。 两台 swap 都是 1G,cn-probe-1 一点没用(SwapFree 等于 SwapTotal),cn-probe-2 用了约 283M(1,049,596 − 759,480 kB)。如果 swap 是文件,这 1G 就是实打实从系统盘里扣的,20G 的盘上它占 5%。
快照占不占套餐配额。 有的商家单独计费,有的直接算进磁盘用量。不问清楚,扩容的钱可能白花。
超额之后是限速还是停机。 流量和磁盘是两套算法,带宽和流量怎么换算 讲的是前者,别拿流量额度去推磁盘容量。
这四条我们没查到统一的商家条款,各家不同,所以只列问题不给答案。真买到不满意的,退之前先看清规则,退款政策怎么读 里拆过时间窗口和扣费方式。
VPS 磁盘空间不足:一页速查表
| 症状 | 命令 | 判据 |
|---|---|---|
面板报警或 SSH 报 No space left on device |
df -hT |
磁盘 Use% 到 85% 以上就该动手(本站口径) |
df 满了但 du 差很多 |
ls -l /proc/*/fd \| grep deleted |
有 (deleted) 且文件大,就是它 |
df -h 还有空间但写不进去 |
df -i |
IUse% 接近 100% 才是 inode 满 |
| 不知道哪个目录大 | du -xhd1 / 2>/dev/null \| sort -h |
看第一列最大的那个 |
| 日志占了多少 | journalctl --disk-usage |
超过 1G 建议设 SystemMaxUse(本站口径) |
| 容器占了磁盘 | docker system df |
分镜像、容器、卷、构建缓存四类看 |
磁盘满导致站点 502,先别怀疑线路。VPS 磁盘空间不足和网络问题是两条线,VPS MTU 怎么查 和上面那篇延迟测量分别对应。机器本身值不值得继续用,VPS 测评 里有按机房和配置逐台过过的记录。
常见问题
df 显示 89%,du 加起来也是 34G,这算对得上吗?
算。我们这两台磁盘就是 11G 对 11G、34G 对 34G,完全对得上。对得上说明没有 deleted 大文件、没有挂载覆盖,占用就是目录里那些文件实打实占的。
清 apt 缓存能腾出几个 G?
在 cn-probe-1 上是 32K,不是几个 G。apt-get clean 跑完 df 基本不动。想腾空间先看 journalctl --disk-usage 和 /var/log/btmp,这两处才是大头:3.9G 和 508M。
inode 要不要查?
要查,但一条命令就够,inode 和块是两套账。两台机器的磁盘 IUse% 是 7% 和 9%,离满很远。df -i 跑一次,数字低就可以彻底放下这条线。真正 inode 满的机器,df -h 往往还有大量剩余空间,症状是新建文件直接失败。
journald 那 4G 能清到多少?
取决于 SystemMaxUse 设成多少。设 500M 就能压到 500M 以内,journalctl --vacuum-size=500M 是手动版的同一件事。清理命令我们没在生产机执行,两台机器的 journal 都还是 3.9G。清之前想清楚:日志是排障时唯一的证据。
20G 的 VPS 跑 Docker 加 MySQL 够吗?
按两台机器的实测倒推,系统本体 3.4G 到 4.2G、journald 4G、Docker 1.1G 到 1.3G,起步就约 9G。MySQL 的数据目录还会持续长。20G 会很紧,建议直接 40G。这是倒推值,不是官方数字。
40G 用到 89% 了,还能撑多久?
没法给天数,增长取决于日志写入量和站点数据增量,这两个我们都没测。能给的是判断线:85% 开始处理,89% 已经在处理窗口内。先跑 journalctl --disk-usage,journal 占大头就设个上限,能退回一截,不用扩容。
