☰
bhyve虚拟机Ubuntu系统盘扩容:用parted resizepart轻松搞定
2026/10/7 10:31:35 网站建设 项目流程

折腾过 bhyve 的朋友都知道,给虚拟机扩容这件事,说难不难,说简单也真不简单。最近我又一次扩充一台 Ubuntu 虚拟机的系统盘,从宿主机到虚拟机内部来回折腾,本来想着跟往常一样改改卷大小、进系统用 fdisk 调一下分区就完事,结果这次偏偏多绕了一步:在 Ubuntu 里多敲了一个 parted 命令,才把新空间真正“激活”。正好有朋友也问到这个问题,我把这次的操作过程完整复盘一遍,希望能给同样在用 bhyve + Ubuntu 的人一点参考。

先说清楚这篇文章要解决的问题:在 FreeBSD 宿主机上跑 bhyve 虚拟机,虚拟机里装的是 Ubuntu,系统盘空间不够了,需要扩大虚拟机的系统盘容量。常规思路是先把宿主机这层的存储后端撑大,再进到 Ubuntu 内部让分区表认出新空间,最后让文件系统也跟着长大。这次跟以往最大的区别,就是分区表这步使用了 parted 的 resizepart 子命令,而不是以前我一直习惯的 fdisk 交互式操作。这个差别看起来不大,但遇到 GPT 分区和某些边界情况时,parted 反而更省事,也不会让你手一抖把起始扇区搞错。

这篇内容既适合刚接触 bhyve 的新手,也适合那些扩容过很多次、想找个更顺手方法的老手。我会把宿主机端的 zvol/文件镜像扩容、虚拟机里的分区调整、最后一步文件系统扩容都讲清楚,另外把我这次踩过的坑一并列出来,比如分区表类型不匹配、内核不认新分区表、resize2fs 报错等问题,直接给你一套可以照着敲的流程。

1. 扩容前先搞懂:bhyve 虚拟机磁盘到底存在哪里

1.1 bhyve 磁盘后端的两种主流形态

bhyve 本身是一个跑在 FreeBSD 上的原生虚拟机监控器,它跟 VirtualBox、VMware 这类带图形界面的虚拟机不太一样,几乎所有配置都得通过命令行和配置文件来驱动。虚拟机看到的“硬盘”,在宿主机上通常对应两种东西:一种是 ZFS 的 zvol,另一种是普通的磁盘镜像文件。

用 zvol 当后端的好处很明显:你能直接用 ZFS 的快照、复制、压缩等特性,而且 zvol 在 ZFS 里就是一个块设备,虚拟机的读写可以走 ZFS 的 ARC 缓存,性能比普通文件镜像要稳。用文件镜像(比如 .raw 或 .img)就简单直接,创建方便、方便拷贝迁移,但性能上限和功能丰富度都不如 zvol。我这次扩容的机器,后端就是 zvol,这种选择在规模化的 bhyve 环境里很常见。

1.2 扩容的真正难点:三层链路都要打通

先想清楚扩容这件事的本质。虚拟机里看到的磁盘容量,实际上由三层决定:

  • 宿主机层的存储后端大小:比如 zvol 的 volsize,或镜像文件的实际字节数;
  • 虚拟机磁盘上的分区表信息:分区表记录的每个分区在磁盘上的起止范围;
  • 分区内的文件系统大小:比如 ext4 的块组数量、inode 表位置。

简单来说,磁盘总容量变大了,并不等于 Ubuntu 里可用的空间变多了。你拿一块 50G 的硬盘给 Ubuntu,如果分区表里根分区仍然只写到 20G 的位置,那 Ubuntu 只会看到 20G;就算分区表改了,如果 ext4 文件系统本身的空间结构还凝固在 20G 处,Ubuntu 的文件系统也不会自动扩展。所以扩容一定得三步做齐:宿主机扩后端 → 虚拟机里扩分区 → 扩文件系统。任何一步漏了,结果都是“看着磁盘大了,但 df -h 还是老样子”。

1.3 这次为什么多了个 parted 命令

