☰
Linux磁盘告警排查与清理:从df到LVM的完整指南
2026/10/17 16:39:25 网站建设 项目流程

1. 磁盘告警之后先别急着删:我的排查顺序和工具选型

我遇到过太多次这样的场景:深夜收到监控告警,说某个分区使用率超过90%,甚至直接100%,服务写不进日志了,网站报错500,数据库开始拒绝连接。很多新手的第一反应是马上冲进去一顿rm -rf,这真的不是什么好习惯。磁盘满了首先是排查问题,而不是制造新问题。

清理分区这件事,本质上分三步:定位、清理、防复发。定位是最容易被跳过的,但恰恰是最重要的。你删了一个大文件,可能只释放了5%的空间,真正的空间黑洞还在某个角落里安静躺着,过几天又会触发告警。所以我的习惯是,接到告警之后先花十分钟把全局看清楚,再决定从哪里下手。

第一步永远是df -h,看整体使用率:

df -h

输出会显示每个挂载点的容量、已用、可用和使用率。这里要注意,df看的是文件系统层面,是逻辑分区的情况。很多人以为df显示的就是物理磁盘的分配情况,其实不完全对。比如LVM逻辑卷、软RAID阵列,df显示的是逻辑卷挂载后的视图。如果某个挂载点已经到了100%,但物理磁盘还有空闲,那不是删文件能解决的,得从卷组和物理卷层面去扩容。这个后面我会单独讲。

df -h只是第一步,它告诉你哪个分区满了,但不告诉你什么东西占的。对应的是du命令,去具体目录底下统计:

du -sh /var/log /home /usr /opt /tmp /root 2>/dev/null

du -sh分别统计这些一级目录的总大小,用2>/dev/null把权限报错过滤掉。拿到这些数字之后,你就能把问题缩小到具体目录范围了。比如/var/log占了几十GB,或者/home下的某个用户目录特别大,那就顺藤摸瓜往下查。

还有一个很多人忽略的点:df显示100%但du统计出来总和远小于已用空间。这种"空间神秘消失"的情况,十有八九是有大文件被删除了,但某个进程还持有句柄没释放;或者某个挂载点被重复挂载、目录被隐藏了。这种情况下再怎么删普通文件也没用,得找到那个还在占用句柄的进程。这个坑我后面会专门写一节。

工具选型上,命令行党用du完全够用,但如果觉得一层层手动往下跑太麻烦,我强烈推荐ncdu。它是个交互式的磁盘分析工具,基于ncurses界面,能用方向键浏览目录树,实时显示各子目录大小占比,比一条条敲du效率高得多:

# Debian/Ubuntu apt install ncdu # RHEL/CentOS yum install ncdu

安装完成后直接在根目录跑ncdu /,它会先扫描一遍目录树,然后给你一个可排序的列表。按大小排序后,大头在哪一目了然,还能直接在里面按d键删除选中的条目。实测扫描速度还行,几TB的数据大概几分钟,作为故障排查的第一梯队工具很称职。

还有个小工具叫duf,是df的现代替代品,输出带颜色、带进度条、能自动识别挂载点类型,一眼扫过去比df舒服很多。不过这只是观感上的提升,核心逻辑还是一样的。

2. 日志、缓存与临时文件:系统自产垃圾的清理逻辑

定位做完之后,通常第一个要开刀的就是各类日志和缓存文件。Linux系统自己就会产生大量"垃圾",这些不用怀疑,放心清理就行。

2.1 journald日志:动辄几十GB的隐形大户

现在主流发行版都跑systemd,所有服务的日志统一交给journald管。默认配置下,journald的日志会一直往/var/log/journal/里写,而且没有任何轮转策略。一个运行了半年以上的服务器,这里堆出二三十GB非常常见,而绝大多数人根本不会去管它。

查看当前journal日志占用:

journalctl --disk-usage

输出类似Archived and active journals take up 5.2G on disk,如果是这个量级,就该处理了。清理命令:

# 只保留最近2天的日志,超过的删除 journalctl --vacuum-time=2d # 按大小限制,总占用控制在500M以内 journalctl --vacuum-size=500M

