整理2026年1月7日的Linux笔记时翻到了当天下午处理一次磁盘告警的完整过程从df排查、du定位到揪出journald日志黑洞再到顺手补上logrotate、临时目录清理和rsync异地备份。事情本身不复杂但把整条链路串起来以后对“Linux服务器上磁盘到底是怎么被占满的”这件事有了更整体的认识。这篇笔记拆解开写既是给自己留档也给同样做运维、折腾自建服务器的朋友一个可直接照抄的排查思路。1. 从磁盘告警开始df、du、inode三维定位1.1 接到告警后先看清楚“满的是哪个分区”2026年1月7日午休刚过值班群里弹出一条告警web-01的/data分区使用率已超过90%。我上服务器后的第一反应不是直接去翻监控图表而是先执行了df -hT确认现在是哪个文件系统、哪个挂载点在报警。df -hT输出大概长这样文件系统 类型 容量 已用 可用 已用% 挂载点 /dev/sda2 xfs 200G 180G 20G 91% /data这里有两个信息值得留意一是文件系统类型xfs和ext4在后续扩容、备份策略上差别很大二是挂载点很多人只看“磁盘满了”却不看是哪个分区满了导致把日志清理工具跑错地方。比如这次报警明确是/data分区系统盘/还有剩余空间那么mlocate、apt缓存这些占系统盘的东西就不该优先排查。很多监控系统只报了“磁盘使用率”没报inode、没报具体目录。所以我建议每次告警都保留一个固定动作df -hT和df -i两行连敲先把“容量”和“文件数量”两个维度都看清楚避免被表象误导。这也是后面踩坑的主要教训。1.2 du逐级下钻把最大的目录找出来确认是/data分区吃紧后我用了du从根上往下钻。平时排查我习惯先看一级目录的总量再决定往哪里走而不是一上来就对所有文件做全盘扫描。du -x -h --max-depth1 /data 2/dev/null | sort -rh | head -20-x的作用是不要跨越文件系统防止把其他挂载点重复统计进来2/dev/null是因为有些目录没有读取权限报错信息会干扰排序输出。第一次执行后最扎眼的是/data/docker占了差不多140G其次是/data/logs约30G。继续往/data/docker里钻发现大部分空间是容器日志和镜像层文件占掉的。一个容器如果在启动时没有显式挂载日志目录docker默认会把日志写到/var/lib/docker/containers/容器ID/*-json.log这是非常经典的隐藏空间杀手。到这里我基本判断出问题有两层大文件撑满了容量大量小文件则可能撑爆inode。所以我没有急着删东西而是先查了文件数量的水位。1.3 意外发现inode使用率已经到了99%df -i这个命令很容易被新手忽略但生产环境里“容量没满、却写不进文件”的诡异故障十有八九和它有关。df -i /data看到结果是文件系统 Inodes 已用(I) 可用(I) 已用(I)% 挂载点 /dev/sda2 3000000 2970000 30000 99% /datainode可以理解成文件系统里每一条文件的“索引卡”。一个分区能创建多少个文件在上层看是无限实际上受inode数量限制。就像停车场已经停满了车入口显示屏再写“剩余车位0”外面还有空地的意思一样——不是没有空间是没法再放进新的车了。当时/data下的小文件大多集中在应用缓存目录里像是图片缩略图、临时生成的Session文件、未清理的任务队列数据。这也解释了为什么du看容量没那么吓人但应用却报“No space left on device”。2. 真正的元凶systemd-journald日志积压2.1 journald为什么能安安静静吃掉几十G在很多人印象里用systemd的系统日志都在journalctl里管着平时命令敲两下能查到就行很少去管它占多大地方。但journald的默认策略其实是“按磁盘空间比例兜底”不是“按日志天数兜底”。如果你的/var/log/journal目录被独立放在哪个分区里或者根分区比较大journald日志甚至可能积压到数十GB。它默认允许使用磁盘空间的10%略保守的说不同发行版默认值有差异但整体思路一致而且没有设置“保留天数”时老日志就像旧报纸一样只进不出。我在web-01上执行了这条命令确认现状journalctl --disk-usage返回的数字是4.2G。放到整个/data分区里看不算特别夸张但它属于“今天不处理、明天继续涨”的持续性问题必须设一个天花板。2.2 现场清理vacuum是真能立刻释放空间的要临时腾出空间可以直接用journalctl --vacuum-size或--vacuum-time让日志“瘦身”。journalctl --vacuum-size200M journalctl --vacuum-time7d--vacuum-size200M的意思是不管现在日志多大只保留最近写入的200MB其余全部删掉。--vacuum-time7d则是按时间线清理只保留最近7天以内的日志。对一台业务服务器来说保留7天通常足够回溯问题超过7天的日志价值很低而且以后也很难有人真的去翻。执行完后磁盘立刻释放了大约3.9G。如果想确认是不是真的成功再跑一次journalctl --disk-usage看到数值掉到符合预期的范围内即可。2.3 改配置文件给日志总量装上“天花板”临时清理只能救急真正避免以后再犯是把journald的容量上限写进配置里。我当时编辑了/etc/systemd/journald.conf[Journal] Storageauto Compressyes SystemMaxUse200M RuntimeMaxUse100M MaxRetentionSec7day MaxFileSec1day简单解释一下这几个参数的含义Storageauto日志优先写入内存目录空间紧张时才持久化到磁盘比较适合服务器场景。SystemMaxUse200M持久化日志总大小上限。所有journal文件加起来超过200MB就自动清理旧日志。RuntimeMaxUse100M内存日志上限。如果服务器不常重启这个值甚至可以再小一点。MaxRetentionSec7day超过7天的日志一旦触发写入就自动删除。MaxFileSec1day单个日志文件最多写一天就切换新文件方便以后按天排查。修改配置后重启服务systemctl restart systemd-journald重启后再次确认journalctl --disk-usage只要配置生效日志总量就永远被压在200MB以内。这一步比任何“今天手动删一下”都关键——不设上限下次还是会在某个月圆之夜把磁盘塞满。3. 清理动作之外顺手补上的三道防线3.1 logrotate接管应用日志别再让日志裸奔清完journald我顺便检查了应用日志目录发现/data/logs下面很多日志文件还在裸奔没有轮转、没有压缩一个Tomcat应用的catalina.out已经写到了4G多。这种情况光靠手动清理根本不现实应该交给logrotate。我在/etc/logrotate.d/下写了一个专门针对该应用的配置/data/logs/app/*.log { daily rotate 14 compress delaycompress missingok notifempty copytruncate }这里有几个参数值得展开daily每天轮转一次。rotate 14保留14份历史日志。compress轮转后的日志用gzip压缩。delaycompress轮转后不立即压缩昨天的日志因为应用可能还在写文件句柄晚一天压缩更保险。copytruncate复制当前日志内容后清空原文件适合应用无法通过信号重开日志文件的情况Tomcat这类进程就很适用。配置完成后建议先“空跑”验证一下logrotate -d /etc/logrotate.d/app-d是debug模式只打印要做什么不真正执行。确认无误后再手动强制执行一次logrotate -f /etc/logrotate.d/app执行完就会发现那些“年份级”的巨型日志被切割成一天一个文件磁盘占用立刻下降。3.2 临时目录清理systemd-tmpfiles的默认规则磁盘清理的第二道防线是处理/tmp和/var/tmp这类临时目录。很多人只知道/tmp会定期被清但清多久一次、由谁清基本不清楚。systemd里有systemd-tmpfiles-clean.timer这个定时器它按照/usr/lib/tmpfiles.d/tmp.conf里的规则定期清理临时目录。默认策略大致是/tmp下超过10天没动过的文件会被删/var/tmp则放宽到30天因为有些程序比如编辑器恢复文件、安装包解压残留生命周期比较长。这次我在服务器上手动扫了一圈把已经超过30天且明显没用的zip包、安装脚本、临时导出文件列出来find /var/tmp -xdev -type f -mtime 30 -exec ls -lh {} \; 2/dev/null先看清单确认没问题再执行删除。我不建议直接find -delete万一误删了另一个服务正在读取的文件代价会很大。3.3 用tar做一次配置基线快照磁盘清理过程中改了不少配置包括journald.conf、logrotate规则还有应用启动脚本。按照我的习惯凡是涉及“动配置”的维护操作做之前都要先留下一份可回滚的快照。考虑到web-01的配置目录都不大我选了tar打包的方式把关键目录存到另一个独立目录里mkdir -p /data/backup/manual_20260107 tar czvf /data/backup/manual_20260107/web01_etc_$(date %F).tar.gz \ /etc/systemd/journald.conf \ /etc/logrotate.d/app \ /etc/nginx/nginx.conf \ /etc/nginx/conf.d/ \ /etc/app/app.properties注意这个快照没有放在/data根目录下而是放进了独立的/data/backup目录。如果快照和原数据在同一个分区磁盘满了的时候连备份也写不进去就失去了兜底的意义。4. 异地备份rsync增量同步与定时任务4.1 为什么选rsync而不是cp磁盘清理完之后我发现web-01缺少一套“自动到异地”的数据备份方案。以前是每周有人手动复制一次不仅麻烦而且容易漏。这次我直接把备份方案升级为rsync增量同步。rsync和cp最本质的区别在于增量同步。第一次全量传完数据之后第二次再跑的时候它只传输有变化的部分而且会对比文件大小、修改时间跳过没改过的文件。对大目录来说这个优势非常明显第一次可能传了2小时第二次往往只需要几十秒。推荐的基础参数组合rsync -avz -H --numeric-ids \ --delete \ -e ssh -p 22022 \ /data/backup/web01/ \ backup192.168.8.20:/backup/web01/参数含义拆开看-a归档模式保留权限、属主、时间戳、软链接等属性。没有这个参数备份过去只是“文件内容一样”权限和属主可能全乱。-v显示传输过程。-z传输时压缩适合文本类文件。-H保留硬链接防止目录里的硬链接关系被复制成多个独立文件。--numeric-ids以数字UID/GID传输避免两端/etc/passwd不一致时属主错乱。--delete让目标目录与源目录保持完全一致源端删除的文件目标端也会删。-e ssh -p 22022指定SSH端口避免默认端口被安全策略挡掉。执行前--delete要极度谨慎。如果源目录路径写错比如少了结尾的/、漏了一层子目录rsync会把目标目录里不该删的文件也删掉。我的习惯是先加--dry-run -v跑一遍看输出确认删除名单里没有意外项再正式执行。4.2 备份之后要验证别让备份变成“心理安慰”rsync跑完不等于备份完成。我见过不少人备份脚本写得很好但恢复的时候才发现备份目录是空的、半截的、权限不对的。那次我做了两件验证的事先做一次“差异对账”用rsync的dry-run模式看两端还有哪些差异rsync -avn --delete /data/backup/web01/ backup192.168.8.20:/backup/web01/命令里的-n只做模拟运行不实际传输。输出里如果还有文件列表说明两端的同步还没真正完成没有文件列表则说明已经一致。再抽查几个关键文件的md5值md5sum /data/backup/web01/app/etc/app.properties ssh backup192.168.8.20 md5sum /backup/web01/app/etc/app.properties两端MD5一致才能确认文件传过去之后没有损坏。这一步看起来繁琐但真正遇到“备份回滚”的时候能救命。4.3 crontab定时任务把备份变成自动化手动同步一次只能代表当天做了以后每天都要做就得交给cron。我先写了一个备份脚本/root/scripts/backup_web01.sh#!/bin/bash export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin LOCK_FILE/var/lock/backup_web01.lock exec 9$LOCK_FILE flock -n 9 || exit 1 rsync -avz -H --numeric-ids --delete \ -e ssh -p 22022 \ /data/backup/web01/ \ backup192.168.8.20:/backup/web01/ /var/log/backup_web01.log 21脚本里用flock做互斥锁防止上一次rsync还没跑完、下一次任务又被触发。如果网络很慢或者数据量突然变大两轮任务重跑会互相干扰甚至产生错乱数据。然后把定时任务写进crontab30 1 * * * /root/scripts/backup_web01.sh意思是每天凌晨1:30执行一次。日志统一写到/var/log/backup_web01.log方便第二天检查。这里需要特别提醒cron环境里的PATH很干净。如果之前手动在终端能执行rsync但在cron里报rsync: command not found基本就是PATH没带上。脚本开头export PATH或者调用时写绝对路径都能解决这个问题。5. 复盘这次踩过的三个坑和监控改进5.1 容量还有5GB应用却报“磁盘满”这次排查里最浪费时间的一步是我在du阶段花了很久去找大文件却没一开始就意识到web-01的问题是inode耗尽。当时应用已经报No space left on device了可df -h明明显示/data还有5GB可用空间。问题的本质之前说过分区里的文件数是有限制的不能因为容量没满就以为万事大吉。后来我用du --inodes重新定位了一下du --inodes -x /data 2/dev/null | sort -rh | head -15这行命令会按“目录下的inode数量”排序很快就能看出哪个目录堆积了海量小文件。那次定位到的是一个消息队列的持久化目录里面上百万个任务数据文件每个只有几十字节但占满了inode索引。建议所有运维同学把df -i的检查加入日常巡检脚本。不要等到“容量尚可、但写不进文件”才发现那通常已经影响业务了。5.2 手贱用rm删日志真正的磁盘占用一点没降清理应用日志时我踩过一个典型坑直接用rm -rf /data/logs/app/catalina.out删掉日志文件结果df -h一看磁盘占用几乎没变化。原因是进程还在继续往这个文件里写。Linux下如果一个文件被进程打开通过rm删除后文件并不会立刻消失空间要等进程关闭文件句柄或进程退出后才释放。这种场景下更稳妥的做法是用truncate清空文件内容truncate -s 0 /data/logs/app/catalina.outtruncate保留文件本身和文件句柄只是把内容清空进程可以继续写磁盘空间也立刻释放。如果应用支持重开日志文件比如nginx还可以用信号通知它重新打开文件句柄然后旧的日志再交给logrotate归档。5.3 把本次经验固化到巡检脚本里处理完整台机器我顺手把这次的经验做成一个小巡检脚本/root/scripts/disk_health_check.sh以后每周自动跑一遍。核心就是把手动敲的命令串起来#!/bin/bash echo $(date %F %T) df -hT /data df -i /data journalctl --disk-usage 2/dev/null find /data -xdev -type f -size 500M -exec ls -lh {} \; 2/dev/null | head -20我看的不仅是输出还有阈值。比如inode使用率超过85%就会列为预警容量使用率超过80%就进入重点排查名单。监控系统里我也额外增加了两个自定义指标/data分区inode使用率和journald日志目录大小这两项是这次事故的直接导火索以前完全没盯着。顺便说一句这类巡检脚本不用写得花哨核心是每次执行都能稳定跑完、输出一眼能看懂。我见过有人把脚本写得很长很复杂结果半路报错后面所有检查项全部挂掉反而成了新的告警源。这次处理下来我最深的体会是磁盘问题从来不是“删几个文件”这么简单它是一个需要容量、inode、日志策略、备份链路整体考虑的长期工程。df和du只是入口真正的坑都在“默认配置”里。最值钱的不是当天腾出了几个G而是把journald、logrotate、定时备份这些机制一次性补齐了以后同类问题能被自动挡在门外。