以前我扩容,进 Ubuntu 之后通常用 fdisk 的交互界面:先 d 删掉目标分区,再 n 新建一个分区,然后手动把起始扇区填成原来的值,最后 w 保存。这套流程轻车熟路,但有个隐患:每次删掉分区重建,起始扇区必须一字不差地填回去,手一抖或者复制粘贴出错,数据就全没了。另外 fdisk 对于 GPT 分区表也能处理,但交互式操作在脚本化或远程终端里显得很啰嗦。

这次扩容时,我发现这台 Ubuntu 的分区表是 GPT,而且分区数量不止一个,用 fdisk 删了重建风险太高。于是改用 parted 的 resizepart 子命令,直接告诉它“从第几个分区开始,把结束位置挪到磁盘末尾”。这个命令只调整分区的终点,起始位置完全不动,彻底避开了删除分区再重建带来的风险。我实际敲完之后才意识到,这一步其实比 fdisk 的方案要优雅得多,以后大概率会成为我的默认操作。

2. 宿主机端:先把底层存储撑大

2.1 先确认你的虚拟机用的是哪种后端

动手之前,先别急着敲命令。得先搞清楚这台虚拟机的磁盘到底挂的是什么。我通常这样查:看一下虚拟机配置文件,或者直接用 zfs list 看看有没有对应的 zvol。

# 在 FreeBSD 宿主机上 zfs list -o name,volsize,used,available

如果看到类似zroot/vm/ubuntu这种名字,并且用了 volsize 属性,说明磁盘后端就是 zvol。要是没有 zvol,那就去虚拟机配置里找磁盘文件路径,一般是.raw或.img结尾的普通文件。

# 查看 bhyve 虚拟机配置(常见路径) cat /etc/vm.conf # 或者直接用 vm 命令列出 vm list

注意这里有个很容易忽略的点:bhyve 虚拟机有时用vm run这种工具来管理,配置写在/etc/vm.conf或/usr/local/etc/vm.conf,里面会给每个虚拟机的磁盘指定disk0disk1等设备的来源,可能是/dev/zvol/...,也可能是一个文件路径。看清楚这步,后面所有操作才不会搞错对象。

2.2 zvol 扩容:一行 zfs 命令

如果磁盘后端是 zvol,扩容很简单,直接用zfs set volsize。比如我要把zroot/vm/ubuntu这个 zvol 从 20G 扩到 50G:

zfs set volsize=50G zroot/vm/ubuntu

就这么一行。执行完可以再确认:

zfs get volsize zroot/vm/ubuntu

这里要特别提醒:ZFS 的 volsize 表示逻辑卷大小,扩容后 zvol 的实际占用(used 属性)不会立刻增长,而是等虚拟机往里写数据时才会消耗空间。这是一个很容易让人误解的地方,别一看 used 没变就觉得扩容没生效。

还有一个实践上的建议:如果虚拟机正在运行,我倾向于先把虚拟机关机再执行zfs set volsize。虽然 ZFS 支持在线调整 zvol 大小,但 bhyve 的 virtio-blk 设备是否能无缝感知扩容后的容量,取决于驱动和固件,与其在运行状态下赌这个,不如停机操作,干净又安全。这次我也没例外,先停了 Ubuntu,在宿主机上扩好卷,再启动虚拟机进去操作。

2.3 文件镜像扩容:truncate 与潜在隐患

如果你的 bhyve 磁盘是 raw 格式的镜像文件,扩容也简单,用 truncate 把文件“撑大”即可:

# 在原基础上增加 30G truncate -s +30G /vm/ubuntu.raw # 或者直接指定最终大小 truncate -s 50G /vm/ubuntu.raw

truncate 修改的是文件逻辑大小,不会填充实际数据,所以扩展的部分在文件系统里显示为空洞,不占宿主机磁盘空间,速度很快。

但有个大坑:如果后端不是 raw,而是 qcow2 或其他带格式的镜像,直接用 truncate 往往会把镜像头搞乱,虚拟机可能直接起不来。bhyve 原生支持最好的是 raw 格式和 zvol,qcow2 通常需要额外的转换/挂载流程。所以再次强调:动手前先确认后端类型。如果是 qcow2,先qemu-img resize去扩,别用 truncate 硬怼。

3. 虚拟机内部:用 parted 重新划分分区

3.1 开机后先确认磁盘设备名和分区表类型

