根分区只剩 2G:LVM 在线扩容的完整流程
服务器跑着跑着收到磁盘告警,登上去一看 df -h,根分区 /dev/mapper/vg0-root 只剩 2G 可用。这时候第一反应通常是删日志、清缓存、把旧备份挪走,能撑一撑。但这些都是临时手段,真正的问题是容量规划错了,早该扩容。
好消息是:如果当初装系统时用的是 LVM(大部分云主机镜像默认都是),那扩容可以在线做,不需要停机,也不需要重装。坏消息是:这个过程有六个地方会卡住,每一步做错都可能让机器起不来。这篇按执行顺序把这六步讲清楚。
先说清楚前提:这套流程只适用于 LVM。如果你的根分区是直接挂在 /dev/vda1 上的裸分区,那在线扩容需要 growpart + resize2fs,能扩但受分区表布局限制——如果分区后面紧跟着另一个分区,就没法扩。所以第一步永远是确认结构。
第一步:先确认磁盘、分区、LVM、文件系统四层结构
很多人一上来就 lvextend,结果报「insufficient free space」,因为根本没往卷组里加物理卷。四层结构必须逐层看清楚:
# 磁盘层:物理盘多大,有没有未分配空间
lsblk
# LVM 层:卷组有多少空闲 PE,逻辑卷多大
vgs
lvs
# 文件系统层:实际用掉多少,是什么类型的 fs
df -hT /
findmnt -no FSTYPE,SOURCE /lsblk 的输出要重点看两件事:磁盘总容量,和分区是否用满了整块盘。如果 vda 是 100G 但 vda3 只有 50G,那说明分区层面还有 50G 没划进来,得先 growpart 把分区撑大,才能 pvresize。
vgs 输出的 VFree 列是关键:如果卷组里已经有空闲空间,直接跳到第三步 lvextend 就行,不用碰磁盘和分区。这是最简单的情况,云主机的镜像里经常会预留一点。
df -hT 里的 Type 列决定第四步用哪个工具:ext4 用 resize2fs,xfs 用 xfs_growfs。这两个的区别不只是命令名——xfs 只能扩不能缩,而且必须在挂载状态下扩;ext4 可以扩也可以缩(但缩要先卸载)。搞错的话,扩容会失败,运气不好还会损坏文件系统。
第二步:云盘扩容(这一步在控制台做)
如果 lsblk 显示磁盘本身就只有 100G,而你需要 200G,那必须先在这一层扩容。云主机的做法是在控制台把云盘从 100G 调到 200G,这个操作一般是在线生效的,几秒到几分钟。
关键的验证点:扩容完成后,在系统里 不能 直接看 df,因为 df 看的是文件系统,而文件系统还没扩。必须用 lsblk 或者 fdisk -l /dev/vda 确认内核已经识别到新容量:
lsblk
# 期望看到 vda 从 100G 变成 200G如果 lsblk 还是显示旧容量,有两种可能:一是云盘扩容任务还没真正完成(去控制台看任务状态),二是内核没重新读分区表。可以试 partprobe /dev/vda 或者 echo 1 > /sys/class/block/vda/device/rescan。如果是热插拔新增的盘而不是原有盘扩容,可能根本没有 rescan 接口,这种情况通常只能重启才能识别——所以在线扩容要尽量在原有盘上做,不要用「加一块新盘然后迁移」的方式,那个必然涉及停机。
第三步:把分区撑大(growpart)
这一步只在「分区没占满磁盘」时才需要。判断方法看 lsblk:vda 是 200G,vda3 还是 100G,那就要扩分区。
# 注意语法:磁盘名和分区号之间是空格,不是 vda3
growpart /dev/vda 3
# 确认
lsblk /dev/vda第一次用可能会报 command not found,包名是 cloud-guest-utils(Debian/Ubuntu)或 cloud-utils-growpart(RHEL 系)。这个包很小,装上就行。
这里有个必须提前想清楚的问题:分区号后面的数字必须对应根分区所在的那个分区。有些镜像的分区布局是 vda1 是 BIOS boot、vda2 是 /boot、vda3 是 LVM PV;有些是 vda14 是 BIOS boot、vda15 是 EFI、vda1 是根。用 lsblk -f 看清楚哪个分区是 LVM PV(FSTYPE 显示 LVM2_member),那才是要扩的。
如果报 NOCHANGE: partition 3 is size X, it cannot be grown,说明这个分区已经是磁盘的最后一个分区并且已经占满,检查一下是不是新容量没被识别(回第二步)。
第四步:pvresize 让 LVM 认出新增空间
分区变大了,但 LVM 的物理卷(PV)还记着旧大小,所以卷组的 VFree 仍然是 0。这一步是很多人漏掉的:
# 扩容前先看一眼 PV 大小
pvs
pvresize /dev/vda3
# 再看:PVSize 应该变大了,VFree 应该出现空闲空间
pvs
vgspvresize 是安全操作,只更新元数据,不动数据,速度几乎是瞬间的。如果报 Device /dev/vda3 not found,检查分区名写错没有;如果报 Cannot use /dev/vda3: device is too small,说明分区没扩成功,回第三步。
顺带提一句:如果 pvs 里看到 PV 的 PFree 不为 0 但 vgs 里 VFree 是 0,说明空间在不同 PV 上——多块盘的卷组要把每块盘对应的 PV 都 resize。用 pvs -o pv_name,vg_name,pv_size,pv_free 看清楚。
第五步:lvextend 扩逻辑卷
到这一步终于可以扩逻辑卷了。有两种写法:
# 写法一:加固定大小(推荐,更可控)
lvextend -L +50G /dev/vg0/root
# 写法二:吃掉卷组所有剩余空间
lvextend -l +100%FREE /dev/vg0/root推荐写法一。+100%FREE 看着爽快,但如果这台机器以后还要新建逻辑卷(比如给 Docker 单独分一个),空间就被吃干净了。留一点余量,下次出问题时有腾挪空间。
注意 /dev/vg0/root 的卷组名和逻辑卷名必须和 lvs 输出一致。有的系统是 ubuntu-vg/ubuntu-lv,有的是 vg0/root,还有的是 centos/root。写错会报 Volume group "vg0" not found。
如果报 insufficient free space: X extents needed, but only Y available,说明 VFree 不够你要的量,要么减少 +50G 的数字,要么回第二、三、四步检查是否有环节没做。
第六步:扩文件系统(最容易忘的一步)
lvextend 之后,逻辑卷变大了,但文件系统还是旧的。这时候 df -h 会显示「逻辑卷很大但可用空间没变」,非常容易误判为扩容失败。实际上只是还差最后一步。
# 先确认文件系统类型
findmnt -no FSTYPE /
# ext4
resize2fs /dev/vg0/root
# xfs(必须走挂载点,不是设备名)
xfs_growfs /这两个工具的行为差异很大,必须分清楚:
resize2fs 可以在线扩容(文件系统已挂载),也可以离线。如果不指定大小参数,它会自动扩到设备的最大容量。resize2fs: Nothing to do! 这个提示不是报错,意思是文件系统已经是最大了——通常说明前面某一步没生效。
xfs_growfs 只能在线做,而且参数必须是挂载点(/ 或者 /data),不能写设备名。如果写成 xfs_growfs /dev/vg0/root 会报 not a mounted XFS filesystem。XFS 不能缩容,所以扩之前想清楚要扩多少,扩多了只能扩不能退。
扩完必须验证:
df -h /
# 对比扩容前的 Available 数字,应该增加了大约你要的量如果 df 还是旧数字,别慌,可能是 df 缓存的问题——直接再看一次,或者 sync && df -h /。如果两次都是旧值,说明第六步确实没执行成功。
六个真实踩过的坑
坑一:在 /boot 上做,然后机器起不来。/boot 一般是独立分区,不在 LVM 里。如果 /boot 满了,需要先删旧内核(apt autoremove --purge),而不是扩容。绝对不要把 /boot 在线改成 LVM,grub 找不到内核。
坑二:swap 抢占卷组空间。如果卷组里有一个很大的 swap LV(比如 16G),而你只需要给根分区扩 10G,可以考虑缩 swap。但 swapoff 需要足够的物理内存装下 swap 里的数据,内存不够时 swapoff 会卡死甚至触发 OOM。安全做法是先把 swap 缩到 4G 再 swapoff。
坑三:没做快照就动手。云主机扩容云盘后,先在最危险的 growpart 之前打一个快照。快照很便宜,重装很贵。特别是 growpart 会改分区表,虽然有校验,但断电或异常中断会让分区表损坏。
坑四:把 df 的 100% 当成分区用满。df 显示 Size 比预期的逻辑卷小,是因为 ext4 默认给 root 保留 5% 空间(Reserved block count)。所以 100G 的盘 df 只显示 95G 可用。这不是故障,是设计。tune2fs -m 1 /dev/vg0/root 可以把保留比例降到 1%,但根分区不建议降到 0。
坑五:扩容后 fstab 里的 UUID 变了。这种情况不会发生在 lvextend 上(容量变化不改 UUID),但如果是在控制台重做了云盘,就会变。blkid 确认一下 UUID 和 /etc/fstab 里一致,不一致的话下次重启会进 emergency mode。
坑六:以为扩容也解决了 inode 不足。df -h 显示空间充足但写入报 No space left on device,用 df -i 一看 inode 满了。ext4 扩容时 resize2fs 会同时扩 inode,所以扩完 inode 也会变多——但如果你的问题就是 inode 耗尽,最快的办法是删小文件(find /tmp -type f -mtime +7 -delete),不是扩容。
一套脚本化的检查流程
把上面的步骤整理成一个只读的检查脚本,扩容前跑一遍,心里有数:
#!/bin/bash
# lvmcheck.sh —— 只读,不改任何东西
echo "=== 磁盘/分区 ==="
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT
echo "=== PV ==="
pvs -o pv_name,vg_name,pv_size,pv_free
echo "=== VG ==="
vgs -o vg_name,vg_size,vg_free
echo "=== LV ==="
lvs -o lv_name,vg_name,lv_size,devices
echo "=== 根文件系统 ==="
df -hT /
findmnt -no FSTYPE,SOURCE,OPTIONS /
echo "=== inode ==="
df -i /这份输出的价值在于:它能告诉你「差哪一步」。如果 vgs 的 vg_free 已经是 50G,那直接 lvextend;如果是 0 而 lsblk 的盘已经是 200G,那要 growpart + pvresize;如果盘还是 100G,那得先回控制台扩云盘。
扩容之后建议顺手把结论写进运维笔记:这台机的根分区是 ext4 还是 xfs、卷组名是什么、扩容时用的是哪条命令。半年后再遇到,不用重新推导一遍。
最后是一个提醒:在线扩容不是「不用做容量规划」的借口。如果这台机器三个月内扩了两次,说明真正的问题在别处——可能是日志没有轮转、备份文件堆在本地、或者数据库的 binlog 没设过期。du -xh --max-depth=1 / | sort -rh | head -10 跑一下,看看空间到底被谁吃了,比继续扩盘更有价值。