上周处理一台CentOS 8虚拟机,客户那边跑着一个数据服务,df -h一看根分区已经100%满,业务日志全在报磁盘写入失败。等我登上去执行fdisk -l,虚拟磁盘明明是200GB,可根分区只有100GB,剩下100GB既没有分区也没有文件系统,就那么白白躺在那里。再深查一层,根分区sda1后面还跟着swap分区,分区表是老式msdos,文件系统是xfs,典型的“当年装系统图省事一路下一步”留下的非LVM分区布局。
这种局面的麻烦在于,大家习惯性想用的lvextend在这儿完全失效,只能走“虚拟磁盘扩容 -> 分区扩容 -> 文件系统扩容”这条完整链路。整个过程我踩了几个不大不小的坑,也总结出了一套可以照抄的排障思路。这篇文章就是把这些东西彻底摊开讲,适合VMware Workstation、ESXi、VirtualBox、KVM环境下跑Linux,装系统时没用LVM,现在空间告急又不敢乱动分区的读者参考。
1. 非LVM分区扩容为什么麻烦:先认清你的磁盘布局
1.1 磁盘有剩余但系统用不到,通常是分区表没跟上
遇到这类问题,第一反应是检查设备节点。lsblk看到sda已经200G,但sda1只有100G,说明物理磁盘层面已经被虚拟机平台扩大过了,只是分区表还停留在100G的状态。听起来很简单,扩一下分区表就行,实则不然。
如果是LVM布局,vgextend加lvextend几步就把空间划给逻辑卷了,全程在线,不慌不忙。非LVM环境里,普通分区的边界是写死在分区表里的,操作系统启动时依靠分区表找到每个分区的起始扇区和结束扇区,然后在这个范围内挂载文件系统。你想让文件系统变大,必须先改分区表,把sda1的结束扇区往后推。这就牵出了分区表类型、文件系统类型、分区排列顺序三个硬约束。
1.2 LVM和非LVM的底层差异:为什么lvextend那套失效
用一个生活化类比:LVM像是一个开放办公区,随时可以在一侧加桌子,甚至可以把隔壁房间打通扩容,因为墙不是承重墙;非LVM像是固定隔断的老办公室,你要扩大某个房间,就得动承重墙结构,而这个结构就是分区表。
具体到技术原理,LVM有三层逻辑:物理卷(PV)把磁盘或分区包装成“可以灵活分配的资源池”,卷组(VG)把这些资源池汇总,逻辑卷(LV)再从池子里切出固定大小的逻辑块设备给文件系统用。因为LV只是逻辑概念,底层可以横跨多块物理磁盘、多个分区,所以在线扩展非常自然——哪怕PV本身不够了,新加一块盘做成PV并加入VG就行。
普通分区没有这层抽象。/dev/sda1直接映射到磁盘上的某段物理扇区,起点终点在分区表里写死,文件系统完全依赖这个边界。除非你这个分区恰好“头顶就是磁盘尾部空余空间”,并且分区表是GPT,才能在线把边界往后推;否则就必须离线操作,从外部环境介入。
1.3 三个约束条件,直接决定你走在线还是离线
扩容前必须搞清楚三件事,它们决定了整个方案的走向,缺一不可。
第一是分区表类型。执行parted /dev/sda print,能看到Partition Table: gpt或Partition Table: msdos。GPT分区的元数据结构天然适合在线扩展末尾分区,growpart对它非常友好;msdos(MBR)对2TB以上磁盘支持有限,而且结构上存在主分区、扩展分区、逻辑分区的嵌套关系,一旦根分区被扩展分区包裹,在线扩展基本没戏。
第二是文件系统类型。blkid /dev/sda1或df -hT能看出来。ext4和xfs支持在线扩大,但工具不同:ext4用resize2fs,xfs必须用xfs_growfs,混用必然报错。而且xfs有一个致命特性——只能扩大不能缩小,一旦分区排布想调整,xfs的容错空间比ext4小很多。
第三是根分区是不是“磁盘上的最后一个分区”。如果根分区后面还有swap、/boot或者其他数据分区,在线方案直接破产,因为分区边界无法越过后面的分区往前推进。这时候要么离线调整分区顺序,要么放弃扩这个盘,走“加新盘迁移目录”的替代路线。
2. 动手前必须排掉的三颗雷:快照、分区表类型和备份
2.1 有快照的虚拟机,扩容动作会被直接卡住
无论你用的是VMware Workstation、ESXi还是VirtualBox,有快照状态存在的虚拟机,在平台侧扩虚拟磁盘时都会遇到阻力。VMware Workstation里更直接,虚拟机的硬盘设置页面,“扩展磁盘”的按钮是灰的,根本点不了。
真实原因是快照机制会锁定虚拟磁盘的元数据。虚拟磁盘被快照拆分后,原始vmdk变成只读基础盘,后续写入都落在delta文件中。此时调整基础盘大小,快照链的一致性就没法保证,平台宁可拒绝操作也不让你冒险。所以正确顺序一定是:先把快照删掉或者提交合并,再扩展磁盘。
ESXi平台上有时扩展按钮不置灰,但也不建议带着快照直接扩。我实测过,带着快照扩盘之后,分区增长过程中快照delta文件会异常膨胀,最后磁盘占用比预期高出好几倍,搞得到处找空间清理。ESXi里如果虚拟机有快照,在编辑设置的硬盘区域能看到“快照状态:存在”之类的提示,先处理干净再继续。
VirtualBox同样是这个逻辑。命令行执行VBoxManage modifymedium disk "xxx.vdi" --resize 204800时,如果虚拟机存在快照,会直接报错cannot resize medium because it has snapshots。所以VirtualBox用户点开“文件 -> 虚拟介质管理器 -> 对应磁盘 -> 属性 -> 大小”之前,也得先清理快照。
2.2 确认分区表是GPT还是MBR,文件系统是ext还是xfs
确定快照清干净了,下一步是做台账。我会习惯性把下面几条命令的输出存到本地,作为结束后验证和排障的依据:
lsblk df -hT fdisk -l /dev/sda parted /dev/sda print blkid关键看两点。第一点,parted输出的分区表类型和当前磁盘最大容量是否匹配。如果磁盘已经超过2TB但分区表还是msdos,那先别急着扩容,要想清楚要不要转GPT。MBR的分区描述符只能管理到2TB,超过部分就算扩了分区也无法正常识别。反过来,如果磁盘不到2TB,分区表是msdos还是gpt都能在线扩展末尾分区,但msdos下growpart的成功率不如GPT稳定,后面实操部分会细说。
第二点,文件系统类型。df -hT能看到根分区是/dev/sda1 xfs还是ext4。这里提醒一句,别只看/dev/sda1就默认是ext4,现在很多CentOS、RHEL默认安装都是xfs。xfs用resize2fs扩会直接报Filesystem has unsupported feature(s),一开始就把类型确认好,能省掉一次白忙活。
2.3 备份和应急回滚方案,这一步花的时间最值
直接改分区表属于高风险操作,尤其是后面要讲到的离线移动分区场景。一旦中途断电、虚拟机崩溃或者操作失误,数据可能就是物理层面丢失,连软件恢复工具都未必救得回来。我自己的习惯是,扩容前一定在虚拟化平台打一个快照,这比任何系统内备份都管用。
打快照的时机有讲究。如果虚拟机正在跑业务,先停服务或者至少让数据库把脏数据刷盘,再打快照。快照完成意味着当前虚拟磁盘的完整状态被冻结,后续所有分区操作都能随时一键回滚,这比tar、rsync之类的文件级备份可靠得多,因为它是整机层面的状态拷贝。
如果用的是KVM/QEMU环境,没有现成的“快照按钮”,可以趁业务低峰直接复制一份qcow2或raw镜像文件到另一块存储上:
cp -a /var/lib/libvirt/images/your-vm.qcow2 /backup/your-vm.bak.qcow2镜像文件很大,拷完记得核对一下文件大小和校验值。qcow2是稀疏文件,单看ls -lh不准确,要用du -h看实际占用的块。备份做完,后续所有操作都等于“有保险丝”的状态,心里不慌。
3. 最省事的在线扩容实操:growpart + resize2fs/xfs_growfs
3.1 虚拟机平台侧把虚拟磁盘撑大
平台侧操作因为系统不同略有差异,但核心目标一致:让虚拟磁盘在硬件层变大。VMware Workstation是“编辑虚拟机设置 -> 硬盘 -> 扩展”,输入新磁盘大小。ESXi是“编辑设置 -> 硬盘 -> 调整大小”。VirtualBox可以用GUI或命令;KVM/QEMU用qemu-img。
各虚拟化平台扩容虚拟磁盘的常用方式对比:
| 平台 | 操作方式 | 是否要求关机关快照 |
|---|---|---|
| VMware Workstation | GUI 编辑设置 -> 硬盘 -> 扩展 | 有快照时按钮置灰 |
| ESXi | 编辑设置 -> 硬盘 -> 调整大小 | 建议先删快照 |
| VirtualBox | 文件 -> 虚拟介质管理器 -> 属性 -> 大小 或VBoxManage modifymedium disk --resize | 有快照直接报错 |
| KVM/QEMU | qemu-img resize /path/disk.qcow2 200G | 通常需要先关机 |
KVM用户执行前最好先确认镜像当前大小和格式。比如当前100G的qcow2想扩到200G,命令是:
qemu-img resize /var/lib/libvirt/images/ubuntu.qcow2 200Graw格式同样支持这个命令。扩容后如果虚拟机处于开机状态,有些平台支持热插拔SCSI设备自动识别,但virtio-blk设备通常要重启才能看到新容量。为了省事,我一般都在平台侧扩完盘之后直接重启一次虚拟机,让后续排查基于一个稳定状态。
3.2 让系统感知新容量:rescan还是重启
平台侧扩完磁盘,进入虚拟机第一件事是确认系统是否看到了新大小。lsblk仍然显示sda 100G,这是正常现象,因为内核还没刷新设备容量。最稳的方式是重启虚拟机,重启之后BIOS/UEFI层以新的大小枚举磁盘,内核自然能识别。这是我最推荐的方式,因为你接下来要做分区表操作,系统处于刚启动的干净状态最有利。
如果业务不能重启,可以尝试在线刷新。SCSI设备路径下做rescan:
echo 1 > /sys/class/scsi_device/1:0:0:0/device/rescan问题是你得先知道当前磁盘挂在哪个SCSI target下。用ls -l /sys/class/scsi_device/查看,或者直接对每个路径都执行一遍,失败不会造成影响。但KVM下的virtio-blk设备是/dev/vda,走的不是SCSI rescan路径,这种“热刷新”经常失效。实测下来,virtio-blk在线刷新只能靠echo 1 > /sys/block/vda/device/rescan这样的方式,且依赖较新的内核。总而言之,不能重启的场景,成功率偏低,别硬刚,还是找维护窗口重启最舒服。
3.3 growpart扩展分区,再扩展文件系统
系统识别到新容量后,用parted /dev/sda print确认一下分区表仍旧是GPT,且根分区位于磁盘末尾。满足条件就可以在线扩展分区了。growpart是cloud-init自带工具,也可以单独安装:
Ubuntu/Debian:
apt install cloud-guest-utilsCentOS/RHEL:
yum install cloud-utils-growpart然后执行:
growpart /dev/sda 1注意中间没有/dev/,是“设备 分区号”的格式。growpart的原理就是调用parted把分区结束扇区推到磁盘最后一个可用扇区,GPT下这种操作非常安全,因为它只修改分区表项里的End字段,不动文件系统数据。命令输出会显示CHANGED: partition=1 start=2048 old: size=... end=... new: size=... end=...。
分区扩展完,文件系统还没感知到这个变化。ext4执行:
resize2fs /dev/sda1它会自动读取分区新大小,在线把ext4文件系统扩展到整个分区。xfs则要用挂载点作为参数:
xfs_growfs /注意xfs使用的参数是挂载点,不是设备节点,用设备节点在某些发行版上会提示失败。ext4的resize2fs对挂载中的分区是安全的,不用担心。
3.4 在线扩容的适用条件和最终验证
整个在线过程看起来一气呵成,但适用条件卡得很死:GPT分区表 + 根分区必须是磁盘最后一个分区 + 文件系统是ext4或xfs + 没有快照锁盘。四个条件缺一个,就不要硬来。
验证部分也比较简单。df -hT确认根分区已经变成新容量,lsblk确认sda1占满整个磁盘剩余空间。另外建议顺手跑一次文件系统检查,ext4可以在下次重启时通过tune2fs设置强制检查,或者用e2fsck -f /dev/sda1在卸载状态下检查;xfs用xfs_repair -n做只读检查。扩容操作本身不碰文件系统数据,但这道验证工序花不了几分钟,能拦住大多数后续隐患。
4. 分区次序不理想时的离线方案:GParted Live完整操作
4.1 准备GParted Live启动盘
如果根分区后面还跟着swap、/boot或者数据分区,在线扩容路径走不通,就得进入离线环境,用GParted Live从虚拟机外部操作。GParted Live是一个极简Linux发行版,专门做分区管理和文件系统调整,整个系统跑在内存里,不会占虚拟磁盘空间。
去官网下载最新的gparted-live-x86_64.iso,挂载到虚拟机的CD/DVD光驱,把虚拟机启动顺序调整为“光驱优先”。GParted Live启动后一路回车进入默认桌面,桌面有个“GParted分区编辑器”图标,双击就能进入图形化管理界面。界面里所有分区一目了然,右键分区就能看到Resize/Move选项。
注意,GParted Live本身不带数据救援功能,它更像“外科医生的手术刀”。进了图形界面,第一步是把之前打的快照再确认一遍存在且可用,然后才进行分区调整。这步没有商量的余地,我见过太多人直接在GParted里拖动分区后系统起不来的案例,有快照是唯一后悔药。
4.2 调整根分区并重建后面分区的实际步骤
最典型且常见的是“根分区sda1 + swap分区sda2”布局,根分区不是最后一个分区,需要先处理掉后面的swap才能扩展根分区。swap本身是临时性存储,重建代价低,适合直接删除后再重建。
在GParted里按顺序操作:
- 在swap分区上右键,选择“Swapoff”,确保没有交换空间处于激活状态,然后右键“删除”。删除操作不会立即写入磁盘,只是待执行状态。
- 在根分区sda1上右键,选择“Resize/Move”,直接把大小拖到最大,或者在上方new size输入框填写最大可用值。GParted会显示根分区“前面空闲空间0,后面空闲空间xG”,确认后点击Resize。
- 在未分配空间上右键,选择“New”,文件系统类型选
linux-swap,大小按原swap大小或实际内存需求设置,点击Add。 - 工具栏绿色对勾按钮执行所有待定操作,GParted会弹出确认窗口,明确提示“应用这些操作到磁盘”,确认后开始。
应用过程会实际写入分区表并移动文件系统元数据,耗时取决于磁盘大小和数据量。中间千万不要关闭虚拟机或者拔掉虚拟磁盘,一旦中断,文件系统元数据可能处于半写状态。操作完成后退出GParted Live,卸载ISO,重启进入原系统。
4.3 重建swap后必须处理的UUID问题
这一节是我自己踩过最深的坑。swap分区被删除重建后,它的UUID一定跟原来不一样。进入原系统后,如果fstab里用了UUID=xxxx的方式挂载swap,系统会报一个类似waiting for device /dev/disk/by-uuid/xxxx的错误,启动过程卡很久,最后swap无法激活。
处理办法是重新生成swap文件系统并同步fstab。进入系统后先看当前的swap设备UUID:
blkid /dev/sda2输出里的UUID="新uuid"就是要用的。如果swap分区类型标签不对,可以重新执行:
mkswap /dev/sda2然后编辑/etc/fstab,把swap对应行的UUID改成新值。如果不care用设备路径,可以直接把fstab里的swap行改成/dev/sda2 none swap sw 0 0,但这种写法在设备名漂移时(比如新加硬盘导致盘符变化)会出错,所以还是建议用UUID方式。改完执行swapon -a验证swap是否正常激活。
4.4 移动/boot分区的高危操作与替代方案
如果根分区后面跟的是独立/boot分区,处理起来就比swap麻烦太多了。/boot里是内核和引导加载程序文件,GRUB启动时依赖分区号和UUID定位这些文件,删除重建、移动位置都会直接影响系统能否正常引导。
GParted里可以拖动/boot分区到磁盘尾部,理论上可行,但移动分区的过程是全盘数据拷贝,一旦中间出问题,GRUB大概率找不到引导文件。而且移动之后必须重新安装引导程序,操作链路非常长:先备份/boot内容,然后删分区、扩根分区、重建/boot分区、恢复文件、chroot进原系统重装grub。每一步都有炸点。
我的经验是,遇到这种情况别硬用GParted。更稳的做法是“整盘迁移”或者“新盘迁移”,两种都比移动/boot安全得多。
整盘迁移的思路是:新建一块更大的虚拟磁盘挂到虚拟机里,用Live CD启动,把原系统完整复制到新盘,安装引导程序后设置从新盘启动。整个过程不需要在原盘上动分区,原盘的完整性始终保留,即使迁移失败也能回滚。迁移工具可以用rsync逐目录复制,也可以用dd整盘克隆,但要处理UUID冲突和分区大小差异,新手操作成本不低。
新盘迁移的思路则更简单:新加一块数据盘,把某些大目录迁上去并挂载,直接绕开“扩容系统盘”这个命题。这个方案对业务数据类分区非常实用,系统盘本身不需要动,后面第5章会展开讲。
5. 我用踩坑换来的排错清单与不折腾的替代思路
5.1 扩容中高频报错自查表
几次扩容做完,我把最容易遇到的报错和对应解法整理成了表格,下次再处理类似问题直接对照着查:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 平台侧扩展磁盘按钮置灰 | 存在虚拟机快照 | 删除或提交快照后再扩展 |
| 系统内lsblk看不到新容量 | 内核设备容量未刷新 | 重启虚拟机,或在scsi设备路径下执行rescan |
growpart报错not the last partition | 根分区后面还有其他分区 | 走GParted离线方案或加盘迁移 |
growpart报错does not have a GPT table | 分区表是msdos或分区在扩展分区内 | 转GPT或离线操作 |
resize2fs报unsupported feature | 文件系统是xfs,误用了ext4工具 | 改用xfs_growfs / |
xfs_growfs /dev/sda1报错 | xfs_growfs参数应为挂载点 | 执行xfs_growfs / |
| 启动时卡在等待swap设备 | swap分区删除重建后UUID变了 | blkid查新UUID,更新fstab |
5.2 根分区满了连系统都起不来,怎么先救命
如果根分区满到连日志都写不进去,系统可能根本起不来,这时扩容反而成了次要问题,先恢复业务才是关键。
进Live CD后把原根分区挂载到某个目录,比如:
mount /dev/sda1 /mnt然后用du逐级排查大文件:
du -xh --max-depth=1 /mnt | sort -hr | head -20重点关注几个常见占空间大头:/mnt/var/log/journal(systemd日志)、/mnt/var/log/下各种历史日志、/mnt/tmp和/mnt/var/tmp、/mnt/var/lib/docker的overlay2目录、/mnt/home下的用户缓存。清理日志可以用:
journalctl --root=/mnt --vacuum-size=100M或直接删除Live环境里可见的旧日志文件。清理出一部分空间后,系统能启动,再按前面第3章的在线扩容流程做正规扩容。
如果不想进Live CD,也可以在GRUB引导界面按e编辑内核启动参数,在linux开头的那一行末尾追加systemd.unit=rescue.target,然后按Ctrl+X启动进入救援模式。救援模式下系统只挂载必要分区,网络和图形服务都不启动,这时候删文件也一样有效。需要root密码,这点提前跟管理员确认好。
5.3 与其折腾旧分区,不如加盘迁移目录
非LVM分区扩容折腾一圈后,我最大的感触是:很多时候根本不该扩系统盘,而是应该加一块新盘把大目录迁出去。这个方案不用碰旧分区表,不用离线,风险低到一个“即使操作失误也只是服务短暂中断”的程度。
以MySQL数据目录为例。虚拟机加一块100G新盘进入系统后:
fdisk /dev/sdb # 新建分区,建议独立分区或改为LVM mkfs.xfs /dev/sdb1 # 文件系统按需选择 mkdir /data mount /dev/sdb1 /data然后停服务,迁移数据,改挂载点:
systemctl stop mysqld rsync -avxP /var/lib/mysql/ /data/ mv /var/lib/mysql /var/lib/mysql.bak mkdir /var/lib/mysql接着编辑fstab,把新分区持久化挂载到/var/lib/mysql:
/dev/sdb1 /var/lib/mysql xfs defaults 0 0最后mount -a挂载,再启动mysqld。数据文件在新分区上,根分区压力立刻缓解。这个思路对/home、/var/lib/docker、/opt这些路径同样适用,区别只是迁移前目标服务要停干净。
这种方案的另一个好处是,新盘在创建分区时还能顺手做成LVM,相当于给未来留了一条活路。下次空间不够,直接往这个VG里加新PV、扩LV,不用再经历“旧盘非LVM扩容”的痛苦循环。
5.4 说说我现在装机的分区习惯
踩过好几次非LVM扩容的坑之后,我现在装虚拟机Linux的分区习惯已经完全固定下来,也分享给大家参考。
只要发行版支持,一律用LVM。系统盘整个做成一个PV,VG里创建根文件系统和/home两个LV,预留一部分未分配空间在VG里放着不动。等哪天空间紧张,lvextend -l +100%FREE /dev/vg/root加上xfs_growfs /,两分钟解决所有问题,全程在线。Ubuntu Server和CentOS/RHEL安装器都有“自定义分区”选项,多花两分钟选LVM,后面省下的时间是按小时计的。
如果因为特殊原因必须用非LVM(有些专有软件对分区布局有特殊要求),我会尽量保证根分区是磁盘的最后一个分区,并且分区表用GPT,文件系统优先选ext4而不是xfs。xfs不能缩小,一旦分区排布需要调整,手段就少了一半;ext4至少还能在离线状态下缩小,给未来留点余地。当然,这不代表就彻底安全了,备份和快照的习惯仍然要一直保持。
毕竟分区管理这类操作,做之前怎么谨慎都不为过,做的时候能不多动绝不多动,一次做对才是真正的效率。