这两个命令按需选一个就行。--vacuum-time=2d适合那些日志量不大但一直积累的场景;--vacuum-size=500M适合日志量大、对历史日志没什么回溯需求的场景。我个人在生产服务器上通常用500M限制,开发机直接设成100M。

更省事的方式是直接改配置,让journald自己控制占用上限。编辑/etc/systemd/journald.conf,找到这几行:

SystemMaxUse=500M SystemKeepFree=2G RuntimeMaxUse=100M

SystemMaxUse就是journal日志的最大占用,SystemKeepFree是保证留给系统的空闲空间,防止日志把盘写满。改完重启journald:

systemctl restart systemd-journald

2.2 apt/yum/dnf的缓存:清掉之后能释放几个GB

Debian系系统的包管理器会把下载的deb包缓存在/var/cache/apt/archives/,装完的、升级用的包全都留一份。时间久了,这个目录达到几个GB也不算稀奇。清理方式:

apt clean # 清空所有缓存的deb包 apt autoclean # 只清空那些已经无法再下载的过时包

我一般用apt clean,既然空间紧张,就别婆婆妈妈只清过时的。apt autoremove也可以配合跑一下,它会把那些因为依赖关系自动安装、现在没有任何包再需要的库和组件一次性移除,有时候一次能释放几百MB甚至上GB。

如果是RHEL系,对应的是yum clean all或者dnf clean all,逻辑一样,把缓存目录下的包和元数据全部清掉。

这里有个细节:清理包缓存不会影响系统里已安装的软件,它删的只是那些安装时下载的离线安装包。所以放心跑,不会有副作用。唯一的影响是下次重装某个包需要重新下载。

2.3 /tmp、/var/tmp与core dump文件

