帮某项目组处理一台 CentOS 7 开发机的时候,又双叒叕遇到了那个经典问题:应用开始报磁盘写入失败,df -h一看,根分区/直接 100%,再往下翻一行,/home还剩好几百 GB 空闲。这种画面我在各种机房见了太多次,原因几乎百分之百是装机时贪省事选了自动分区,系统把所有大头都丢给了/home,/只分到了刚好能开机的那一丁点空间。
这次趁着处理完,我把完整的操作链路整理成一篇记录。文章会分成五个部分:先讲清楚问题是怎么来的、动手前要确认哪些条件、然后分别讲 ext4 和 xfs 两种文件系统下“缩小 /home”的两种路线、再把空间归还给 root 分区,最后复盘几个我踩过的坑。这次用的是 CentOS 7 + LVM 的标准场景,适用于所有“/home 太大、/ 不够用”的服务器。
1. 为什么 root 分区会被塞爆:默认分区方案留下的坑
先说结论:CentOS 7 安装程序默认的自动分区逻辑,会把绝大部分磁盘空间切给/home,/通常只有 50GB 左右。这在个人桌面机上其实还好,家目录放文件嘛,理所当然;但放到服务器上就非常尴尬——数据库数据目录在/var/lib,容器镜像在/var/lib/docker,日志在/var/log,Nginx 站点在/usr/share/nginx或者/var/www,这些全都在/下面。
于是就会出现一个很分裂的画面:/已经红了,连日志都写不进去,/home却白白空着几百 GB。我见过最夸张的一台,磁盘 1.2TB,/home分到了 1.1TB,实际只用了不到 20GB,而/只有 50GB,跑了一个 MySQL 实例之后三个月就满了。
这里有一个关键前提需要先明确:CentOS 7 的默认文件系统是 xfs。xfs 支持在线扩容,但是不支持缩容——无论在线还是离线都不行。所以“缩小 /home 扩展 root”这件事,在 CentOS 7 上通常有两种做法:
| 情况 | 处理方式 | 操作复杂度 |
|---|---|---|
/home文件系统是 ext4 | 可以直接缩容:resize2fs缩小文件系统,再lvreduce缩小逻辑卷 | 中等,不需要动数据 |
/home文件系统是 xfs(CentOS 7 默认) | 不能直接缩,只能“备份数据 → 删除逻辑卷 → 重建小容量逻辑卷 → 恢复数据” | 较高,要搬数据 |
文章后面的第 3 节会分别给出两条路线的完整命令。不管走哪条路线,核心原理都一样:LVM 逻辑卷的容量调整,必须在文件系统层面先确认安全,再动块设备层。
现在先别急着操作,下一节说的准备工作看一眼,能帮你少走好几个小时的弯路。
2. 动手前必须确认的六件事:文件系统、LVM 结构与备份
很多第一次调整分区的朋友,上来就umount /home,然后发现根本卸载不掉;或者对着一个 xfs 文件系统敲resize2fs,直接被工具怼回来“不支持”。问题几乎都出在准备工作没做全。建议按下面六项检查一遍,花不了十分钟,但能避免绝大部分幺蛾子。
2.1 确认文件系统类型:这决定你走哪条路线
判断/home和/的文件系统类型,一条命令就够:
df -hT输出里Type列如果显示xfs,直接跳到第 3 节的 xfs 重建路线;如果显示ext4,那恭喜你,可以用更优雅的在线缩容方式。
再顺手看一眼逻辑卷的设备路径:
lsblk pvs vgs lvsCentOS 7 默认卷组名可能是centos,也可能是cl,看你安装时的版本。逻辑卷一般有三个:root、home、swap。我们要动的只是home和root,swap不用管。
2.2 确认 /home 实际占用了多少空间
du -sh /home这个数字决定了三件事:备份要多长时间、重建之后/home应该给多大、以及整个维护窗口要留多久。比如你看上去/home有 400GB,但实际数据只有 20GB,那重建之后把/home切成 50GB 就非常宽裕了。
2.3 查清谁在占用 /home
umount报target is busy是非常常见的问题,根源就是有进程的文件句柄还挂在/home下面。先下手为强:
fuser -mv /home lsof +D /home如果有输出,确认这些进程是什么业务,需要的话提前停掉,或者和相关负责人约定维护时间。不建议在没确认的情况下直接kill,尤其生产环境上有用户正在操作文件的情况。
2.4 备份:没有备份就不要动分区
这句话说再多都不嫌多。备份方式我比较推荐rsync,因为可以断点续传、可以保持属主权限和时间戳,还能用--exclude排除不想搬的目录:
mkdir -p /data_backup/home rsync -aAXv /home/ /data_backup/home/-a是归档模式,-A保留 ACL,-X保留扩展属性,-v显示进度。备份目标务必保证不在/home里面,否则就是左手倒右手。
备份完再验证一下两边文件数是否一致,或者至少看一眼 rsync 的退出码和最后几行输出,别备份完就默认成功了。
2.5 确认 fstab 里的挂载方式
cat /etc/fstabCentOS 7 安装器默认在 fstab 里用的是/dev/mapper/卷组名-逻辑卷名这种路径,比如/dev/mapper/cl-home。如果你发现自己的系统里写的是UUID=xxxx,那在第 3 节重建逻辑卷之后,必须去 fstab 里更新新的 UUID,否则重启必然进紧急模式。这个坑后面第 5 节我会详细复盘。
2.6 规划好目标容量
先算账,再动手。举个例子:磁盘 500GB,当前分配是/50GB、swap8GB、/home442GB,/home实际用了 20GB。我计划让/home保留 50GB,剩余空间全部给/,那么:
- 需要从
/home释放:442 − 50 = 392GB /扩容后:50 + 392 = 442GB
这个账算清楚之后,后面lvextend到底用-L +392G还是-l +100%FREE,心里就有数了。
3. 缩小 /home:ext4 直接缩容与 xfs 重建两种路线
现在进入正题。按第 2 节确认的文件系统类型,二选一。
3.1 ext4 文件系统:真正意义上的“缩小”
如果你的/home是 ext4,操作序列是:卸载 → 检查 → 缩文件系统 → 缩逻辑卷 → 重新挂载。完整命令如下:
# 1. 卸载 /home umount /home # 2. 强制检查文件系统,缩容前必须做 e2fsck -f /dev/mapper/cl-home # 3. 先把文件系统缩小到目标大小,注意单位是文件系统块大小 resize2fs /dev/mapper/cl-home 50G # 4. 再把逻辑卷缩小到同样大小 lvreduce -L 50G /dev/mapper/cl-home # 5. 重新挂载 mount /home # 6. 确认 df -h /home为什么顺序必须是“先缩文件系统,再缩逻辑卷”?我打个比方:逻辑卷是房子的外墙,文件系统是墙里面的装修和家具。你要把整个房子改小,一定是先把家具规整好、把里面的隔断拆掉,再砌外墙;反过来先推墙,家具全压坏了。具体到技术层面,逻辑卷缩小之后,块设备尾部被截断,如果文件系统的元数据还引用着那部分空间,数据就彻底找不回来了。
resize2fs指定的目标大小必须要能装下现有数据。比如/home用了 20GB,你直接缩到 10GB,工具会明确拒绝并报错。另外提醒一句,resize2fs括号里的单位默认是文件系统块大小,直接写50G这种带单位的写法是支持的,不用自己去换算成块数。
3.2 xfs 文件系统:备份 → 删卷 → 重建 → 恢复
CentOS 7 默认 xfs 的情况更常见。xfs 这个文件系统在设计上偏重大型文件和高并发场景,支持在线增长,却不提供任何“缩小”的能力,官方工具只有xfs_growfs,没有xfs_shrinkfs。所以遇到 xfs 的/home,别浪费时间尝试缩容了,直接走重建路线。
完整步骤如下:
# 1. 全量备份 /home 数据 rsync -aAXv /home/ /data_backup/home/ # 2. 卸载 /home umount /home # 3. 删除原来的 home 逻辑卷(数据已经备份,确认无误再执行) lvremove /dev/mapper/cl-home # 4. 重建一个更小的 home 逻辑卷,比如 50G lvcreate -L 50G -n home cl # 5. 格式化新的逻辑卷 mkfs.xfs /dev/mapper/cl-home # 6. 挂载并恢复数据 mount /home rsync -aAXv /data_backup/home/ /home/这里有几个细节:
lvremove这条命令,敲下去之前真的要三思。系统会问你Do you really want to remove active logical volume cl/home?,默认输入y就真删了。没有备份的前提下,千万不要执行这一步。
重建逻辑卷之后,/dev/mapper/cl-home这个设备路径本身不会变,但文件系统的UUID 会变。如果 fstab 里写的是设备路径,那可以不用改;如果写的是 UUID,必须用blkid /dev/mapper/cl-home查出新 UUID 更新进去。
mkfs.xfs默认参数对大部分场景够用,不必专门调-s、-b这些参数。如果你知道未来这个/home要存大量小文件,可以考虑加上-n ftype=1,不过 CentOS 7 的 mkfs.xfs 默认已经开了,不需要额外操作。
3.3 为什么 xfs 不能直接缩容?
顺便把原理说透。xfs 使用 B+ 树组织空闲空间,分配和释放的粒度是 extent(连续块区域),这种设计在顺序大文件写入时效率极高,但也让“收缩”变成一个非常复杂的事——要把文件数据从后面往前面搬,还要保证 B+ 树节点、空闲空间索引、实时设备等一系列结构一致更新。官方干脆没做这个功能。相比之下 ext4 的块位图设计天然支持缩容,所以resize2fs能双向调整。
这也是为什么在生产环境里,如果预计以后分区容量要来回调整,很多老手宁愿把文件系统格式化成 ext4,也不愿意用 xfs。但在 CentOS 7 上根分区默认是 xfs,扩展没问题,缩容就只能靠重建。明白这个机制之后,你再遇到“xfs 能不能缩”这类疑问,心里就有底了。
4. 归还空间给 root:扩展逻辑卷与文件系统的完整流程
/home缩小之后,释放出来的空间回到了卷组(VG)这个“池子”里。接下来就是把池子里的水引到 root 逻辑卷。
先看一眼卷组的剩余空间:
vgs vgdisplay输出里 Free 那项就是当前可分配的空间。如果你的操作是“完全删除了 lv_home”,那 Free 就等于原 home 的完整大小;如果做了“ext4 缩容”或者“重建了更小的 xfs home”,那 Free 就是两者之差。
4.1 扩展 root 逻辑卷
两种写法二选一:
# 写法一:指定容量精确增加 lvextend -L +392G /dev/mapper/cl-root # 写法二:把卷组所有剩余空间全部给 root(最省事) lvextend -l +100%FREE /dev/mapper/cl-root-L +392G是增加 392GB,注意那个加号。不带加号写成-L 392G的意思是把逻辑卷直接设置成 392GB,如果当前 root 已经 50GB,那这等于让它缩回去,非常危险。日常操作我习惯用-l +100%FREE,直接把所有剩余都划给 root,省得算来算去。
4.2 扩展文件系统:xfs 与 ext4 各一步
逻辑卷扩容之后,上面的文件系统还不知道这件事,需要单独通知它。按文件系统类型操作:
xfs 文件系统(CentOS 7 默认):
xfs_growfs /xfs_growfs接的是挂载点,也可以接设备路径,比如/dev/mapper/cl-root。我推荐直接写挂载点/,简单直观。
ext4 文件系统:
resize2fs /dev/mapper/cl-rootresize2fs不带参数时默认扩展到逻辑卷的全部可用空间,所以不用写大小。
实际上在 CentOS 7 上,lvextend命令支持-r参数,意思是扩容逻辑卷的同时自动扩展文件系统:
lvextend -r -l +100%FREE /dev/mapper/cl-root-r会自动调用xfs_growfs或resize2fs,省一步操作。不过我更习惯手动分开执行,因为能看到每步的输出,万一挂了也知道卡在哪一步。这个看个人习惯,没有谁对谁错。
4.3 完整操作顺序总结
以上面的 500GB 磁盘例子为例,完整流程连起来是这个样子:
# 阶段一:备份好数据 # 阶段二:处理 /home(假设 xfs) umount /home lvremove /dev/mapper/cl-home lvcreate -L 50G -n home cl mkfs.xfs /dev/mapper/cl-home mount /home rsync -aAXv /data_backup/home/ /home/ # 阶段三:扩展 / lvextend -l +100%FREE /dev/mapper/cl-root xfs_growfs / # 阶段四:验证 df -h / df -h /home lvs跑完之后,df -h应该能看到/大幅增长、/home刚好是你规划的大小。这里我建议顺手看一眼vgs,确认卷组剩余空间已经归零或者只剩一点碎片空间,避免出现“空间明明释放了但没分给 root”的悬空状态。
4.4 重启验证与 fstab 检查
在reboot之前,先执行一次:
mount -a这个命令按照 fstab 重新挂载所有条目,如果配置有问题会当场报错,而不是等你重启之后才在开机流程里炸。确认没有报错之后再重启,重启完第一时间再跑df -h和lsblk,确认两个分区都正常挂载。
一个非常容易被忽略的点:如果你重建了/home逻辑卷并且 fstab 里写的是UUID=,重启之后系统会直接进紧急模式(emergency mode),提示找不到 UUID 对应的设备。排查方式后面第 5 节会讲。
5. 踩坑复盘:fstab、残留进程和误操作的修复经验
最后这部分是纯经验。下面几个问题,全是我自己或者身边同事在类似操作里真实遇到过的,写出来给各位提个醒。
5.1 重建逻辑卷后忘记改 fstab,重启进紧急模式
那次是某台测试服务器,我删掉 xfs 的lv_home重建之后,只记得挂载和恢复数据,忘了查 fstab 里写的是不是 UUID。重启之后系统直接进不了正常模式,屏幕上一排[ TIME ] Timed out waiting for device dev-disk-by\x2duuid-xxxx.device。
排查思路:在系统卡住的界面选进入救援模式或者直接看日志。进入 shell 之后,用blkid /dev/mapper/cl-home拿到新 UUID,再vim /etc/fstab改掉,改完mount -a验证即可。
如果 fstab 里本来就是/dev/mapper/cl-home这种设备路径,就不会有这个问题。这也是我后来给新装机系统做配置时的一个习惯:fstab 里的逻辑卷一律用设备路径,不用 UUID,因为 LVM 设备路径天然稳定,重建逻辑卷后路径不变,少一个麻烦。
5.2 umount 提示 target is busy,杀错进程是更大的坑
umount /home报target is busy的时候,先用fuser -mv /home看清楚罪魁祸首是谁。我有一次图省事直接对这个目录里的进程一顿操作,结果发现那个进程是某用户正在跑的训练任务,数据实时写在这个目录下,强杀之后任务报废,还连累备份数据不一致。
正确做法是:先列出来,判断哪些进程可以停、哪些要等业务方处理,再决定继续。如果只是临时挂载卡住,也可以先用:
umount -l /home-l是 lazy unmount,先断掉挂载关系,等所有进程释放文件句柄后真正卸载。但 lazy 模式对后续的lvremove并不友好,因为lvremove会吐槽设备正忙,所以生产环境不建议把 lazy 当常规手段,只适合救急。
5.3 备份目录放错了位置,rsync 把自己绕进去了
有个经典低级错误:创建备份目录时,把它建在了/home里面,比如/home/backup_dir,然后在/home还没卸载时执行rsync /home/ /home/backup_dir/。结