宿主机扩容完成之后,启动 Ubuntu。进入系统后,先看虚拟机关联到的块设备名称。bhyve 的 virtio 磁盘在 Linux 里通常是/dev/vtbd0(有些内核版本或配置也可能显示为/dev/vda,取决于 virtio 驱动)。我这里的 Ubuntu 显示为/dev/vtbd0。

lsblk fdisk -l /dev/vtbd0

lsblk 能直观看到磁盘和分区的层级关系。比如:

NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT vtbd0 252:0 0 50G 0 disk ├─vtbd0p1 252:1 0 1M 0 part ├─vtbd0p2 252:2 0 1G 0 part /boot └─vtbd0p3 252:3 0 19G 0 part /

这里能看到磁盘 vtbd0 现在已经是 50G,但最下面的根分区还停留在 19G(原来 20G 磁盘减去 boot 分区等),说明分区表还没有感知到新增空间。这就是我们要处理的核心问题。

接下来关键一步:确认分区表类型。GPT 和 MBR 的调整方式有细微差别。执行:

parted /dev/vtbd0 print

输出里会明确显示Partition Table: gpt或Partition Table: msdos。如果是 GPT,后面用 parted 会很顺手;如果是 MBR(msdos),也可以用 parted 扩,但要小心主分区数量不能超过 4 个的限制。

3.2 parted resizepart 的正确姿势

确认完后,就可以用 parted 调整分区了。我要把第三个分区(根分区,编号 3)扩展到磁盘末尾。parted 支持交互模式和非交互模式,我强烈推荐非交互模式,因为命令执行完,结果一目了然,出错也容易回溯。

# 非交互模式:直接把第 3 个分区的终点设为磁盘 100% 位置 parted /dev/vtbd0 resizepart 3 100%

这条命令的意思是:把分区 3 的 end 位置调整到磁盘结尾 100%。parted 会自动计算结束扇区,完全不用你手动换算起始扇区、结束扇区那些数字。

如果是交互模式,大概是这种感觉:

parted /dev/vtbd0 (parted) resizepart 3 End? [40.0GB]? 100% (parted) quit

交互模式里,parted 会问你新的结束位置,你可以直接输入想设置的容量数值,比如50GB,也可以输入100%。我个人更喜欢非交互式,因为可以完整保留命令记录,方便写文档和回头检查。

这里有一个很重要的细节:parted 的 resizepart 只需要指定分区号和新的结束位置,完全不需要碰起始位置。这也是我这次特别想强调的一点——相比 fdisk 删除重建,这个命令天然就更安全。起始位置一旦改变,文件系统的基本盘就碎了,而 resizepart 把这个风险直接降为零。

3.3 执行后再仔细确认分区状态

执行完 parted 之后,千万别急着去扩文件系统,先确认分区表本身没问题:

parted /dev/vtbd0 print

这时应该能看到分区 3 的终点已经变成磁盘末尾了。我再建议顺便看一眼:

lsblk fdisk -l /dev/vtbd0

正常的话,vtbd0p3 的 SIZE 已经变成了接近 48G 左右(取决于前面有没有 boot 分区)。到这一步,分区表已经“认识”了新的空间,但内核可能还保留着旧的分区表缓存。Linux 内核是在分区表变化后,可能不会主动重读。

最稳妥的办法是重启虚拟机,让系统以全新状态加载分区表。但如果你不想重启,有几种方式可以触发内核重读分区表:执行partprobe /dev/vtbd0,或partx -u /dev/vtbd0。我在这次操作中先试了 partprobe,能更新大部分场景,但如果是根分区所在的磁盘,有时还是会提示 partition in use,这时候最好不要强行操作,直接重启最省心。

注意:扩容过程中任何一步出现“设备忙”或“分区被占用”的提示,都不要强制重试。尤其是根分区所在磁盘,内核重读会有风险。我这次的方案就是干脆重启,Ubuntu 启动非常快,没必要省这几秒钟给自己挖坑。

4. 文件系统扩容:最后一步别漏掉,也别搞错命令

4.1 ext4:resize2fs 使用要点

分区表已经扩好了,但 Ubuntu 里的文件系统还是旧尺寸。这一步对应的是根分区,通常文件系统是 ext4(也可能是 xfs,取决于当初安装选项)。先确认文件系统类型:

df -T /

输出里Type那一列如果是ext4,就用 resize2fs;如果是xfs,就要改用 xfs_growfs。我这次这台虚拟机是 ext4,所以执行:

