在虚拟机里跑Linux,我最怕看到的画面不是服务挂了,而是df -h刷出来一行红字,容量直接100%。前阵子VMware里一台Ubuntu服务器跑着跑着突然写不进日志,nginx报错、定时任务全卡住,一查就是根分区被占满了。当时第一反应是清理垃圾,但清完发现底层的虚拟磁盘本身已经不够用——这时候最彻底的解法就是给虚拟机做磁盘扩容。
很多朋友以为“虚拟机Linux磁盘扩容”就是把VMware的硬盘调大然后开机完事,实际上这只是一个起点。完整链路涉及三层:虚拟化层(把虚拟磁盘调大)→ 分区层(让分区表用上新空间)→ 文件系统层(让文件系统真正吃到容量),任意一层漏了都会出现“硬盘明明调大了但系统里还是老容量”的迷惑现象。这篇文章就按这个链路把每一步讲透,同时把容易翻车的细节和我实战中踩过的坑一并放出来,适合所有在用VMware、VirtualBox或KVM跑Linux的运维和开发朋友参考。
1. 先给磁盘做“体检”:搞清楚满在哪、当前是什么分区方案
扩容之前一定要先诊断,这不是走流程,而是为了避开后面的大坑。很多人在没弄明白分区表类型、文件系统类型、是不是LVM的情况下直接下手,结果扩容到一半发现方式和预期完全不一样,甚至把数据搞丢。
1.1 用df和du定位真实占用情况
第一步永远是df -Th,注意带上-T参数,可以顺带显示文件系统类型:
df -Th输出里能看到所有挂载点、容量、已用、可用和文件系统类型。我先看到/dev/sda2挂载在/,ext4,容量占满。这里要区分两种情况:一种是磁盘本身还有剩余空间,只是某个目录特别大;另一种是分区容量确实到了上限,比如根分区只有20G但数据已经19.8G。
如果磁盘整体还有空间但某个分区满了,优先用du找到大目录:
sudo du -sh /var /home /tmp /var/log /opt /usr 2>/dev/null | sort -h我在生产环境里最常见的大户是/var/log(journald日志不清)、/var/lib/docker(容器镜像和overlay2堆积)、/home下的构建缓存。实在挤不出空间再考虑扩容,因为扩容虽然是常规操作,但能少一次就少一次。
1.2 判断分区布局与文件系统类型
看结构用lsblk -f,这个命令会输出块设备树、挂载点、文件系统类型和UUID:
lsblk -f这是判断“接下来走哪条路”的关键。常见情况有三种:
- 非LVM、单一根分区:比如
/dev/sda2直接挂载到/,扩容目标是把sda2分区扩大,再扩文件系统。 - LVM逻辑卷结构:输出里能看到
─ubuntu--vg-ubuntu--lv之类,实际挂载在/的设备可能是/dev/mapper/ubuntu--vg-ubuntu--lv,这时候分区层只要pvresize,然后把逻辑卷扩大,再扩文件系统。 - 多分区表MBR/GPT:
fdisk -l可以判断分区表类型。
再配合pvdisplay、vgdisplay、lvdisplay确认LVM的信息:
sudo pvdisplay sudo vgdisplay sudo lvdisplay1.3 为什么这个预判能避免白折腾
因为Linux扩容在不同场景下命令完全不同:
- ext4文件系统用
resize2fs,xfs要用xfs_growfs,两者混用大概率报错。 - 不是LVM的话,分区层要用
growpart或fdisk手动扩分区;LVM则必须执行pvresize,顺序错了逻辑卷也看不到新空间。 - 分区表是MBR还是GPT,会直接影响
fdisk操作时的警告和步骤,GPT分区表在删除重建时容易出现备份表不同步的问题。
我见过一位朋友在非LVM的Ubuntu Desktop上直接敲lvextend,系统提示找不到逻辑卷,他还以为是命令没安装。其实只要lsblk -f看一眼是sda2直挂还是mapper设备,就全明白了。所以这一步别省,两分钟的事,后面能省两个小时。
2. 虚拟化层扩容:先把“虚拟硬盘”本身做大
确认好结构后,第二步是回到宿主机,把虚拟磁盘的总容量调大。这一步不涉及Linux内部操作,但在不同的虚拟化平台上细节略有差异。
2.1 VMware Workstation/Player的扩展操作
VMware系列我用得最多,操作路径如下:
- 关闭虚拟机(必须关机,VMware不支持在开机状态下扩展虚拟磁盘)。
- 右键虚拟机 → 设置 → 硬盘 → 实用工具 → 扩展。
- 输入目标磁盘大小,注意是“最终总大小”,不是“增加多少G”。比如原来是40G,想加20G,这里填60G。
- 等待VMware完成扩展,完成后点确定。
如果是纯命令行环境,可以用vmware-vdiskmanager:
vmware-vdiskmanager -x 60GB /path/to/vmdisk.vmdk-x表示扩展磁盘,目标大小写清楚单位。这个命令扩展VMDK在虚拟机离线状态下执行。
2.2 VirtualBox与KVM/libvirt的对应操作
VirtualBox扩展比较简单,路径是:菜单栏“文件” → “虚拟介质管理器” → 选中虚拟磁盘 → “属性” → “大小”,拖动滑块或直接输入新容量,点应用。VirtualBox对VDI格式支持在线调整,但我仍习惯在关机状态下操作,稳一点。
KVM/libvirt环境用qemu-img:
qemu-img resize /var/lib/libvirt/images/ubuntu.qcow2 40Gqcow2格式同样要注意目标总大小。调整完虚拟机的XML不需要改,因为磁盘大小由镜像文件控制。部分图形化界面(virt-manager)也能在磁盘属性里直接调整。
2.3 扩容前务必做的两件事:快照与备份
说到这必须强调:在虚拟化层调大磁盘之前,先拍快照或备份分区表。VM虚拟磁盘的调整虽然极少失败,但一旦失败往往伴随分区表不可读,而对生产环境来说,最贵的不是磁盘空间,是恢复可用状态的时间。VMware里直接在开机前为虚拟机拍一个快照;VirtualBox可以在“生成备份”里做当前状态备份。哪怕是个人测试机,我也建议花这一步。
另一个容易被忽略的是宿主机的磁盘剩余空间。扩展虚拟磁盘时,VMware会在宿主机上为新增长度分配块,如果宿主机本身磁盘快满了,扩展可能完成得极其缓慢甚至失败。我自己遇到过宿主机只剩1.2G空间强行扩展20G,卡了半小时最后报错的情况。所以先看一眼宿主机的df -h,留足余量再动手。
3. 分区层处理:让Linux内核“看见”新增空间
虚拟化层做完,绝大多数人会直接开进系统看容量——然后疑惑“怎么还是老样子”。这很正常,因为虚拟磁盘变大了,但Linux的分区表里,分区的边界还停留在原来的位置,系统不会自动把新空间并进已有分区。这一步要处理的是分区表。
3.1 新空间为什么不会自动生效
用lsblk看一下就能发现规律:磁盘整体大小已经变成60G,但/dev/sda2分区还是40G,后面多出来的20G显示为未分配空间。内核虽然知道磁盘变大了,却不知道这个“新区域”应该属于哪个分区。所以需要把分区的物理边界往后推,才能让分区用满新空间。
方案有两种:一是用growpart自动调整分区边界,推荐优先;二是用fdisk手动删除分区再重建,操作得当也可靠,但风险略高。
3.2 growpart一步扩展分区边界(推荐)
在Ubuntu等系统上,先安装工具:
sudo apt update && sudo apt install -y cloud-guest-utils然后执行:
sudo growpart /dev/sda 2注意命令格式是growpart [磁盘] [分区号],中间没有/dev/前缀。执行后可以看到类似“CHANGED: partition=2 start=... old size=... size=...”的输出。
如果磁盘是GPT分区表,growpart可能会提示需要修正备份分区表,确认即可。执行完用lsblk检查分区大小是否已经变化。growpart的本质是自动计算新的结束扇区并改写到分区表,全过程不需要手工输入数字,能最大程度避免人为失误。
3.3 手动fdisk删除重建分区的老办法
有些老系统没装growpart,又不方便联网安装,这时可以用fdisk手动操作。过程不复杂,但有一个红线:重建分区时,起始扇区必须和原来一致。
sudo fdisk /dev/sda交互过程如下:
p查看当前分区表,记下目标分区的“起始扇区”和“结束扇区”。d删除分区(如果只有一个分区就是2,按实际输入分区号)。n新建分区,分区号保持原来一致。- 起始扇区直接回车使用默认值——这时默认值通常就是刚才删除前的起始扇区,只要确认没变就行。
- 结束扇区直接回车使用默认值(最大可用空间),或输入新的大小。
- 如果提示是否移除签名(signature),选
N,不删除分区上的文件系统标记。 w保存退出。
保存后用partprobe /dev/sda刷新分区表,或直接重启。这里一定要强调:fdisk法最危险的步骤就是重启后分区UUID变了。MBR分区表删除再重建时,分区系统ID和UUID可能变化,如果/etc/fstab里用UUID挂载,很可能开机直接进emergency mode。所以不是万不得已,我优先用growpart,因为它只改结束边界,不重算UUID。
3.4 LVM场景的专属分区处理路径
如果lsblk -f看到的是LVM结构,比如Ubuntu Server默认的ubuntu--vg-ubuntu--lv,那分区层只需做一件事:把物理卷扩展到新分区边界上。
sudo pvresize /dev/sda2这里假设/dev/sda2是LVM物理卷所在分区。执行后pvdisplay或pvs能看到Physical Volume的PE数和大小已经变化。如果分区本身还没扩展(比如growpart没跑),那pvresize扩的也只是分区内已有空间,所以LVM场景下正确的顺序是:
growpart /dev/sda 2扩展分区边界pvresize /dev/sda2让物理卷感知新空间lvextend -l +100%FREE /dev/mapper/xxx把空闲空间全部分配给逻辑卷- 扩容文件系统(下一步)
这里有个小细节:lvextend可以只加一部分而不是全加,比如lvextend -L +10G /dev/mapper/xxx。生产环境里我喜欢预留一部分空间在卷组里,方便以后某个卷紧急扩。不过测试环境直接+100%FREE最省事。
3.5 分区表操作遇到的警告与应对
扩分区时最常见的一个提示是GPT分区表的警告:GPT PMBR size mismatch或Not all of the space available to /dev/sda appears to be used...。遇到这种提示别慌,通常意味着分区表和实际磁盘大小不一致。最稳妥的做法是用gdisk或parted修复。
用gdisk修复很简单:
sudo gdisk /dev/sda进入交互界面后,输入w写回分区表,它会自动修正备份GPT表,然后退出。这里注意,gdisk的w操作会直接保存,前提是你确认当前分区表内容没毛病。
4. 文件系统层收尾:ext4和xfs必须分开处理
分区边界扩展完了,逻辑卷也大了,但文件系统本身还不知道,最后一步是让文件系统用满新空间。这一步的坑最多,因为ext4和xfs的操作命令完全不同。
4.1 ext4系:一条resize2fs在线搞定
Ubuntu Desktop和很多Debian系默认用ext4。对于ext4,直接在线执行:
sudo resize2fs /dev/sda2如果分区结构是LVM,设备路径就是/dev/mapper/xxx:
sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lvresize2fs默认会扩展到设备的最大可用空间,输出会显示文件系统从多少block扩到了多少block。ext4的好处是支持在线扩容,挂载状态下执行没问题,这也是我用它做根分区时的首选。
4.2 xfs系:只能用xfs_growfs,且不支持缩容
红帽系(RHEL/CentOS/Rocky)默认用xfs,很多朋友会下意识敲resize2fs,然后收到“resize2fs: Bad magic number in super-block”之类的报错,这时候应该用xfs专用命令:
sudo xfs_growfs /注意xfs_growfs的参数是挂载点,不是设备路径。也可以指定设备:
sudo xfs_growfs -d /dev/mapper/centos-root-d表示扩大到最大值,xfs在线扩容没问题,但xfs不支持缩容,这个特性在规划磁盘大小时要想好,一次性给够空间,避免后面要缩回来。
下面这个表每次遇到都值得贴出来参考:
| 文件系统 | 扩容命令 | 是否支持在线 | 是否支持缩容 |
|---|---|---|---|
| ext4 | resize2fs /dev/xxx | 支持 | 支持(离线) |
| xfs | xfs_growfs /挂载点 | 支持 | 不支持 |
| btrfs | btrfs filesystem resize max / | 支持 | 支持 |
| swap | swapoff后重新mkswap | 否 | 可重做 |
4.3 LVM与文件系统的完整组合流程演示
以一台Ubuntu Server(LVM + ext4)为例,完整命令串如下,方便直接套用:
# 1. 扩展分区边界 sudo growpart /dev/sda 2 # 2. 让物理卷感知新的分区大小 sudo pvresize /dev/sda2 # 3. 逻辑卷使用卷组中所有剩余空间 sudo lvextend -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv # 4. 扩展文件系统到最大 sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv # 5. 验证 df -Th如果是RHEL系xfs,把第4步换成:
sudo xfs_growfs /注意逻辑卷设备路径可能带--,比如ubuntu--vg-ubuntu--lv,这是因为卷组和逻辑卷名里含有-时会转义成双连字符,以lsblk显示为准。
4.4 顺带处理swap分区扩容
很多虚拟机初始只有2G的swap,跑编译或大数据任务时经常被打满。swap扩容和根分区扩容是两条路径,但可以一起做。思路是:先建一个新的swap分区(或用swap文件),启用后移除旧分区。简单的方法是swap文件,无需动分区表:
sudo fallocate -l 8G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile持久化写入/etc/fstab:
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab如果想复用原来的swap分区,流程是swapoff /dev/sda3→fdisk删除旧swap分区重建出更大分区(或growpart)→mkswap /dev/sda3→swapon /dev/sda3,同时记得更新/etc/fstab里的UUID。swap分区扩容中间有一次交换空间清零,对内存吃紧的机器要挑业务低峰操作。
5. 扩容后的验证与翻车实录
扩容完成并不意味着结束,必要的验证和几个高频翻车点我专门放在这节讲。毕竟我这些经验都是真金白银换来的。
5.1 怎么确认扩容真正成功
判断成功的标准有三个,全部满足才算完成:
lsblk -f # 分区大小正确 df -Th # 挂载点容量显示新大小 mount | grep ' / ' # 挂载参数正常,UUID没有漂移如果df里显示的还是旧容量,最大可能是文件系统层没执行到位;如果lsblk里分区大小没变,说明分区层没执行成功。按这个顺序倒查,能快速定位问题在哪一层。
5.2 “resize2fs: Device or resource busy”是什么意思
我见过不少朋友扩容LVM时卡在这里。其实报错的意思是设备忙,文件系统无法安全调整。ext4在挂载状态下通常能在线扩容,但某些情况(比如挂载选项带nouser_xattr等特殊参数)或对根文件系统时,仍可能提示busy。处理办法:
- 先确认是不是命令用错了设备,比如拿
/dev/sda2在LVM环境扩,设备其实应该是mapper路径。 - 如果是普通分区且能卸载,可以启动到live环境离线resize,但虚拟机场景重启又快,没必要非得在线。
- 如果确实要在线,检查挂载参数,必要时先
mount -o remount,rw /dev/xxx /再试。
实际上对根分区最省事的方式是:确保分区层已经扩好,直接用resize2fs在线扩,绝大多数ext4都没问题。这个报更多出现在xfs文件系统被错误执行resize2fs,报错类型不一样但同样让人头大。
5.3 GPT备份表不一致导致的认盘异常
有次我用fdisk手动扩展GPT分区,保存时报了一堆警告,重启后系统差点起不来,原因就是备份GPT表还保留旧结构。应急修法是用gdisk修复:
sudo gdisk /dev/sda进入后不用改任何设置,直接输入w,它会自动重建备份GPT表。如果系统卡在grub界面,还能用e2fsck修复文件系统。
这也就是为什么我前面强调growpart优先——它对GPT的处理更规范,手动fdisk虽然能成,但每多一次手工操作,就多一分风险。
5.4 扩容完成后fstab失效的经典问题
MBR分区表删除重建之后,分区的UUID可能变了,/etc/fstab还按旧UUID找设备,开机直接失败。遇到这种情况,先用live系统或紧急模式把根分区只读挂载起来,查看blkid拿到新UUID,然后更新/etc/fstab。
更好的办法是从一开始就避免:如果fstab里用的是UUID而不是设备路径/dev/sda2,手动fdisk重建分区前一定要把原UUID记下来,重建后用mkswap -U或直接保留原分区类型重新指定UUID。ext4可以这样保留:
sudo tune2fs -U <原UUID> /dev/sda2但我真实建议还是优先growpart,它不会改变分区UUID,省掉这整个风险。
5.5 一些实操中的小习惯与体会
我个人的习惯是:给虚拟机扩容前,先用lsblk -f > disk_layout.txt把分区结构存一份档,真的出问题时能对照找回原始布局。扩容顺序一定是“先快照、再虚拟化层、再分区层、再文件系统层”,每一步用对应的命令验证一次,不要一股脑全部跑完。很多朋友一次跑完所有命令,中间某一步失败后反而不知道去哪排查。
最后再分享一个小技巧:如果虚拟机里跑的是Docker,扩容后记得看下/var/lib/docker占用,docker的overlay2层经常占用超出预期。有时候“磁盘满了”不是容量不够,而是镜像和日志堆积——先清理再扩容,能让扩容这件事的效果维持更久。扩容不是万能药,量出为入、定期清理,才是虚拟机Linux长期稳定运行的核心。