/tmp目录默认很多发行版会配置tmpfs清理策略,systemd-tmpfiles会在开机时清理掉一些超期文件。但前提是系统一直正常运行,如果长期不重启,/tmp下积累的临时文件也相当可观。手工清理建议只删当前用户会话相关的文件,谨慎起见我用tmpreaper这个工具按时间阈值清理,不直接rm -rf /tmp/*,万一有程序正在使用的临时文件被删了,可能引起诡异行为。

# Debian系安装tmpreaper apt install tmpreaper # 清理超过7天未访问的临时文件 tmpreaper -m 7d /tmp

-m参数表示按修改时间算,7d是7天。/var/tmp同理,比/tmp保留时间更长,但积累的东西性质差不多。

core dump文件也是个容易被忽略的点。程序崩溃时Linux会生成核心转储文件,默认路径通常在进程的工作目录或者/var/lib/systemd/coredump/下。一个崩溃的Java进程、Python进程,core dump动辄几个GB。查找方式:

find / -name "core.*" -type f -size +500M 2>/dev/null find /var/lib/systemd/coredump -type f -size +100M 2>/dev/null

如果确认这些core文件不需要了(一般生产环境也不需要,排错都靠日志和调试器,很少去啃core文件),直接删:

find /var/lib/systemd/coredump -type f -name "core.*" -delete

如果不想让系统继续生成core文件,可以改系统配置,临时关掉或者限制大小。编辑/etc/security/limits.conf加入:

* soft core 0 * hard core 0

需要重新登录才会生效。不过这里提醒一句,开发环境建议保留core文件,很多难排查的内存问题都得靠它。

2.4 /var/log下其它日志的轮转策略

除了journald,应用自己写的传统日志文件也可能很大。先排查占空间的大文件:

du -sh /var/log/* 2>/dev/null | sort -rh | head -20

常见的大头有syslog、kern.log、messages、nginx/access.log等。这些日志应该靠logrotate(日志轮转)来管理,原理就是定期把旧日志压缩、重命名、删除超期文件。

大多数发行版默认都装了logrotate,但配置可能不全。比如/etc/logrotate.d/下面有一条应用日志的配置:

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

含义是每天轮转一次、保留7份、压缩旧日志、使用copytruncate方式复制后截断原文件,避免影响正在写入的进程。配好之后,历史日志占用会被严格锁在一个可控范围。

3. 日志清完了还是满的:那些"删了又涨"的空间黑洞

日志和缓存清完之后,如果分区使用率还是居高不下,就得花精力去跟真实占用空间的大文件打持久战了。这里常见的坑比你想的多。

3.1 用户目录下那些莫名其妙的大文件

服务器上如果有跑应用的用户,比如/home/deploy、/var/www、/data这类路径,里面很容易藏着各种大文件。最常见的是:

  • 数据库备份文件:定时任务做的mysqldump或者pg_dump备份,一个备份几个GB,如果脚本没做清理策略,备份文件会一直累积。
  • 应用上传目录:用户上传的图片、视频、附件,永远只增不减。
  • 构建产物:前端打包的dist目录、后端编译的target目录,一次构建几GB不稀奇。
  • Anaconda/HuggingFace缓存:如果服务器上有搞AI的,~/.cache/huggingface、~/.conda/pkgs动辄几十GB。

用ncdu /home或者ncdu /data进去扫一圈,基本就能找到元凶。我的习惯是定位到具体目录后,再看时间戳和内容确认能不能删,不要直接一刀切。

3.2 Docker相关:镜像、容器、卷、构建缓存

现在几乎每台开发机上都有Docker,而Docker自己攒垃圾的能力在所有软件里也是排得上号的。查一下:

docker system df

这个命令会告诉你images、containers、local volumes、build cache四类分别占了多少空间。实测跑几个项目的开发机,build cache和废弃镜像加起来分分钟吃掉几十GB。

清理命令:

# 删除所有悬空镜像(没有标签的中间层镜像) docker image prune # 删除所有未使用的镜像 docker image prune -a # 清理构建缓存 docker builder prune # 一键清理所有未使用的资源 docker system prune -a --volumes

docker system prune -a --volumes这个命令比较猛,会把所有没用到的镜像、容器、网络、卷全删了。如果磁盘空间极度紧张,用它救急很快;如果只是想日常维护,我还是建议分开跑,逐项确认。

这里提醒一个容易踩的坑:docker system prune -a会把那些你没在使用的镜像全删掉,下次要用还得重新拉。如果网速不理想或者有离线部署需求,删之前先掂量一下哪些镜像是常用的。

3.3 开发环境里的缓存目录

开发相关的缓存目录也是空间消耗大户,但很多人不知道它们也属于"可清理"范畴:

目录内容评估
~/.npm/_cacachenpm下载的包缓存可清理,重新npm install会重建
~/.cache/pippip下载的wheel缓存可清理,不会影响已装环境
~/.gradle/cachesGradle构建缓存可清理,重构建会重新生成,但会变慢
~/.m2/repositoryMaven本地仓库建议保留常用依赖,清理旧版本可省空间
~/.cache/go-buildGo编译缓存可清理,影响仅是重新编译变慢
~/.cargo/registryRust包索引缓存保留也可,清理后依赖重下

这些目录的具体大小用du -sh ~/.npm ~/.cache/pip ~/.gradle ~/.m2 2>/dev/null一次确认,清理时直接删除对应目录即可,不会影响已经装的软件包。不过注意,删除~/.m2/repository之后,下次Maven构建会把所有依赖重新下载,构建时间会明显变长。建议只用Maven自带的清理插件去清理旧版本的SNAPSHOT依赖,不要整个删。

3.4 快照文件和孤儿文件

还有两种文件容易被忽略:

一是文件系统快照。如果用了LVM且创建过快照(snapshot),快照会随着原卷的数据变更不断膨胀,最终把卷组空间耗尽。这个用lvdisplay看一下快照卷的属性就能确认。如果快照确实不需要了,lvremove按名字删除,能释放大量空间。

二是被删除但进程仍持有的文件。前面提到的"空间神秘消失"就是这么回事。Linux下删除文件只是把目录项移除,如果还有进程打开着这个文件的句柄,文件内容不会真正释放。典型场景是:rm -f删了一个正在被Apache或某个Java进程写的大日志,然后df一看,使用率一点没降。

排查命令:

lsof +L1

lsof +L1会列出所有被删除但仍被进程占用的文件,看SIZE列就知道哪坨内存块没释放。找到之后kill掉对应进程(或重启服务),空间立刻回来。要是不能停进程,也可以> /proc/<PID>/fd/<FD_NUMBER>把文件内容清空,但这操作要非常小心,确认好文件描述符再动。

4. 删了还是没有空间:inode耗尽与无法释放的分区问题

清理分区过程中你会碰到一个奇怪现象:df显示使用率只有30%,但系统还是报"No space left on device"。这种问题跟文件大小无关,是inode被耗尽了。

4.1 inode耗尽是怎么回事

inode是文件系统里记录文件元数据的结构。每个文件、目录、软链接都要占一个inode,不管文件有多小。如果某个分区上创建了海量的小文件,inode就会被全部占光,这时候即磁盘空间还有富余,也没法新建任何文件了。

排查方法:

df -i /data

df -i看inode使用率,如果IUse%到了100%,那就是inode耗尽。最常见的元凶是:

  • 邮件服务器里的信件存储,每封信一个小文件
  • 消息队列、任务队列的持久化目录
  • 缓存系统(比如小图片、小缩略图)
  • clog或者一些错误日志系统疯狂写小文件

应对办法分两步。第一步是找出哪个目录的文件数量异常多:

for d in /data/*/; do echo "$(find "$d" -type f | wc -l) $d"; done | sort -rn | head