# 在线扩容根分区对应的文件系统 resize2fs /dev/vtbd0p3

不需要指定大小,resize2fs 默认会把文件系统扩展到分区大小。执行完再看:

df -h /

正常情况下,根分区已经变成接近 48G 的容量了。

这里我要特别提一个容易踩的坑:如果分区表中分区 3 的结束位置没有真正确认好,resize2fs 可能会报错,比如Couldn't find valid filesystem superblock或者Invalid argument。遇到这类报错,第一反应不应该是怀疑文件系统损坏,而应该先回去检查分区表:用parted /dev/vtbd0 print确认分区终点是否真的到了磁盘末尾、有没有出现未分配空间。绝大多数时候,问题都出在分区层,不在文件系统层。

4.2 其他文件系统:xfs 和 btrfs 的差异

如果当初装系统时选了 XFS,那扩容方式完全不同。XFS 文件系统扩展必须挂载后才能进行,而且它是基于挂载点来扩容,不是基于设备节点:

# 假设根分区是 xfs xfs_growfs /

注意看区别:resize2fs 后面跟的是设备路径(/dev/vtbd0p3),xfs_growfs 后面跟的是挂载点(/)。这是因为 XFS 的文件系统扩容工具在设计上必须针对已挂载文件系统操作,而且它读取的也是挂载信息里的设备。

如果是 btrfs,命令又不一样:

btrfs filesystem resize max /

btrfs 可以支持 subvolume 和在线 resize,相对灵活,但在 Ubuntu 默认安装里比较少见,这里就不展开了。总之,动手前一定先确认文件系统类型,别拿 ext4 的命令去怼 xfs,那是牛头不对马嘴。

5. 实际操作中踩过的坑与排查实录

5.1 为什么 fdisk 删除重建的风险那么大

在这次扩容的过程中,我稍微复盘了一下之前用 fdisk 删分区重建的经验,越想越觉得这次选择 parted 是明智的。fdisk 交互式删除重建时,系统会提示你输入分区的起始扇区,很多人想当然地直接回车“使用默认值”,结果默认的起始位置和原来并不完全一致,轻则分区偏移导致文件系统无法挂载,重则被识别成损坏分区。

我在以前的一台测试虚拟机上就翻过车:删除分区后新建时,起始扇区输错了,导致后面所有分区的顺序错乱,恢复起来相当耗时。parted 的 resizepart 从根本上规避了这个风险——它不给你修改起始位置的机会,只让你改终点。对于扩容场景,这就够了。

还有一点:现在的 util-linux 新版 fdisk 也支持resizepart命令了,但很多人习惯的还是旧版交互式流程。如果你不想装 parted,或者系统里正好没有 parted,可以试试这条:

fdisk /dev/vtbd0 Command (m for help): r

有那份功夫,我宁可装 parted,它的输出格式更直观,命令也更好写进脚本。

5.2 分区表类型不匹配导致 resizepart 报错

有次我在另一台机器上执行parted resizepart报了错。查了半天才发现,那台虚拟机的磁盘分区表是 MBR(msdos),而我在指令里用了类似 GPT 场景下的写法,导致 parted 对分区边界的校验方式不一样,给出了Error: Can't have overlapping partitions之类的提示。

后来我把输出贴出来细看,发现问题在于磁盘尾部有未分配空间,且 MBR 的分区记录方式对结束位置有自己的一套对齐规则。解决办法也不难:先用parted /dev/vtbd0 unit MiB print查看精确的边界,再用parted /dev/vtbd0 resizepart 3 <新结束位置>指定到合适的 MiB 边界。总而言之,遇到报错别慌,先确认分区表类型、确认分区编号、确认没有其他分区挡在目标结束位置之前。

5.3 根分区在线扩容的危险边界

理论上,resize2fs 支持在线扩容,也就是说分区表调整好之后,不必重启也能直接执行。但在实际操作中,我建议根分区还是重启一次最好。原因有几个:

  • 根分区被内核持续挂载使用,partprobe 可能无法稳定重读分区表;
  • 如果分区表信息与内核实际使用的块设备状态不一致,resize2fs 可能读到不完整的块设备大小;
  • 系统在扩容过程中如果发生异常断电或 IO 错误,恢复的复杂度要比普通分区高很多。

