PVE 虚拟机 QCOW2 迁移到 RAW 分区直通:完整教程与踩坑实录

作者:Mooloco

环境:PVE 8.x / Samsung 990 PRO 1TB / Windows 10 LTSC

目标:将 VM 从 QCOW2 虚拟磁盘迁移到 NVMe 物理分区直通,降低 CPU 开销


一、为什么要做这件事

QCOW2 格式虽然功能丰富(快照、精简配置),但代价是额外的 CPU 开销:

一次磁盘 I/O 的 CPU 周期消耗:

QCOW2 虚拟磁盘:     ~11000 周期  ← 含格式转换 + COW 开销
RAW 分区直通:        ~3300 周期  ← 仅块设备操作

CPU 开销降低约 70% ✅

二、准备工作

2.1 确认 VM 当前配置

qm config <VM_ID>
# 重点关注磁盘类型 (scsi0/virtio0) 和路径

2.2 确认目标硬盘

lsblk
# 找到你要迁移到的目标盘(如 /dev/nvme0n1)

2.3 关机

qm shutdown <VM_ID>
# 或
qm stop <VM_ID>

三、缩容数据盘(如有需要)

如果你的硬盘上没有空闲空间给新分区,需要从现有分区中割出空间。

3.1 严格的步骤顺序

卸载分区     ← 第一步
   ↓
e2fsck -f    ← 第二步:检查文件系统
   ↓
resize2fs    ← 第三步:先缩文件系统!
   ↓
parted resizepart  ← 第四步:再缩分区
   ↓
e2fsck       ← 第五步:验证
   ↓
重新挂载     ← 第六步

⚠️ 绝不能先缩分区再缩文件系统! 否则文件系统超出分区边界,数据无法访问。

3.2 ⚠️ 最大的坑:单位不一致

这是最容易翻车的地方,请务必注意:

# resize2fs 的 G 是 GiB(2^30 = 1,073,741,824 字节)
resize2fs /dev/nvme0n1p1 800G
# 这里的 800G ≈ 800 GiB
# parted 的 G 是 GB(10^9 = 1,000,000,000 字节)
parted /dev/nvme0n1 resizepart 1 800G
# 这里的 800G = 800 GB ≈ 745 GiB

两者不一致的后果:

resize2fs 800G → 文件系统 800 GiB
parted 800G    → 分区大小 745 GiB

→ 文件系统比分区大 55 GiB!
→ e2fsck 报错: "filesystem size is larger than physical size"

✅ 正确做法

进入 parted 后切换单位:

parted /dev/nvme0n1
(parted) unit GiB           ← 切换到 GiB!跟 resize2fs 保持一致
(parted) resizepart 1 800   ← 直接输入数字,不带单位
(parted) print

3.3 第二个坑:GPT 对齐导致差 1 MiB

即使单位和数值完全一致,仍可能遇到:

filesystem size: 209715200 blocks
physical size :  209714944 blocks
差: 256 blocks × 4096 字节/块 = 1 MiB

原因:GPT 分区默认按 1 MiB 对齐,parted 自动调整了结束位置,导致分区比文件系统小了 1 MiB。

修复方法

直接按 block 数指定文件系统大小:

# 确认分区未挂载
findmnt /dev/nvme0n1p1

# 无输出后执行,指定精确的 block 数(物理分区可容纳的最大 block 数)
resize2fs /dev/nvme0n1p1 209714944

# 验证
e2fsck -f /dev/nvme0n1p1

3.4 创建新分区

parted /dev/nvme0n1
(parted) unit GiB
(parted) print free          ← 查看空闲空间
(parted) mkpart primary <起始> <结束>
(parted) quit

新分区不要格式化,保持裸分区状态。


四、数据迁移:QCOW2 → RAW 分区

# 推荐使用 qemu-img convert(只搬实际数据,比 dd 快)
qemu-img convert -p -O raw \
   /var/lib/vz/images/<VM_ID>/vm-<VM_ID>-disk-0.qcow2 \
   /dev/nvme0n1p<分区号>

参数说明:

  • -p:显示进度
  • -O raw:输出为裸格式

qemu-img convert 只搬运实际写入的数据块,不会像 dd 那样搬整个虚拟大小,速度快很多。


五、VM 配置切换

5.1 获取稳定的 by-id 路径

ls -l /dev/disk/by-id/ | grep nvme | grep part<分区号>

找到类似这样的路径:

/dev/disk/by-id/nvme-Samsung_SSD_990_PRO_1TB_S6Z1NJ0W231490M-part2

为什么用 by-id 而不是 nvme0n1p2 因为 Linux 内核的 NVMe 设备名 nvme0n1 / nvme1n1 可能因启动时序不同而变化。by-id 基于硬盘序列号,永远稳定。详见本文末的踩坑记录

5.2 删除旧的磁盘引用

qm set <VM_ID> --delete scsi0

旧的 QCOW2 会变成 unused0 状态,仅移除引用,不删除文件。

5.3 添加 RAW 分区

qm set <VM_ID> --scsi0 /dev/disk/by-id/nvme-xxxx-part2,discard=on,ssd=1,iothread=1

推荐参数:

  • discard=on:启用 TRIM,让 SSD 及时回收空闲块
  • ssd=1:让 VM 识别为 SSD
  • iothread=1:独立 I/O 线程,提高高负载场景性能

5.4 设置 SCSI 控制器

qm set <VM_ID> --scsihw virtio-scsi-single

virtio-scsi-single 比默认的 virtio-scsi 有更好的多队列性能。

5.5 设置启动顺序

qm set <VM_ID> --boot order=scsi0;net0

六、验证与清理

6.1 启动 VM

qm start <VM_ID>

6.2 验证配置

qm config <VM_ID>
# 确认 scsi0 指向 by-id 路径

6.3 保留旧 QCOW2 作为回退方案

# 确认运行正常后,重命名而非删除
mv /var/lib/vz/images/<VM_ID>/vm-<VM_ID>-disk-0.qcow2 \
   /var/lib/vz/images/<VM_ID>/vm-<VM_ID>-disk-0.qcow2.bak

6.4 确认无误后删除旧镜像

运行一周无问题后,在 PVE Web 界面或通过命令行清理:

qm disk unlink <VM_ID> --idlist unused0
rm /var/lib/vz/images/<VM_ID>/vm-<VM_ID>-disk-0.qcow2.bak

七、性能收益

对比项QCOW2 虚拟磁盘RAW 分区直通
CPU 开销20-25%5-8%
4K 随机 IOPS~15K~35K+
连续读写受 QCOW2 格式转换限制接近设备上限
TRIM 支持需 QEMU 配合直通到设备

放弃的能力

功能说明
QCOW2 快照不再支持虚拟机级快照
精简置备 (Thin Provision)分区空间一次性分配
在线迁移(需共享存储)本地 RAW 分区不能热迁移

八、踩坑记录

坑 1:设备名变化导致 VM 找不到盘

现象: 重启 PVE 节点后,VM 启动停在 BIOS 界面,提示没有可引导的磁盘。

原因: 配置写的是 /dev/nvme0n1p2,但重启后内核枚举顺序变了,盘变成了 nvme1n1p2

根因: Linux 内核按驱动加载时序给 NVMe 命名,不是按物理插槽。GB-PVE 上有两个 NVMe 盘(990 Pro + SN730 系统盘),两个控制器谁先完成初始化谁就是 nvme0,时序受 ACPI 状态影响,每次启动不完全一致。

解决方法: 使用 /dev/disk/by-id/ 路径替代裸设备名。

/dev/disk/by-id/nvme-Samsung_SSD_990_PRO_1TB_xxxxx-part2

基于硬盘序列号,永远指向同一块盘,不受枚举顺序影响。

坑 2:resize2fs 和 parted 的单位不一致

详见本文第三节

坑 3:GPT 对齐导致的 1 MiB 差异

详见本文第三节

坑 4:Windows VirtIO 驱动导致 PE 看不到盘

现象: VM 无法启动后,想进 WinPE 修复引导,发现 diskpart 里看不到盘。

原因: Windows 修复环境默认不带 VirtIO 驱动,无法识别 VirtIO 磁盘。

解决方法: 挂载 virtio-win.iso 作为 VM 的第二个光驱,在 PE 启动时手动加载驱动。


附录:推荐 VM 配置示例

cpu: host
scsihw: virtio-scsi-single
scsi0: /dev/disk/by-id/nvme-Samsung_SSD_990_PRO_1TB_S6Z1NJ0W231490M-part2,discard=on,iothread=1,ssd=1
balloon: 0
agent: 1
boot: order=scsi0;net0

本文档由 Hermes Agent 整理,基于 Mooloco 的实际操作经验。

发表评论