找到之后评估哪些能删。如果判断这个目录本来就是垃圾文件堆积(比如日志碎片、异常产生的空文件),清掉一部分inode就释放了。

如果目录里有大量真实有用的文件,那就得考虑文件系统层面的方案了。比如把那个分区格式化成支持更多inode的方式(XFS的inode是动态分配的),或者重新规划分区,把目录挪到新的大分区上。这种属于"容量规划"问题,清理只是治标。

4.2 检查文件系统错误与隐藏的大目录

还有一个可能是文件系统层面出了状况。比如ext4文件系统某些块组损坏,du统计不出来。这种情况建议先只读挂载检查一下:

# 先卸载分区(或确保没有进程在写) umount /data # 只读模式检查(ext4) fsck -fn /dev/sdb1

-f强制检查,-n表示不交互、不修改,只报告问题。如果fsck报告大量错误,说明文件系统确实有问题,在备份好数据的前提下可以去掉-n让它修复:

fsck -fy /dev/sdb1

这里必须提醒:对正在使用的分区直接跑fsck很危险,务必先卸载或至少以只读方式挂载后再操作。生产服务器上如果没法卸载分区,宁可先停下相关服务,也别强行修文件系统,搞崩了连数据都没了。

还有个场景:某个父目录下有别的文件系统被挂载上去了,比如/data底下又挂了个单独的分区到/data/docker。这时候你在/data下跑du -sh *,子目录显示的大小是挂载点里文件的总和,但它们在df里是单独统计的。这种情况很容易让你误判"某个目录占了超多空间",其实问题出在另一个分区上。用findmnt -R /data查看一下挂载树,把所有隐藏挂载点都揪出来再分析。

4.3 分区空间规划:物理分区、LVM与文件系统扩容

如果分区是真的满到删无可删,那就不是"清理"能解决的问题了,得谈扩容。这里分两类情况。

第一类是物理分区直接挂载的,比如/dev/sda3直接挂在/home下。这种情况要扩容就得用fdisk或parted调整分区边界,然后resize2fs(ext4)或xfs_growfs(XFS)扩大文件系统。风险高,过程繁琐,如果分区后面还有别的分区,改边界还可能动到后面的分区。除非是测试机,否则不建议在线操作,最好在救援模式下做。

第二类是LVM管理的逻辑卷,扩容就温和多了。流程图大致是:pv(物理卷)有剩余空间,vgextend或lvextend把空间划给逻辑卷,最后resize2fs(或xfs_growfs)扩展文件系统到新的大小。

# 查看卷组剩余空间 vgdisplay # 扩展逻辑卷20G lvextend -L +20G /dev/vgdata/lvdata # 扩展文件系统 resize2fs /dev/vgdata/lvdata # ext4 xfs_growfs /dev/vgdata/lvdata # XFS

LVM扩容可以在线进行,不需要停机卸载,这是它最大的优势。如果你的服务器当初没做LVM,现在又频繁遇到分区不够用,可以考虑在下次重装或大维护时把数据盘改成LVM布局,一劳永逸。

5. 我的清理脚本与日常巡检配置

清理手动过一次之后,觉悟应该到位了:这东西不能每次都靠故障驱动。至少每周自动看一次磁盘使用率,超过阈值就自动清理最基础的垃圾。下面是我自己常用的一个脚本,比较简单,但足够挡掉绝大多数的"盘满"事故。

#!/bin/bash # 简易磁盘清理脚本 set -e # 配置区 ALERT_THRESHOLD=85 # 使用率超过85%触发清理 MIN_FREE_GB=2 # 保证至少2G空闲 LOG_FILE=/var/log/disk-clean.log # 获取根分区使用率(去掉%) USE_PERCENT=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%') FREE_GB=$(df -h / | awk 'NR==2 {print $4}' | tr -d 'G') echo "[$(date)] 根分区使用率 ${USE_PERCENT}%, 空闲 ${FREE_GB}G" >> $LOG_FILE if [ "$USE_PERCENT" -lt "$ALERT_THRESHOLD" ]; then echo "[$(date)] 未达阈值,跳过清理" >> $LOG_FILE exit 0 fi # 1. journald日志控制到200M journalctl --vacuum-size=200M >> $LOG_FILE 2>&1 # 2. apt缓存清理 if command -v apt-get >/dev/null; then apt-get clean >> $LOG_FILE 2>&1 apt-get autoremove -y >> $LOG_FILE 2>&1 || true fi # 3. yum缓存清理 if command -v yum >/dev/null; then yum clean all >> $LOG_FILE 2>&1 || true fi # 4. Docker清理(仅当docker存在时) if command -v docker >/dev/null 2>&1; then docker system prune -f >> $LOG_FILE 2>&1 || true fi # 5. 清理超过7天的core文件 find /var/lib/systemd/coredump -type f -name "core.*" -mtime +7 -delete 2>/dev/null || true # 6. 清理临时文件(超过3天未访问的/tmp内容) find /tmp -type f -atime +3 -delete 2>/dev/null || true find /tmp -type d -empty -mtime +3 -delete 2>/dev/null || true echo "[$(date)] 清理完成,当前使用率 $(df -h / | awk 'NR==2 {print $5}')" >> $LOG_FILE

这个脚本有几点要注意。apt-get autoremove -y后面加了|| true,因为某些情况下autoremove会返回非零退出码(比如有包被hold住),在set -e下会导致脚本中断。写生产脚本时,非关键操作全部要容忍失败,别一个环节报错就跳过后面的清理。

find /tmp -type f -atime +3 -delete这个是按访问时间清理的,只删3天没访问过的文件。那些正在被程序频繁读写的临时文件不会被误删。-delete直接删除,没有经过-exec rm,效率更高,但同样要确认路径没问题。

5.1 配合监控工具自动告警

光有清理脚本还不够,得有人在磁盘快满时提前知道。基础设施成熟的地方一般用Prometheus + node_exporter采集磁盘指标,加上Alertmanager做阈值告警。这些是高阶玩法,这里不展开。简单场景下,直接用cron跑上面的清理脚本已经能解决大部分问题。

如果不想自己写告警,Linux自带的mail命令配合watchdog思路也能实现"使用率超了就发邮件"。我的经验是,执行类的任务就交给脚本,通知类的任务必须轻量到位,别在清理脚本里又串联告警又串联处理,职责不清会在故障时互相拖累。

5.2 日志轮转配置推荐

最后补一条日志轮的推荐配置,把常见服务日志和系统日志的轮转策略都配上,避免日志成为"清理完又立刻涨满"的顽固分子。

查看当前生效的轮转配置:

cat /etc/logrotate.conf ls /etc/logrotate.d/

系统基础日志(/var/log/syslog等)在/etc/logrotate.d/rsyslog里已经有一份默认配置,保持默认即可。业务日志要自己写配置。以nginx为例:

/var/log/nginx/*.log { daily rotate 14 compress delaycompress missingok notifempty create 0640 www-data adm sharedscripts postrotate [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid` endscript }

配置要点说明:daily每天轮转,rotate 14保留14份,也就是日志最多留两周。compress压缩旧日志,delaycompress表示本轮转时不压缩上一个文件,防止某些时间点nginx还在写旧日志名。copytruncate在某些场景(比如Java应用无法重新打开日志文件)很有用,但会丢失极少量日志内容,能用postrotate发送信号的方式就尽量用信号方式。

配好之后用logrotate -d /etc/logrotate.conf做一次debug输出验证配置是否合法。当然没有真实跑过会有一堆文件访问的提示,正常看关键错误就行。正式生效是logrotate -f /etc/logrotate.conf强制跑一次。

6. 收拾残局:几个让我印象深刻的奇怪案例

清理分区这事看着简单,实际上每次处理告警都有可能撞上意外。分享几个我真实见过的情况,给各位做个心理预期。

6.1 大文件在删除后依然占用

一次线上磁盘告警,我用lsof +L1发现一个被删掉的access.log还占着3.2G空间,句柄在一个nginx worker进程里。正常情况下nginx会定期重开日志文件,但那次因为配置问题,worker没有响应USR1信号,老句柄就一直没释放。解决办法是重启nginx,空间瞬间回来了。这个案例说明,磁盘告警不一定是文件多,也可能是进程告辞了一半。

6.2 目录被重复挂载导致统计错乱

另一个案例更有迷惑性。du -sh统计/home显示有8G,但df显示整个根分区才用了4G,剩下4G哪去了?排查了半天,才发现有个用户把/home下某个目录挂载了一个tmpfs到/home/username/tmp上。du算的是tmpfs里文件的大小,而df统计的是挂载点各自的数据。这类目录挂载的"障眼法"在日常检查时特别容易让人一头雾水。

6.3 inode耗尽的邮件服务器

还有一次是小公司内部的邮件服务器,df显示根分区还有60%空闲,但发邮件一直报错说写不进磁盘。后来df -i一看,分区inode已经被二十几万的邮件小文件占满了。那次清理了两天,把三年以上的历史邮件都归档压缩掉,inode使用率才降下来。清理完之后我给他们加了一条cron定时任务,专门清7天前的邮件队列文件,之后没再犯过。

6.4 LVM快照膨胀的惨案

还有一个经典教训:数据库卷做LVM快照做备份,原本想着快照用完就删,结果快照一直挂在那边,数据库持续写入把快照撑到跟原卷一样大。等发现时卷组空间已经满了,最后不得不把快照删除,才释放出一大块空间。那次之后我养成了习惯,每次创建快照都会在提醒自己写好自动删除策略,绝不让快照变成常驻进程。

7. 一点工具之外的体会

最后说点工具之外的东西。清理分区看起来是个技术活,但真正拉开差距的不是你会几个命令,而是你对"空间为什么会满"这件事的理解深度。磁盘告警背后往往意味着某种业务趋势:日志量增长速度、用户上传量、备份策略是否合理、应用是否在处理完临时文件后及时清理。把这些因素想清楚,清理起来才会有的放矢。

我个人的建议是,每台服务器至少养成三个习惯:

一是给每个分区设置合理的监控阈值,别等100%才处理。我通常设为75%预警,85%自动清理,90%人工介入。75%这个数字不是拍脑袋,因为很多临时写操作、文件系统metadata增长都需要一定的余量,太晚处理会很被动。

二是动手清理前一定先确认"能删"。比如按atime清理/tmp时,要确认没有程序把临时文件放在/tmp且依赖它。稍微多花两分钟确认,比误删之后花两小时恢复数据要划算得多。

三是把每一次"盘满事故"都当成一次排查演练,记录下触发原因、处理过程、生效命令。我的经验是,同一台服务器反复磁盘满,大概率是同一个原因在滋生,不根治的话,清理脚本写得再勤也只是拖延下一次告警的时间。

清理分区从来都不是一道删除命令的事,它是一个持续的小工程。每次用心处理完一次告警,你都会对系统文件结构、业务写入模式多一点认识,这种积累很实在。希望这篇内容能让你在处理类似的磁盘告警时,少走一点弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询