我这次是停机扩的 zvol,启动 Ubuntu 后先做 parted,然后重启了一遍再执行 resize2fs。你说慢吧,也就多一分钟;但换来的确定性和安全性,远大于这一分钟的成本。

5.4 扩容后宿主机 zvol 空间占用骤增的疑问

还有一个容易误解的点:还在宿主机端。扩容 zvol 后,有人会惊讶地发现 zfs list 里的 used 值涨了不少,以为出问题。这其实是因为虚拟机开机后,Ubuntu 的 ext4 文件系统扩容会触发大量元数据更新,写入新的块组和 inode 表,自然消耗了 ZFS 的存储空间。如果你启用了 ZFS 的压缩属性,情况会好很多。另外,扩容完成后如果你有快照习惯,建议在确认系统正常后再清理旧的过期快照,别在扩容中途删快照,以免万一出问题时没有后悔药。

我把这次遇到的典型问题和处理方式整理成一张表,放在下面:

症状可能原因处理方式
lsblk 显示磁盘已变大,但分区没变分区表未更新用 parted resizepart 调整分区终点
parted print 显示分区表类型不匹配GPT/MBR 判断错误先确认分区表类型,再按对应规则调整
partprobe 提示设备忙根分区或挂载中的分区无法安全重读重启 Ubuntu 再继续
resize2fs 找不到有效超级块分区表起点/终点异常,或分区未扩展成功回头检查 parted print 输出,确认分区边界
df -h 容量没变化文件系统还没扩容按文件系统类型执行 resize2fs/xfs_growfs
宿主机 zfs used 暴涨虚拟机文件系统扩容写入元数据正常现象,必要时检查 ZFS 压缩属性

5.5 再补两个实用小技巧

第一,如果有条件,执行扩容前给 zvol 或文件镜像做一个快照,这是成本最低的保险。我在这次扩容前顺手打了个 ZFS 快照:

zfs snapshot zroot/vm/ubuntu@expand_before

万一后面哪一步出了问题,一条zfs rollback就能把虚拟机的存储状态恢复到扩容前。这种“事前快照”的习惯,能省掉很多个通宵抢救数据的夜晚。

第二,远程操作时,建议把每一步的命令输出都存下来,方便事后复盘:

parted /dev/vtbd0 print | tee /tmp/parted_before.txt # 执行扩容... parted /dev/vtbd0 print | tee /tmp/parted_after.txt diff /tmp/parted_before.txt /tmp/parted_after.txt

这样一眼就能看到分区边界发生了什么变化,也方便排查问题。

6. 这次扩容后我沉淀下来的操作心得

复盘这次扩容,我对“宿主机 zvol 扩展 + 虚拟机内 parted 调整分区 + resize2fs 扩展文件系统”这条链路有了更清晰的把握。如果你要问我现在给 bhyve 里的 Ubuntu 扩容,最推荐的路径是什么,我会给出这样一套:

  1. 宿主机确认后端类型(zvol 还是 raw 文件);
  2. zvol 执行zfs set volsize,或 raw 文件执行truncate -s +N;
  3. 启动 Ubuntu,用parted /dev/vtbdX resizepart <分区号> 100%扩展分区;
  4. 重启以确保内核重读分区表;
  5. 根据文件系统类型执行resize2fs、xfs_growfs或btrfs filesystem resize max;
  6. 用df -h验证,顺手打一个确认状态的快照。

最关键的变化,就是把从前用 fdisk“删了重建”的老操作,换成了 parted 的 resizepart。这个过程真的只有一字之差,但安全性却高了不止一个量级。尤其当你面对的是根分区、GPT 分区表、多分区布局的时候,parted 的“只动终点,不碰起点”理念,几乎是针对扩容场景量身定做的。

如果只是临时应付一台虚拟机,你现在就可以照着这篇的步骤去做;如果打算长期维护一批 bhyve 虚拟机,我建议把扩容流程脚本化,把 zvol 大小、分区号、文件系统类型都做成参数,到时候一条命令跑完,省心省力。脚本化的过程中,强烈建议保留每一步的校验逻辑,别只图快,毕竟系统盘扩容这种东西,一次出错,付出的代价远比你省下的那几分钟多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询