Loading...

文章背景图

PVE 虚拟机 OVA 导出教程:ovaexport.sh 使用指南

2026-08-21
2
-
- 分钟

引言

如果你运维过 Proxmox VE(以下简称 PVE),大概率遇到过这样的场景:同事、客户或云平台要求你提供一个 OVA(Open Virtual Appliance) 格式的虚拟机镜像,以便导入 VMware vSphere、Workstation、Fusion 或 VirtualBox。

但 PVE 官方 至今 (2026年8月21日) 没有在 Web 界面提供「导出为 OVA」的按钮。默认情况下,你只能:

  1. 手动用 tar 解包/打包、用 qemu-img 把 qcow2/raw 转成 VMDK;

  2. 手写 OVF 描述符(XML 格式,字段繁琐且容易出错);

  3. 再拼装成最终的 .ova 归档。

这套流程不仅繁琐,还极易在 OVF 描述符的硬件映射、磁盘引用上踩坑。ovaexport.sh 正是为了解决这个痛点而生——它是一个纯 Bash 脚本,直接在 PVE 宿主机上运行,自动完成「读取配置 → 转换磁盘 → 生成 OVF/清单 → 打包 OVA」的完整链路。

ovaexport.sh 是什么

ovaexport.sh 来自开源项目 FikesMedia/Proxmox-OVA-Export,作者是 FikesMedia。它的定位非常清晰:让 PVE 用户能在 Shell 里一条命令导出 OVA 设备

它的核心能力可以概括为:

  • 读取真实配置:通过 pvesh API 直接拉取目标虚拟机的配置(CPU、内存、磁盘、网卡、机型、操作系统类型等),而不是靠人肉抄写;

  • 智能磁盘解析:支持 PVE 常见的多种存储后端,包括目录(Directory)、LVM、LVM-Thin、ZFS、NFS、CIFS、RBD(Ceph);

  • 统一转换为 VMDK:调用 qemu-img 把磁盘转换为 streamOptimized 子格式的 VMDK,这是 VMware 系平台导入时兼容性最好的流式格式;

  • 生成 OVF 1.0 描述符:自动把 PVE 的操作系统类型、硬件型号映射成 VMware/OVF 通用的 GuestOSID 与设备类型;

  • 打包 + 校验:生成 .ovf、.mf(SHA256 校验清单)和 disk-N.vmdk,再用 tar 封装成单个 .ova 文件。

脚本中内置了相当完整的 OS 类型映射表,例如 PVE 的 l26(Linux 2.6+ 64 位)映射为 otherLinux64Guest,win10 映射为 windows9_64Guest(VMware 用 windows9 表示 Win10),win11 映射为 windows11_64Guest,Windows Server 各版本也有对应 ID。这大幅降低了 OVF 里 GuestOS 识别错误导致「导入后无法开机」的概率。

依赖安装

脚本本身不依赖任何第三方库,只需要宿主机上存在若干命令行工具。脚本在启动时会通过 check_prerequisites 逐一检查,缺失即报错退出。

必需依赖清单

命令

用途

来源

qm

PVE 虚拟机管理

PVE 自带

pvesh

PVE Shell/API 调用

PVE 自带

jq

解析 API 返回的 JSON

需单独安装

qemu-img

磁盘格式转换

随 qemu-utils

tar

打包 OVA

系统自带

sha256sum

生成校验和

coreutils 自带

uuidgen

生成唯一 ID

随 uuid-runtime

bc

数值计算(容量换算)

需单独安装

stat

读取文件大小

coreutils 自带

其中 qm、pvesh、tar、sha256sum、stat 在标准 PVE 安装中已经存在,真正需要手动补齐的主要是 jq 和 bc(以及确认 qemu-img、uuidgen 在位)。

一键安装

在 PVE 宿主机上以 root 执行:

apt update && apt install -y jq bc qemu-utils uuid-runtime

安装完成后可以快速自检:

for cmd in qm pvesh jq qemu-img tar sha256sum uuidgen bc stat; do
  command -v "$cmd" >/dev/null 2>&1 && echo "OK  $cmd" || echo "MISS $cmd"
done

脚本下载与准备

  1. 下载脚本到宿主机。可以直接用 git,也可以手动下载单文件:

# 方式一:git clone
git clone https://github.com/FikesMedia/Proxmox-OVA-Export.git
cd Proxmox-OVA-Export

# 方式二:仅下载脚本文件
curl -o ovaexport.sh https://raw.githubusercontent.com/FikesMedia/Proxmox-OVA-Export/main/ovaexport.sh
  1. 赋予可执行权限:

chmod +x ovaexport.sh
  1. 脚本必须以 root 运行(需要读取 VM 配置、访问磁盘路径并执行转换命令)。

基本用法

脚本的调用方式非常简洁,只接收两个位置参数:

sudo ./ovaexport.sh <VMID> <output_ova_filepath>
  • <VMID>:要导出的目标虚拟机编号(纯数字,脚本会做格式校验);

  • <output_ova_filepath>:最终 .ova 文件的完整路径(必须带文件名,不能只给目录)。

示例

# 导出 VMID 108 到 /mnt/export 目录
sudo ./ovaexport.sh 108 /mnt/export/vm108.ova

# 导出到本地 home 目录
sudo ./ovaexport.sh 101 /root/vm101.ova

脚本运行时会输出带时间戳的日志,让你清楚看到每个环节的进度:

  • 读取到的 VM 名称、vCPU、内存、OS 类型、机型;

  • 遍历到的每块磁盘(ide/sata/scsi/virtio)及其解析后的源路径与格式;

  • 每块磁盘 qemu-img convert 的转换进度;

  • 生成的 OVF、MF 文件路径;

  • 最终 tar 打包结果。

tmp 临时目录设置(重点)

这是实际使用中最容易「翻车」的一环,也是很多用户第一次运行就失败的根因。理解它的工作机制,能避免大量磁盘空间相关的报错。

工作机制

脚本在转换磁盘前,会创建临时工作目录,把转换好的 VMDK 文件先放在这里,最后再统一打包。关键代码逻辑是:

WORKDIR=$(mktemp -d -p "${TMPDIR:-/tmp}" "ovaexport.${VMID}.XXXXXX")

拆解一下这行命令:

  1. mktemp -d 创建一个唯一命名的临时目录(ovaexport.<VMID>.XXXXXX,X 为随机字符);

  2. -p "${TMPDIR:-/tmp}" 指定父目录:优先使用环境变量 TMPDIR 指定的路径,若未设置则回退到系统默认的 /tmp。

也就是说,默认情况下所有转换的 VMDK 临时文件都会落在 /tmp。

磁盘空间需求

这里有一个关键认知:临时目录所需空间 约等于所有磁盘的「虚拟大小(virtual size)」,而不是「实际已用大小」。

  • PVE 的 qcow2 是稀疏格式,一块标称 100 GB 但只用了 20 GB 的盘,其 qcow2 文件可能只有 20 GB 出头;

  • 但转换过程中,脚本会把整块盘的容量展开处理,/tmp 需要能容纳对应大小的临时 VMDK;

  • 同时,最终输出目录(<output_ova_filepath> 所在位置)也需要足够空间存放打包好的 .ova。

常见问题:/tmp 空间不足

很多 PVE 节点的 /tmp 挂在根分区(/)上,而根分区往往不大。导出大容量虚拟机时,很可能在转换中途报「No space left on device」。

解决方案:把临时目录指向一个空间充足的挂载点,利用 TMPDIR 环境变量:

# 假设 /mnt/bigdata 是一个大容量存储
export TMPDIR=/mnt/bigdata/tmp
mkdir -p "$TMPDIR"
sudo ./ovaexport.sh 108 /mnt/export/vm108.ova

⚠️ 重要:sudo 默认会重置部分环境变量,直接 sudo ./ovaexport.sh ... 可能「看不到」你在普通 shell 里 export 的 TMPDIR。有两种稳妥写法:

# 方法一:sudo -E 保留环境变量
export TMPDIR=/mnt/bigdata/tmp
sudo -E ./ovaexport.sh 108 /mnt/export/vm108.ova

# 方法二:把变量写在 sudo 命令内联
sudo env TMPDIR=/mnt/bigdata/tmp ./ovaexport.sh 108 /mnt/export/vm108.ova

临时目录的生命周期

脚本通过 trap 机制保证无论正常结束、报错退出,还是被 Ctrl+C / kill 中断,都会自动清理临时目录:

trap 'log_info "Cleaning up temporary directory: $WORKDIR"; rm -rf "$WORKDIR"' EXIT HUP INT QUIT TERM

因此你一般不需要手动删除临时文件。但如果脚本被 kill -9(SIGKILL)强杀,trap 无法捕获,此时可能残留一个 ovaexport.<VMID>.XXXXXX 目录,记得手动清理以释放空间。

空间规划建议

在开始导出前,先用一条命令评估磁盘虚拟总大小,做到心中有数:

qemu-img info /path/to/vm-108-disk-0.qcow2 | grep "virtual size"

然后确保:临时目录剩余空间 ≥ 所有盘虚拟大小之和输出目录剩余空间 ≥ 最终 .ova 体积(通常因 streamOptimized 压缩,.ova 会比虚拟大小小,但预留充裕更稳妥)。

脚本工作原理速览

理解内部流程有助于排错。脚本大致按以下步骤执行:

  1. 权限与依赖检查:确认 root 身份,逐一校验必需命令;

  2. 读取 VM 配置:pvesh get /nodes/<node>/qemu/<VMID>/config 拉取 JSON,提取名称、CPU、内存、机型、OS 类型;

  3. 解析磁盘:遍历 ide/sata/scsi/virtio 四类磁盘键,跳过 CD-ROM 与 none 项,结合存储配置解析出每块盘的真实路径与源格式(目录→文件路径,LVM→/dev/vgname/lv,ZFS→/dev/zvol/...,RBD→rbd:pool/name);

  4. 转换磁盘:对每块盘执行

qemu-img convert -p -f <源格式> -O vmdk -o subformat=streamOptimized <源> <临时目录>/disk-N.vmdk
  • 失败时会先重试一次(改用自动探测源格式);

  • 生成 OVF:写入 .ovf 描述符,映射 CPU、内存、控制器(IDE/SCSI)、磁盘、网卡等硬件,并用 uuidgen 生成系统 ID;

  • 生成 MF 清单:对 .ovf 和每个 .vmdk 计算 SHA256,写入 .mf;

  • 打包:把上述文件用 tar 封装成单个 .ova。

支持范围与已知限制

支持的存储后端:Directory、LVM、LVM-Thin、ZFS(zvol)、NFS、CIFS、RBD(Ceph)。脚本对每种类型都写了专门的路径解析逻辑,这也是它比「手动 qm importdisk」更省心的地方。

需要注意的限制

  • 必须在目标 VM 所在的宿主机上运行:脚本靠 hostname -s 定位节点,再经 pvesh 读配置,天然面向单机;集群环境下请登录到承载该 VM 的节点执行;

  • VM 建议关机或停机导出:脚本直接读底层磁盘,若虚拟机正在写入,导出的磁盘可能处于不一致状态(虽然脚本未强制关机,但数据一致性风险需由使用者自行把控);

  • IDE 多控制器映射存在边界:源码注释里也提到,IDE 第 2、3 块盘(ide2/ide3,通常对应第二个 IDE 控制器)在 OVF 单控制器模型下可能产生映射冲突,脚本会给出 warning 并跳过;复杂磁盘拓扑建议导出后在目标平台人工核对;

  • streamOptimized VMDK 的取舍:该格式对 VMware 导入最友好(支持流式读取、体积较小),但它是「只读优化」格式,若你之后想再在 VMware 里扩容或频繁快照,可能需要二次转换。

常见问题与排错

Q1:运行提示 Required command 'jq' not found

缺失 jq。执行 apt update && apt install jq 后重试即可。同理,bc 缺失会报对应命令未找到。

Q2:转换中途报 No space left on device

临时目录空间不足。按上文方法用 TMPDIR 指向大容量挂载点,并确认输出目录也有足够空间。

Q3:Failed to retrieve configuration for VM <ID>

通常是 VMID 不存在、拼写错误,或脚本运行所在节点不是该 VM 的宿主节点。用 qm list 确认 VMID,并在正确节点上执行。

Q4:某块盘 Resolved disk path ... does not exist

磁盘路径解析失败,常见于存储类型特殊或卷名异常。检查该盘是否仍在配置中引用、存储是否在线。脚本对 RBD 之外的类型会尝试 qemu-img 预检并给出明确错误。

Q5:导入 VMware 后无法识别 OS 或网卡异常

OS 类型由 OVF 的 GuestOSID 决定,脚本已内置映射表,但个别冷门 OS 会回退到 otherGuest64;网卡则默认映射为 Vmxnet3,若客户机缺驱动(尤其老版 Windows)可尝试在 VMware 侧把网卡改成 E1000。

总结与展望

ovaexport.sh 把「PVE 导出 OVA」这一原本需要多步手工操作、极易出错的流程,收敛成了一条命令。它最大的价值在于:

  • 自动化:自动读配置、解析多类型存储、生成合规的 OVF/清单并打包;

  • 标准化:统一产出 streamOptimized VMDK + OVF 1.0,跨平台导入兼容性好;

  • 可维护:纯 Bash、零第三方依赖,逻辑透明,便于二次定制。

使用时牢记两点即可避开绝大多数坑:先补齐 jqbc 依赖再根据磁盘虚拟大小规划好临时目录与输出目录的空间(必要时用 TMPDIR 重定向临时目录)。

随着 PVE 生态的发展,官方也在逐步完善跨平台迁移能力(如 ESXi 导入功能),但「反向导出」目前仍主要依赖社区工具。ovaexport.sh 这类轻量脚本,正是填补这一空白的务实之选。如果你有定制需求(比如固定 VMX 版本、批量导出),读懂它的源码后也很容易在其基础上扩展。


参考资料:

原创

PVE 虚拟机 OVA 导出教程:ovaexport.sh 使用指南

本文链接: PVE 虚拟机 OVA 导出教程:ovaexport.sh 使用指南

本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

文章目录