“根目录空间不足”这个告警,凡是做服务器运维的人早晚都会撞上。尤其是当你手里管着几台CentOS 7或者Ubuntu虚拟机,某天登录上去准备照常部署服务,结果发现命令敲下去就报错,连日志都写不进去,系统里到处飘着“No space left on device”。这种时候如果你还不太清楚根目录和磁盘分区之间的区别,很可能会在排查的路上绕很久。
这篇文章就围绕“检查根目录空间”这件事,把从最基础的查看命令,到定位大文件、清理垃圾、给根分区扩容、排查疑难问题的完整链路都讲一遍。适用对象不只是专职运维,也包括自己折腾Linux虚拟机、搞Android开发时发现SD卡根目录无法授权、开Word提示“内存或磁盘空间不足”这类桌面环境问题的普通用户。内容不涉及复杂底层原理,更多的是大家实际踩坑后的经验汇总,按步骤操作一般都能解决。
1. 先搞明白:根目录空间为什么总是最先告急
在日常工作中我见过不少新手一上来就执行df -h /,看到使用率90%就直接慌了,然后漫无目的地删文件。但真正要解决“根目录空间不足”,先得弄清楚根目录在这台机器上究竟处于什么位置,以及它为什么那么容易被塞满。
1.1 分区、挂载点和目录,三者经常被搞混
很多“根目录空间不足”的排查过程之所以绕弯路,就是因为在概念上把分区、挂载点、目录这三件事黏在了一起。
磁盘接入系统后,我们会对它做分区,比如一块500G的硬盘可以划分成/dev/sda1、/dev/sda2。但分区本身是不可以直接使用的,系统需要把它挂载(mount)到某个目录上,这个目录就被称为挂载点。比如提前规划好的场景下,/dev/sda1挂载到/boot,/dev/sda2挂载到/,/dev/sda3挂载到/home。你平时看到df -h输出里的每行记录,本质是某个文件系统(也就是已格式化并挂载的分区)的使用情况。
所以当我们说“检查根目录空间”,真正检查的对象是挂载到/目录的那个分区,而不是路径/本身。路径/只是一个入口,只要系统在运行,它一定存在;但承接它背后数据的那个文件系统如果满了,访问就会出问题。这个区别很重要,因为它决定了排查时该去df看挂载点,而不是拿du去扫陌生目录。
另外有个非常常见的误导点:你以为某个目录的数据在根分区下,实际上它可能被单独挂载到另一块磁盘上了。比如很多公司会把/home、/data、/var/lib/mysql单独挂载,因为它们的增长量非常可预测。当你发现根目录空间快满了,先去看一眼挂载情况,别急着删根分区下的文件,避免删完发现根本没删到正主。
1.2 根目录被塞满的常见“元凶”
做过一段运维的人都会有这种体会:根分区只要规划时没留够余量,用满只是时间问题。从实际经验看,最常见的填充来源集中在下面几类:
一类是各类日志文件。系统日志、应用日志、容器日志、数据库日志,增长速度快得惊人。比如一个没有配置日志轮转的Nginx访问日志,高峰期一天写几个G不是新鲜事;Java应用如果不做日志切割,一次异常反复打印堆栈,几天就能把分区写满。
另一类是软件包缓存和临时下载文件。CentOS 7下yum默认会把下载的rpm包缓存在/var/cache/yum,Ubuntu下apt的缓存在/var/cache/apt;Docker镜像和容器日志默认都堆在/var/lib/docker;还有临时目录/tmp里的文件,看起来不起眼,但例如一个长期运行的脚本崩溃前留下的几G临时转储文件,就能把空间耗掉一大截。
还有一类是用户数据误放到系统分区。比如有的应用默认把数据写入/opt,有的数据库直接装在根分区下,有的同事习惯把大文件解压到/root或/tmp再转移,结果一次性文件堆积多了,根分区就被吃满了。
搞清楚这些“元凶”会直接影响后续清理的路径选择,别一上来就盯着日志目录删,先把盘子里的数据分布看清楚,再动手。
2. 五分钟定位根目录空间消耗大户
检查根目录空间这件事,看起来简单,就是看一个使用率数字,但深入一点,你需要回答两个问题:当前可用空间还剩多少?根目录下到底是哪些目录吃掉了空间?别觉得这两个问题是一回事,分区使用率只告诉你结果,目录占用明细才能指导你下一步动作。
2.1 第一手检查:df命令的完整解读
最基础的命令是df -h。-h参数让输出以人容易读懂的格式显示,比如K、M、G这样的单位,实际执行后你看到的结果像这样:
Filesystem Size Used Avail Use% Mounted on /dev/sda2 50G 47G 2.1G 96% / tmpfs 3.9G 0 3.9G 0% /dev/shm这一行信息里,Size是分区总大小,Used是已使用量,Avail是当前用户可用的空间,Use%是使用比例,Mounted on是挂载点。
我一般会顺手多加几个参数:df -hT里的T用来显示文件系统类型,因为ext4、xfs、overlayfs的处理方式在不同场景下是有区别的;df -i则是查看inode使用情况,这是很容易被忽略的一点,后面单独讲。
一个比较容易被忽略的操作是:检查根目录空间的时候,别只盯着挂着/的那行,最好顺手看下所有挂载点的使用率。这能帮你确认一个问题——根分区满了,别的地方还有富余吗?如果/home挂载在独立分区且有大量空闲,那你清理策略的重心就可以放在迁移数据而非删除数据上;如果所有分区都快满了,那就真要考虑扩容或者加盘了。
另外补充一个细节:root目录下的空间和普通用户目录下的空间,很多时候并不是同一块区域。当/home被独立挂载到专门的数据盘时,普通用户文件写满那个盘,和根分区并没有直接关系;反过来也是一样。所以每次做根目录空间检查,我都会强调“先看清挂载结构,再谈空间状况”。
2.2 用du命令快速定位谁占了你的根目录
df告诉你的是结果,du才能告诉你原因。定位根目录下哪个子目录占用最多,常用的是下面这条:
du -h --max-depth=1 / 2>/dev/null | sort -hr | head -20--max-depth=1的意思是只显示/直属下第一层子目录的大小,不会无限往深层递归,这样输出的结果一眼就能看完。sort -hr是按人类可读数值反向排序,占空间最大的目录排在最上面,这是一个很顺手的小技巧,因为du默认输出顺序是随机的,直接配sort才能看得舒服。2>/dev/null则是把权限不足一类的报错丢掉,避免刷屏。
执行完成后,你会看到类似这样的输出:
18G /var 12G /usr 5.2G /home 2.3G /opt 1.8G /root这时候思路就清晰了,接下来钻进/var这个目录,继续执行同样的套路,逐层往下剥。绝大多数情况下,三层以内就能找到真正的“空间刺客”。
这里要提醒一下:du扫描大目录会花一些时间,比如/下如果有海量小文件,可能要跑几十秒甚至几分钟。有些朋友遇到根分区快满时急着要结果,看到扫描慢就不耐烦,其实可以缩小范围,比如怀疑是容器日志,就直接du -h --max-depth=1 /var/lib/docker/containers 2>/dev/null | sort -hr | head,指向性更强,速度也快得多。
实际排查中还有个高频场景:你删了很多日志却发现空间没释放。这个问题的原因之一是删文件时对应进程还开着句柄,也就是文件虽然从目录里移除了,但进程仍然占着它,空间要等进程释放才会归还。这种情况最典型的就是删除了一个正在被Java进程写入的日志文件,明明du看到文件不见了,df却显示空间一点没少。解决办法是找到那个文件对应的进程并重启它,或者直接lsof | grep deleted查一下哪些是已删除但仍被占用的大文件。
2.3 inode被占满的隐蔽情况
有些时候,根分区的df -h显示还有几十G可用,但应用就是报“No space left on device”,甚至创建文件都失败,这种诡异现象十有八九是inode耗尽了。
Linux文件系统存储文件时,除了数据本身,还需要记录文件元数据,例如权限、属主、大小、时间戳等。这些元数据存放在一个叫inode的结构里。每个文件和目录都会占用一个inode,分区容量固定,inode数量也是格式化时就算死的。如果分区里塞满了大量小文件,比如某个程序生成了几百万个缓存小文件、邮件系统的邮件队列堆积,那么即使总字节数没超过分区容量,inode可能已经先用完了。
检查方法非常直接,执行df -i:
Filesystem Inodes IUsed IFree IUse% Mounted on /dev/sda2 3276800 3276780 20 100% /看到IUse%到了100%,就基本可以锁定是inode耗尽。继续定位的方式和之前类似,用du找大目录,但真正要找的是“文件数量最多”的目录,此时可以用find来辅助:
find / -type f -printf '%h\n' 2>/dev/null | sort | uniq -c | sort -rn | head -20这段命令会把所有文件的所在目录统计出来,按文件数量倒序排列,很快能找出程序疯狂创建小文件的目录。找到后用确认清理即可。
提示:inode耗尽的现场通常发生在邮件系统、消息队列、缓存类应用上。清理时如果只是删一条队列里的消息,批量删除建议用
find /目录 -type f -delete,如果文件数量极大,一条条删会等到天荒地老。
3. 根目录空间不足后的两条路:清理与扩容
如果是临时告警,优先做安全清理;如果是业务持续增长导致的常态问题,那就得做扩容。这两条路线针对不同情况,很多人只走其中一条,导致过段时间又满,这次我给一个完整的选择框架。
3.1 安全清理:日志、缓存与临时文件一手抓
在动手清理前,强烈建议先看一眼有没有正在写入大文件的进程,避免做无用功。可以通过lsof +L1找出那些被删但仍被占用的文件,如果有,先处理对应进程。清理顺序我按安全性从高到低排一下,亲测有效:
第一类最安全,也最该优先清理的是各类缓存。CentOS 7下执行:
yum clean allUbuntu下则是:
apt-get clean rm -rf /var/lib/apt/lists/*这两条命令清理的是系统更新时下载的软件包缓存,不会影响已安装的软件,清理出来的空间视机器使用频率而定,少的几百M,多的时候能到几个G。
第二类是系统日志。但注意日志不能直接一股脑全删,生产环境的日志有时候还有追溯排查价值。更稳妥的做法是找到一个日志量最大的文件,确认里面的内容不再需要追溯后清空它,而不是删除它。原因很简单,相关进程还可能继续在写这个文件,如果删了它,进程不会自动重建,反而可能造成异常:
> /var/log/messages journalctl --vacuum-time=3djournalctl --vacuum-time=3d这条尤其值得记下来,它只保留最近3天的systemd日志,之前的总量大,清理后的效果非常明显。对于旧日志,也可以提前配置logrotate,让日志按天分割、按数量保留,这个在文末会提。
第三类是Docker垃圾,这个经常是根分区爆满的主因。在一台装了Docker且频繁更新镜像的机器上,镜像和容器日志占掉几十G非常正常:
docker system prune -a这条命令会清理所有未被容器使用的镜像、网络和构建缓存,执行前它会提示你确认。如果只是想清理日志文件,可以进到/var/lib/docker/containers/*/目录看那些*-json.log文件,直接清空对应日志即可:
cat /dev/null > /var/lib/docker/containers/容器ID/容器ID-json.log第四类是/tmp和/var/tmp目录。长期运行的机器上总是堆了一些临时文件,重启相关服务后不再被引用的临时文件就可以清掉。需要提醒的是别手滑用rm -rf /这种恐怖写法,建议只清空指定目录:
find /tmp -type f -atime +7 -delete-atime +7表示访问时间在7天前的文件,这条命令执行前最好先find /tmp -type f -atime +7 | wc -l统计数量,确认没误伤当前正在使用的临时文件。
3.2 动态扩容:给CentOS 7和Ubuntu虚机扩展根分区
如果清理完之后空间使用率还是长期维持在80%以上,就说明这台机器容量规划本身有问题,与其反复清理,不如直接扩容。这里分两种常见情况,一种是虚拟机场景,比如VMware、KVM下挂载的虚拟磁盘,另一种是云主机场景(通过控制台扩容云盘)。核心步骤其实是相通的。
先说底层逻辑。大多数Linux安装时默认采用LVM管理分区,也就是把物理分区(PV)加入卷组(VG),再在卷组上切出逻辑卷(LV),最终格式化并挂载。所以扩容的链路是:先扩充物理磁盘 -> 让系统识别到新空间 -> 扩充PV -> 扩充LV -> 扩充文件系统。每一步执行完成后,都可以通过对应命令验证空间是否生效。
举一个CentOS 7虚拟机扩容根分区的典型例子。假设根分区对应的逻辑卷是/dev/mapper/centos-root,卷组名是centos,执行前先确认现状:
df -h / vgdisplay pvdisplayvgdisplay里能看到卷组当前空闲空间(Free PE / Size),如果空闲空间有富余,直接走“扩充LV”这一步即可;如果没有,就先加物理磁盘,然后执行pvcreate把新磁盘加入卷组。虚拟机加盘后在系统里未必马上出现,可以执行partprobe或者直接重启让系统识别。
扩容逻辑卷的命令如下:
lvextend -l +100%FREE /dev/mapper/centos-root这条命令把卷组里所有剩余空间全部划给根分区对应的逻辑卷。但到这一步还没结束,系统分区并不会自动变大,还要让文件系统感知新空间。ext4和xfs的方法有区别,CentOS 7默认可能是xfs:
xfs_growfs /如果根分区是ext4,则执行:
resize2fs /dev/mapper/centos-rootUbuntu下的流程基本一样,区别只在于卷组名和逻辑卷名的具体值不同,执行lvdisplay就能看到当前VGT和LVT名称,套进去即可。扩容完成后用df -h /复查,逻辑卷容量和文件系统容量应该都变大了。
注意:执行扩容操作前,强烈建议先给虚拟机打快照或做数据库备份。虽然LVM扩容本身风险不高,但这件事情属于底层磁盘操作,万一中间断电或操作失误,数据安全永远要放在第一位。
3.3 桌面环境与开发工具的特殊场景
标题热词里还有一个容易被忽略的部分:当你电脑的分区满了,桌面应用也会出各种奇怪问题,比如热词里提到的Word提示“内存或磁盘空间不足,无法显示所请求的字体”,Android Studio无法对SD卡根目录授权,微信的文稿数据在根目录堆积等等。这些场景其实归结为一个共同点:它们的临时文件、缓存、用户数据大多存放在系统盘(Windows下是C盘,Linux桌面环境下挂在根目录)。
以Windows为例,如果C盘空间耗尽,Word这类应用连临时文件都创建不出来,于是给出“内存或磁盘空间不足”这种看起来莫名其妙、其实只是因为临时落盘失败的报错。这时候一般先清理%TEMP%、C:\Windows\Temp,再把微信文档图片缓存、Android SDK缓存清理一下,重新启动软件基本就恢复正常了。如果你在系统设置里看到“压缩此驱动器以节约磁盘空间”的选项,我个人的经验是不要对系统盘开启压缩,尤其是SSD,开启后性能下降明显,空间收益却很有限,真正治本的办法还是把个人用户目录迁移到其他盘符。
Android开发中“无法获得SD根目录授权”的问题也比较典型。新版本Android对存储权限收得很紧,应用不再自动拥有访问SD卡根目录的权限,需要去系统设置里手动开启“所有文件访问权限”或“USB存储”权限。这和磁盘满没有直接关系,但如果你在Android Studio连接设备时刚好遇到设备存储空间不足,授权窗口也常常弹不出来,卡在一个类似“无法完成操作”的状态。这时候先连接设备清理存储空间,再重新打开授权页,通常就能正常走通了。
4. 实操中容易踩进去的坑和对应解法
前面讲了不少命令和流程,这一节把我在一线运维中反复踩过、也看到同事踩过的坑集中整理一下。这些坑不看文档的话很难一个晚上绕过去,因为它们大多不是报错信息本身有误导,而是现象和原因之间存在一层“中介变量”。
4.1 文件删了空间却没释放,到底为什么
这是最经典的一个问题,日志目录已经删掉了好几个G的文件,df -h显示根目录使用率纹丝不动。我在之前提过原因,这里展开讲一下处理套路。
当你执行rm删除文件时,如果某个进程已经打开了这个文件,且离不开它(比如日志文件、临时数据库文件),文件系统会把这个文件标记为已删除,但实际的磁盘块直到该进程关闭文件句柄后才能释放。你用ls或du检查目录时看不到这个文件,但它的物理空间仍被占用。
排查手段是:
lsof | grep deleted输出结果里会看到删除文件的进程PID和文件路径,对进程服务类的文件,重启服务即可释放;如果对应进程已经不必要了,杀掉进程更直接。还有一种更隐蔽的,是数据库进程在数据文件被“删除”后仍在持续写入,那么重启数据库前请先确认事务完整性,否则容易带来数据丢失风险。
4.2 盲目清理导致误删关键目录
清理空间这件事本身相对安全,但选错目录就另说了。我见过有人为了腾空间,直接把/var/log整个目录删了,以为只是日志,结果服务起不来;也见过有人对/run目录下手,导致系统莫名其妙出现各种异常状态。
正确的姿势是:先du --max-depth=1逐层进入,找到具体文件再处理。清理时尽量以“清空文件内容”代替“删除文件本身”。比如/var/log/messages如果还要用,应执行> /var/log/messages清空,而不是rm。系统目录里有大量文件在创建时并没有考虑“被删除”的情况,删了之后重启才能发现少了依赖,那时再修复就麻烦得多。
4.3 扩容后显示空间没变化的排查思路
扩容操作本身执行完了,结果df -h /还是旧容量,常见原因有以下几种:
第一,扩容的是物理磁盘,但新空间没有创建PV或者没有加入VG。执行lsblk看磁盘大小,再执行pvdisplay看PV大小,如果PV本身没变,说明LVM根本不知道新空间存在,需要先parted或fdisk调整分区表,再pvresize /dev/sdX让PV感知新空间。
第二,LV扩了但文件系统没扩。比如我上面说的,xfs用xfs_growfs,ext4用resize2fs,少执行一步,文件系统容量就不会变。太常见了,甚至老手也偶尔会漏。
第三,系统里存在多个逻辑卷,你扩容的LV并不是/实际挂载的那个。这就又回到我开头强调的“先弄清挂载结构”这点上。出现这种状况,多半是机器上装了多个系统或者有多块系统盘,执行挂载路径时选错了目标。每次扩容前,用df -h /和lsblk -f双向确认,就能避免。
4.4 监控里的“缓存占用”那是无法直接清理的吗
有时候你去看文件空间使用率,发现df显示已用空间很大,但用du把所有目录加起来,数字对不上,多出来几个G甚至十几个G。这通常是因为大文件在被进程持续写入时,文件系统已经分配了空间,但du只统计到被目录引用的块,两者统计口径不同。
还有一种常见情况是挖矿脚本、木马程序或某个失控进程在后台疯狂产生临时文件,你删完一批它又写一批,空间使用率短时间就回来了。这种时候不要只顾着清理,先看进程列表,top里CPU占用异常、lsof +L1后能看到可疑进程,优先处理进程本身,否则清理永远只是安慰自己。
5. 把根目录空间管起来:日常检查的正确方式
前面所有内容都在处理“已经满了之后的急救”,但优秀的运维习惯应该是把问题消灭在“还没满”的阶段。这里分享一些我日常使用的方法,不一定多高大上,胜在简单有效。
5.1 用一条命令快速判断根目录健康度
现在我每台新机器上线,都会先配一个简单的检查脚本,核心就是df加阈值判断:
threshold=80 used=$(df -h / | awk 'NR==2{print int($5)}') if [ "$used" -gt "$threshold" ]; then echo "Warning: root partition usage is ${used}%" fi写成脚本后放进crontab,每天跑一次,或者配合监控系统做告警。比如:
*/30 * * * * /usr/local/bin/check_root_space.sh这么做的好处是,告警发生的时机提前了,给自己留足处置时间,不用每次都从90%以上的高危状态开始救火。另外需要说明的是,这条脚本只是帮你看当前状态,如果是判断增长趋势,建议把每天的df -h /输出重定向到一个文件里,积累一两周就能看到趋势,辅助判断是真增长还是偶发波动。
5.2 提前配置日志轮转,避免日志无限膨胀
大部分日志撑爆根分区的案例,其实可以通过配置logrotate来提前规避。Linux自带logrotate工具,系统日志的轮转配置一般在/etc/logrotate.conf和/etc/logrotate.d/目录下。给应用日志增加一条配置示例:
/var/log/myapp/*.log { daily rotate 7 compress missingok notifempty copytruncate }daily表示按天轮转,rotate 7表示保留7份日志,compress对旧日志做压缩,copytruncate适合进程锁定日志文件的场景,它通过复制+截断的方式避免重启应用。Docker容器日志虽然不走系统logrotate,但也可以在Docker的daemon.json里配置"log-opts": {"max-size": "20m", "max-file": "5"}做限制。配置好后可以执行logrotate -f /etc/logrotate.conf手动测试一遍,确认轮转效果符合预期再放到线上。
5.3 何时清理,何时扩容,我的判断标准
实践中我给自己定了几条简单规则,分享出来供参考:
如果根分区使用率低于80%,只需要留意增长趋势,偶尔做做定向清理。如果使用率在80%到90%之间,建议做一轮常规清理(日志、缓存、Docker垃圾),同时排查是否有异常增长的应用。如果使用率超过90%,优先安全检查是否有进程占用未释放空间,之后果断扩容,不要长期让分区处于高水位,因为这类机器一旦业务量突然上来,很快又会触发新一轮告警。
这里想多说一句自己的判断依据:扩容本身并不复杂,并不是每次都需要扩容,比如还在80%以下徘徊就别折腾底层磁盘了。但到了90%以上,扩容大概率比反复清理更划算,因为清理一次的成本(时间、误删风险)已经超过了扩容成本。
5.4 日常小技巧:给根目录空间做体检时顺手养成的习惯
最后补充几个我在实际检查中养成的习惯,它们更像“肌肉记忆”,关键时刻能少走很多弯路。
第一,用lsblk -f看文件系统类型。别以为你记得每台机器根分区是ext4还是xfs就能大意,环境变了、磁盘换过、挂载方式调整过,实际处理扩容命令会完全不同,这个习惯能省掉试错。
第二,清理之前先看一眼挂载点。特别是find / -type f -size +100M这类全盘扫大文件命令,很可能扫到/mnt、/media下的挂载盘,那里虽然从/路径下看得到,但空间根本不属于根分区,扫出来的结果会误导你。如果只关心根分区,把命令限制在非挂载子目录范围内最稳妥。
第三,检查脚本里加一行df -i /的输出。很多人只看容量不看inode,等报错时才发现是inode满。加进去之后成本很低,但能提前暴露出很多潜在问题。
写在最后的一点体会
做了这么久运维,我最大的感受是:根目录空间告警不是单纯的“删文件”或“加磁盘”的问题,它更像是系统运行健康状态的一个晴雨表。你清理掉了日志,为什么还会反复满,到底是谁在持续写入,是应用没配轮转,还是某个服务在泄露文件句柄,这些才是真正值得关注的底层原因。按我自己的经验,每次处理完告警后别急着收工,花十分钟把“为什么满”和“以后怎么防”想清楚,下次才会更轻松。
如果你手上正好有台CentOS 7或者Ubuntu虚拟机,建议先跑一遍第一节和第二节里的命令,熟悉一下df、du、lsblk的输出格式。不用背命令,多敲几遍自然就记住了。真遇到扩容场景,记得先打快照再动手,稳字当头总没错。