PVE 基于 QCOW2 格式进行虚拟机硬盘迁移,实现 lvm 存储转目录

将 RAW 格式虚拟磁盘转为 QCOW2 格式,以便将 PVE 现有的 lvm 存储转为目录存储。

前言

在最初部署 PVE 的时候由于不太清楚 PVE 的存储类型和用途,把一块 3.68T 的 U2 SSD 初始化为了 lvm-thin 格式,lvm-thin 是基于块的 RAW 格式,导致现在只能用来存储虚拟机镜像和容器镜像,没办法用来存储 ISO 镜像和备份文件等,许多空间被浪费掉。因此想要把 lvm-thin 格式的这块盘初始化为目录挂载到 PVE 上,在此之前需要首先把所有的虚拟机存储转为 qcow2,以便在目录上使用。

该笔记也可以供需要迁移虚拟机时参考。

基础知识

PVE 支持的存储类型

名称PVE 类型级别支持共享支持快照是否稳定
ZFS (local)zfspool均是1
Directorydir文件2
BTRFSbtrfs文件实验性
NFSnfs文件2
CIFScifs文件2
Proxmox Backuppbs均是n/a
GlusterFSglusterfs文件2
CephFScephfs文件
LVMlvm3
LVM-thinlvmthin
iSCSI/kerneliscsi
iSCSI/libiscsiiscsidirect
Ceph/RBDrbd
ZFS over iSCSIzfs

基于块和 qcow2 格式的存储

基于块的存储

RAW 格式是一基础的磁盘镜像类型,没有任何元数据或附加结构的纯数据格式,类似于磁盘分区。它将虚拟硬盘的内容以原始字节的形式存储,没有任何额外的格式或元数据封装。这种格式的文件被虚拟机直接识别和使用,就像物理硬盘的一个分区或整个硬盘。

RAW 格式存储依靠宿主机的文件系统管理空间分配,包括对于“空洞”的处理,也就是在物理磁盘上未实际使用的空间。例如以 ext4 文件系统为例,这种机制被称为稀疏文件(sparse file),使得文件能够在不实际占用对应磁盘空间的情况下,显示为预设的较大尺寸。例如,当创建一个 100G 大小的 RAW 格式虚拟硬盘时,使用 ls 命令查看,显示的是文件的逻辑大小,也就是 100G,而使用 du 命令,显示的是实际的物理占用。

RAW 格式比较简单,对比基于文件的存储有更好的 IO 性能,但是不支持动态增长存储空间,而且在 tar 和 scp 等操作的时候使用的是逻辑大小,速度比较慢。

基于文件的 QCOW2 存储

QCOW2(QEMU Copy On Write Version 2)是一种 KVM 支持的磁盘镜像格式,支持写时拷贝特性和快照。

其优点主要有:

  • 支持动态扩展空间,无需 partition 和 format
  • 空间占用小,不需要预先分配空间,即使文件系统不支持空洞特性
  • 支持写时拷贝(COW),减少开销和提高资源利用率
  • 内建支持快照(Snapshot),允许进行增量备份和差异快照以及创建链式快照,支持多快照管理
  • 支持压缩,可以使用 zlib 进行压缩减少空间占用,但是会牺牲效率
  • 支持 AES 加密
  • 兼容性好,QCOW2 格式由 QEMU 支持,可以在多种虚拟化平台上使用,便于迁移

即使 qcow2 相比 RAW 有一点性能损失,但是在没有海量 IO 的情况下,综合来说在我的场景下,还是更具备优势的。

实操

我目前这块硬盘用了大约 700G 左右的空间,由于群晖的硬盘也承载在上面,所以没办法把转为 qcow2 后的文件放到群晖做临时中转。好在 PC 的固态空间足够,而且 PC 和 PVE 目前都具备万兆网卡,可以实现较快的数据中转。因此选择转换完 qcow2 之后通过万兆网络,拷贝到 PC 共享的 SMB 目录中。

当然也可以使用移动硬盘或者其他方式来完成传输。

转换硬盘格式

首先进入对应虚拟机的命令行,用0填充一次未使用空间,减小转换后镜像体积:

1
2
dd if=/dev/zero of=/home/zero.dat bs=2M status=progress
rm -rf /home/zero.dat

进入 PVE 命令行,使用 qemu-img 来转换硬盘格式,完整命令格式是:

1
convert [--object OBJECTDEF] [--image-opts] [--target-image-opts] [--target-is-zero] [--bitmaps [--skip-broken-bitmaps]] [-U] [-C] [-c] [-p] [-q] [-n] [-f FMT] [-t CACHE] [-T SRC_CACHE] [-O OUTPUT_FMT] [-B BACKING_FILE [-F BACKING_FMT]] [-o OPTIONS] [-l SNAPSHOT_PARAM] [-S SPARSE_SIZE] [-r RATE_LIMIT] [-m NUM_COROUTINES] [-W] FILENAME [FILENAME2 [...]] OUTPUT_FILENAME

使用 ls /dev/mapper 查看下硬盘名字,确认要转换的虚拟磁盘。

使用 qemu-img convert 来执行转换:

1
qemu-img convert -p -O qcow2 -c /dev/mapper/vm-100-disk-0 vm-100-disk-0.qcow2

其中:

  • -p 是显示传输进度
  • -O qcow2 指定目标格式
  • -c 进行压缩,这里也可以不压缩,还可以用 -c -o compression_type=zstd 指定 zstd 压缩
  • /dev/mapper/vm-100-disk-0 是要转换的虚拟磁盘
  • vm-100-disk-0.qcow2 是目标路径,这里放到当前目录下,可以修改其他目录。

传输文件

路由配置

承载 PVE 的物理机有一块双千双万的网卡;准备临时存放硬盘文件的 PC,同样同时具备万兆和 2.5G 网卡。为了确保使用万兆网卡进行传输,需要在 PVE 上配置静态路由。 这里假设 PC 万兆网卡的 IP 是 10.0.0.5,PVE 的万兆网卡名为 ens19,使用 ip route 来临时添加路由规则:

1
ip route add 10.0.0.5/32 dev ens19

配置完后, ping 10.0.0.5 检查下是否能够联通。

可以用 iperf3 测试下传输速度(Windows 可以用 scoop 来安装 iperf3):

iperf3 测试

挂载 SMB/CIFS

Windows 使用 SMB 来共享目录,PVE 这边需要挂载共享卷,可以使用 PVE 的 Web UI 来操作:

在数据中心 - 存储下,新建 SMB/CIFS,输入 Windows 共享的信息,建议 Windows 新开一个本地账户来做共享,不要使用微软账户,容易有奇怪的问题x_x 例如下图,我将共享目录挂载到 PVE 的 /pc 目录,内容全部勾选:

添加SMB存储 1/2

添加SMB存储 2/2

但是我这里遇到了 PVE WebUI 无法挂载 SMB 共享的情况,已经确认信息均正确的情况下也无法挂载,提示 create storage failed: storage ‘xxx’ is not online(500):

PVE WebUI 挂载 SMB 失败

如果遇到这种情况,可以换用命令行操作。进入 SSH,使用以下命令先扫描下 SMB,回车后需要输入对应账户的密码:

1
2
pvesm cifsscan <SMB_HOST_IP> --username <USERNAME> --pasword
# e.g. pvesm cifsscan 10.0.0.5 --username pve --pasword

如果检测正常,使用以下命令来挂载:

1
2
pvesm add cifs pc --server <SMB_HOST_IP> --share <共享目录> --path <挂载目录> --username <USERNAME> --password
# e.g. pvesm add cifs pc --server 10.0.0.5 --share pve --path /mnt/pve/pc --username pve --password

挂载成功后,去 WebUI 再次检查,不出意外应该已经在了:

挂载 PVE 成功

最后用 rsync 拷贝文件:

1
rsync --progress -h source_dir /mnt/pve/pc/disks/qcow

重新挂载硬盘

重新分区并挂载硬盘,再将 qcow2 文件拷贝回来之后,使用 qm disk import 来重新挂载虚拟磁盘:

1
2
qm disk import <vmid> <source> <storage> [OPTIONS]
# 例如: qm importdisk 100 vm-100-disk-0.qcow2 ssd
  • vmid 是虚拟机ID
  • source 是虚拟磁盘路径
  • storage 是存放虚拟磁盘的目标存储

参考资料


  1. 虚拟机的磁盘映像存储在 ZFS 卷(zvol)数据集中,这些数据集提供块设备功能。 ↩︎

  2. 在基于文件的存储中,可以使用 qcow2 格式来实现快照功能。 ↩︎ ↩︎ ↩︎ ↩︎

  3. 可以在基于 iSCSI 或 FC 的存储上使用 LVM。从而获得一个共享的 LVM 存储。 ↩︎

0%