引言
如果你运维过 Proxmox VE(以下简称 PVE),大概率遇到过这样的场景:同事、客户或云平台要求你提供一个 OVA(Open Virtual Appliance) 格式的虚拟机镜像,以便导入 VMware vSphere、Workstation、Fusion 或 VirtualBox。
但 PVE 官方 至今 (2026年8月21日) 没有在 Web 界面提供「导出为 OVA」的按钮。默认情况下,你只能:
手动用 tar 解包/打包、用 qemu-img 把 qcow2/raw 转成 VMDK;
手写 OVF 描述符(XML 格式,字段繁琐且容易出错);
再拼装成最终的 .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、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脚本下载与准备
下载脚本到宿主机。可以直接用 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赋予可执行权限:
chmod +x ovaexport.sh脚本必须以 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")拆解一下这行命令:
mktemp -d 创建一个唯一命名的临时目录(ovaexport.<VMID>.XXXXXX,X 为随机字符);
-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 会比虚拟大小小,但预留充裕更稳妥)。
脚本工作原理速览
理解内部流程有助于排错。脚本大致按以下步骤执行:
权限与依赖检查:确认 root 身份,逐一校验必需命令;
读取 VM 配置:pvesh get /nodes/<node>/qemu/<VMID>/config 拉取 JSON,提取名称、CPU、内存、机型、OS 类型;
解析磁盘:遍历 ide/sata/scsi/virtio 四类磁盘键,跳过 CD-ROM 与 none 项,结合存储配置解析出每块盘的真实路径与源格式(目录→文件路径,LVM→/dev/vgname/lv,ZFS→/dev/zvol/...,RBD→rbd:pool/name);
转换磁盘:对每块盘执行
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、零第三方依赖,逻辑透明,便于二次定制。
使用时牢记两点即可避开绝大多数坑:先补齐 jq 与 bc 依赖,再根据磁盘虚拟大小规划好临时目录与输出目录的空间(必要时用 TMPDIR 重定向临时目录)。
随着 PVE 生态的发展,官方也在逐步完善跨平台迁移能力(如 ESXi 导入功能),但「反向导出」目前仍主要依赖社区工具。ovaexport.sh 这类轻量脚本,正是填补这一空白的务实之选。如果你有定制需求(比如固定 VMX 版本、批量导出),读懂它的源码后也很容易在其基础上扩展。
参考资料: