CentOS 7 用久了,系统盘满是个躲不过去的坎。我这个月就处理了三四台机器,有物理服务器,也有装在 VMware 里给测试环境用的虚拟机,症状出奇一致:df -h 一看 / 的可用空间为 0,MySQL 直接拒绝写入,ssh 能连上但敲命令卡到怀疑人生。多数时候你不是撞见了什么天大的故障,而是日志、缓存、旧内核、Docker 镜像这些“隐形大户”一层一层把盘塞满了。这篇文章就把我在 CentOS 7 上清理系统盘的完整套路汇总一遍,从诊断到收尾,全部是可复现的命令。环境是 CentOS 7 x86_64,不管是最小化安装还是 DVD 完整安装,清理逻辑完全一致。
1. 动手清理之前,先把磁盘占用看明白
1.1 用 df 和 du 快速定位大目录
很多新手一上来就 rm -rf 各种文件,结果把系统删坏了还找不到问题在哪。我习惯先看三层:文件系统使用率、inode 使用率、根目录下哪个一级目录最占地方。
bash复制df -h
df -i
du -sh /* 2>/dev/null | sort -hr | head -20
df -h 看的是文件系统的容量和挂载点,df -i 看的是 inode。很多人只盯着空间,忘了 inode 也会满。如果 inode 满了,哪怕磁盘还有几十 G 空余,你也写不了任何新文件,表现就是各种“No space left on device”。
du -sh /* 是把根目录下每个一级目录的大小扫出来,sort -hr 按人类可读的数值倒序排,这样一眼就能看到到底是 /var、/usr、/home 还是 /tmp 在作怪。这一步做扎实了,后面清理才有方向。千万别说你直接 du -sh / 跑一遍,那只会告诉你根目录很大,对你没有半点帮助。
1.2 用 ncdu 交互式往下钻
命令行 du 能定位到一级目录,但再往下钻就比较费劲了。我强烈推荐装一个 ncdu,英文全称是 NCurses Disk Usage,一个基于 ncurses 的磁盘占用分析工具,操作起来像菜单一样直观。
bash复制yum install -y ncdu
ncdu /
进入界面之后,上下键移动光标,回车进入目录,d 键删除当前项,q 键退出。它的好处是把每个子目录的大小按人类可读方式列出来,还带一个百分比进度条,钻目录非常快。我记得有一次排查,就是靠 ncdu 发现了 /var/lib/docker/overlay2 下面藏着一个 12G 的日志文件,普通 du 命令早就被一堆容器层淹没了。
这里有个注意事项:ncdu 扫描大目录会花一点时间,而且第一次扫描时会读大量磁盘,建议在业务低峰期做。另外如果系统盘已经满了到无法 yum 安装的程度,可以先用 du 找出占用最大的目录,再决定要不要删掉一些文件腾出空间来装工具。这是个很现实的先后顺序问题,盘满了反而装不了诊断工具,先删垃圾再用工具,这就是运维的生存智慧。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 日志与包缓存:最容易忽略的“隐形大户”
2.1 systemd journal 日志的积压问题
CentOS 7 从很早开始就把系统日志交给了 systemd 的 journald 管理,默认日志放在 /var/log/journal 目录里,如果这个目录越来越大,根分区必然跟着遭殃。
journald 默认对日志大小没有太严格限制,它会把所有服务输出、内核消息、认证记录全部收进来。我自己就遇到过一台机器,/var/log/journal 累积到 8 个多 G,就是因为某个服务崩溃后疯狂打印错误信息。
最直接的清理方式:
bash复制journalctl --vacuum-time=7d
这条命令会把 7 天前的日志全部清掉。也可以按大小清理:
bash复制journalctl --vacuum-size=200M
我更推荐你在 /etc/systemd/journald.conf 里把上限直接配死,这样以后就不会再堆积:
ini复制SystemMaxUse=300M
改完之后重启 journald 服务:
bash复制systemctl restart systemd-journald
这一步的底层逻辑很简单:journald 在运行时会按照 SystemMaxUse 限制日志总大小,超过上限会触发轮转和删除,所以这条配置是长期的、治本的。很多人只手动清一次,过两个月又满,就是因为没做这个设置。
2.2 yum 缓存与半成品的包管理痕迹
yum 在安装、更新软件包时,会把下载的 rpm 包缓存到 /var/cache/yum 目录下。正常情况下这部分不算大,但架不住你频繁 update、install、卸载重装,缓存会慢慢累积。清掉它非常简单:
bash复制yum clean all
yum clean all 会清理所有缓存,包括 metadata 和包文件。如果磁盘压力特别大,也可以直接清 /var/cache/yum 下面的实际文件,但用 yum 自带命令更安全,不会误删目录结构。
还有一个很多人不知道的点:CentOS 7 在升级过程中如果中途断电或者被 kill,会留下未完成的事务。这些半成品事务会让 yum 在下次执行时报错。可以用:
bash复制yum-complete-transaction
把历史遗留事务处理掉,然后 yum clean all 再清理一次。这类“软件包管理的残骸”虽然单个不大,但积少成多,而且会影响后续安装的稳定性。
2.3 应用日志交给 logrotate 管
系统日志清了,应用日志更得管起来。nginx、Tomcat、各种 Java 服务的日志如果没人管,/var/log/nginx/access.log 能给你写到几个 G。
CentOS 7 自带 logrotate 机制,但默认只对少数系统日志生效。我给你一个很实用的配置模板,放在 /etc/logrotate.d/myapp:
bash复制/var/log/nginx/*.log /var/log/tomcat/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
copytruncate
}
简单解释下每个参数:daily 是每天轮转一次,rotate 7 表示保留最近 7 个轮转文件,compress 用 gzip 压缩旧日志,delaycompress 让上一次轮转的文件延迟到下次再压缩,copytruncate 是在不重启服务的情况下复制当前日志内容再清空原文件。这个配置非常适合 nginx 这种不能随便重启的服务。
配好之后可以先手动验证一下:
bash复制logrotate -vf /etc/logrotate.d/myapp
-v 显示详细过程,-f 强制执行一次。如果你嫌轮转太频繁,把 daily 改成 weekly 或 size 100M 都是可以的。我的经验是:日志按天轮转最稳妥,太大或太频繁都不好排查问题。
3. 把这几年 Docker 占用的空间收回来
3.1 先看 Docker 到底吃了多少
如果你的机器上装了 Docker,尤其是用来跑各种测试环境或者 CI 的,那系统盘大多数空间基本都被 /var/lib/docker 吃掉了。Docker 的镜像层、容器读写层、卷、日志,个个都是空间黑洞。
到这一步,一定要先用 Docker 自带的工具看清楚:
bash复制docker system df
这条命令会输出一个漂亮的表格,清楚显示镜像、容器、本地卷、构建缓存各自占了多少空间,还会告诉你哪些是“可回收的”(RECLAIMABLE)。有了这个数值,你就能决定要不要动刀子。
同时配合看目录大小:
bash复制du -sh /var/lib/docker/*
重点留意 overlay2 目录,这是容器和镜像的存储层所在,一般也是整个系统盘里最肥的一块。
3.2 一套 prune 组合拳
Docker 官方提供了一组很人性化的清理命令,我一般按顺序执行:
bash复制docker container prune -f
docker image prune -a
docker volume prune -f
docker system prune -a --volumes
第一行是删除所有已停止的容器,第二行是删除所有没有被容器使用的镜像,第三行是删除没有容器引用的匿名卷,第四行是系统级大扫除,把上面三类的全部一次性清掉。
这里有个坑我必须提醒你:docker image prune -a 会把所有没有被运行的容器引用的镜像全部删掉,这会导致你之前下载的中间层镜像消失,之后启动相关容器时可能要重新 docker pull。如果你的网络带宽宝贵,或者离线环境不方便拉镜像,执行前先掂量一下。保守方案是只删悬空镜像:
bash复制docker image prune -f
这个只删被标记为 <none> 的悬空镜像,不会影响正在用的版本。我个人习惯是先用保守方案,等确定系统盘还是不够再上全面的 docker system prune -a --volumes。
3.3 从源头控制容器日志与镜像堆积
清理只能救一时,源头控制才是正解。Docker 的容器日志默认是无上限的,一个疯狂输出日志的容器能把宿主机的盘写到爆。最好的办法是在 /etc/docker/daemon.json 里加日志上限:
json复制{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
}
}
max-size 表示单个日志文件最大 50M,max-file 表示最多保留 3 个文件。这样单个容器最多占 150M 日志,换个服务、加个新容器都不怕日志爆盘。改完配置记得重启 Docker:
bash复制systemctl restart docker
注意这个配置只对新建容器生效,已经存在的容器要重新创建才会套用新限制。如果你是运维一台已经在跑的机器,可以先用 truncate -s 0 清空某个容器的超大日志文件,再改配置,然后让容器漂移重启。
另外提一句关于镜像源的问题,很多团队会在 daemon.json 里配置镜像加速,这本身没问题,但这个文件里千万不能混入来历不明的配置。每次改动后都要用 docker info 检查一下 daemon 是否正常加载。
4. 旧内核、core dump 与数据库备份的清理
4.1 删除长期残留的旧内核
CentOS 7 每次 yum update 升级内核,老内核并不会自动删除,它会在 /boot 目录下积累多个 vmlinuz、initramfs 文件。升级几次之后,/boot 会变成一个小小的“垃圾场”,而且内核文件很占空间。
先看当前用的内核版本和已安装的内核数量:
bash复制uname -r
rpm -qa | grep ^kernel-
CentOS 7 自带一个工具可以清理旧内核,但前提是你装了 yum-utils:
bash复制yum install -y yum-utils
package-cleanup --oldkernels --count=2
--oldkernels --count=2 的意思是保留最近两个版本的内核,其余全部删除。保留两个版本是为了防备新内核有兼容性问题,万一出问题还能回退到上一个版本。
如果不想装 yum-utils,也可以手动删:
bash复制rpm -e kernel-3.10.0-1160.el7.x86_64
用 rpm -e 删除对应版本后,/boot 下的引导文件会被自动移除。我实际踩过很多次坑,手动删除 /boot 文件而不用包管理器,会导致 rpm 数据库和实际文件不一致,后面 yum 会一直报错。所以内核这种东西,一定要走包管理器,别直接 rm。
4.2 core dump 和临时文件
crash 产生的 core dump 文件往往被很多人忽略,它们默认放在 /var/lib/systemd/coredump 目录下。一个 core dump 动辄几百 M,如果某个服务反复崩溃,这个目录会迅速膨胀。
查看有哪些 core dump:
bash复制coredumpctl list
清空全部 core dump:
bash复制coredumpctl clean
它支持按时间清理,比如只保留最近 7 天的:
bash复制coredumpctl clean --since=7d
另外 /tmp 和 /var/tmp 也需要定期扫一眼。CentOS 7 自带的 systemd-tmpfiles 默认会清理 10 天前的 /tmp 文件,但如果你手动在里面放过大型安装包、临时导出文件,它不会马上帮你清。我一般习惯手动检查:
bash复制find /tmp -type f -mtime +10 -delete
这个命令会删除 /tmp 下最后修改时间超过 10 天的普通文件,对目录和软链接不会误删,安全性相对高。
4.3 数据库备份不能只做备份不设清理策略
看到热词里有 xtrabackup 2.4 在 CentOS 7 上安装,我必须多说一句:用 xtrabackup 做 MySQL 周期全备的机器,简直就是系统盘清理的“重灾区”。xtrabackup 2.4 备份出来的文件是物理文件,一个不大不小的库全备出来就是好几个 G,如果每天一个全备又没做清理,一个月下来系统盘直接爆掉。
如果你也用 xtrabackup 或类似工具做备份,请一定给备份目录配一个清理策略。最实用的就是 find 按时间删:
bash复制find /backup/mysql -name "*.xb.gz" -type f -mtime +7 -delete
find /backup/mysql -type d -mtime +7 -empty -delete
第一条删除 7 天前的备份压缩包,第二条删除已经清空的残留目录。结合 crontab 定时执行,就能做到“只留最近一周的备份”。
MySQL 自身的 binlog 也是系统盘杀手,尤其是主从复制中断或者从来不做 purge 的情况下,binlog 会无限制增长。可以在 MySQL 里设置过期时间:
sql复制SET GLOBAL expire_logs_days = 7;
生产环境建议直接写进 my.cnf:
ini复制[mysqld]
expire_logs_days = 7
设置之后,MySQL 在写入新 binlog 时会自动清理 7 天前的旧文件。不过要提醒一句,binlog 过期清理会受从库复制进度影响,提前确认好从库已经追平,别因为清理 binlog 把复制链路弄断。
5. 实操中常见的疑难杂症与排查技巧
5.1 文件删了但 df 空间不变?
这个现象几乎每个运维都遇到过:发现一个超大日志文件,rm 掉了,结果 df -h 一看,可用空间一点没变。原因很简单,有进程还持有这个文件的文件描述符。Linux 删除文件时,如果文件正被进程打开,文件并不会真正释放,它会一直占用磁盘,直到进程关闭这个文件描述符。
排查方法是用 lsof 找出被删除但仍在被占用的文件:
bash复制lsof +L1
+L1 参数会列出所有 link count 为 0、但仍有进程打开着的文件。找到进程号后,要么重启对应服务,要么正常终止进程让内核回收空间。这一步处理完,df 才会真正降下来。
我记得有一次 nginx 日志文件被我们手动 drop 之后,access.log 文件大小一直是 0,但磁盘空间根本没回来,排查后才发现是 nginx master 进程还握着旧日志文件的句柄。最后执行 nginx -s reopen,日志文件句柄重新打开,空间立即释放了。
5.2 df 和 du 统计对不上
还有一种常见情况:df -h 显示 / 用了 80%,但把 / 下面所有目录的 du 加起来只有 50%。中间的差值有相当一部分就是“已被删除但仍被进程占用的文件”。这类文件在 du 的目录统计里已经不出现了,但 df 统计的是文件系统实际已分配块,所以两者对不上。
面对这种情况,不要急着删任何东西,先用 lsof 完完整整查一遍所有 deleted 文件:
bash复制lsof | grep deleted
把所有显示为 deleted 的文件找出来,按占用空间排序,再一个个处理。这个方法几乎能解决 90% 的“空间去哪了”问题。
5.3 inode 耗尽的情况
有个冷门但很致命的问题:磁盘空间还剩不少,但所有进程都报 No space left on device,一查 df -i,inode 使用率 100%。这种情况多见于某个目录下堆积了海量的小文件,比如邮件队列、Tomcat 临时目录、没限制粒度的日志轮转目录、Docker 的临时层等。
快速定位哪个目录文件数量爆表:
bash复制find / -xdev -type d | while read d; do count=$(ls -A "$d" 2>/dev/null | wc -l); if [ "$count" -gt 10000 ]; then echo "$count $d"; fi; done
这条命令稍微有点慢,但能把文件数超过一万的目录都列出来。找到元凶后,批量清掉旧文件即可。记住,inode 是文件系统创建时就定死的,没有特别好的扩容办法,唯一出路就是删文件。
5.4 我常用的收尾习惯
清理完系统盘之后,我一般会顺手做三件事:打一个快照、重启一次服务、观察三天磁盘曲线。
如果你这台机器是 VMware 虚拟机,清理前先打个快照,出问题还能秒回滚,这个习惯能救你很多次。清理完成后,systemctl restart rsyslog systemd-journald docker 这些服务最好都重启一遍,确认日志句柄全部重新打开,避免清理过程中留下隐患。最后用 df -h 记录一下清理后的基准值,后面每天看看磁盘增长速度,如果持续走高,说明还有没堵上的源头,继续按前文的思路排查。
我在实际运维中最大的体会是:系统盘清理从来不是一锤子买卖,比清理动作更重要的是把清理策略自动化。yum clean、journald 上限、logrotate、Docker prune、备份保留周期,这五样东西写成脚本挂到 cron 里每天跑一次,比你每个月想起来才手动处理一次踏实得多。CentOS 7 到这个阶段,稳定压倒一切,把能预判的问题提前解决,后面你才能睡得着觉。
