具体的转换脚本如下: 该脚本可直接 在ubuntu 22.04 x86 环境运行
#!/bin/bash
# ============================================================
# 将 vmdk、qcow2 或 raw 格式的镜像转换为 Azure 兼容的固定 VHD,并保证对齐到 MB 边界。
#
# 支持的转换路径:
# 1) vmdk -> qcow2 -> raw -> (对齐检查) -> vhd(fixed)
# - 中间生成的 qcow2,使用与输入文件同名的 .qcow2 后缀文件,并予以保留
# - raw 临时文件会在脚本结束时删除
# 2) qcow2 -> raw -> (对齐检查) -> vhd(fixed)
# - 输入文件不删除,raw 临时文件会在脚本结束时删除
# 3) raw -> (对齐检查) -> vhd(fixed)
# - 输入文件不删除,不会产生额外 qcow2,raw 不删除(用户原文件)
#
# 依赖:
# - qemu-img (转换、检查、resize)
# - gawk (解析 JSON 输出获取 virtual-size)
#
# 用法:
# ./convert_vmdk_to_azure_vhd.sh <输入镜像文件> <输出VHD文件>
#
# 示例:
# ./convert_vmdk_to_azure_vhd.sh mydisk.vmdk mydisk-azure.vhd
# ./convert_vmdk_to_azure_vhd.sh rhel-9.2.qcow2 rhel9-azure.vhd
# ./convert_vmdk_to_azure_vhd.sh centos.raw centos-azure.vhd
#
# 修改要点:
# - 如果从 vmdk 转到 vhd,会永久保留中间生成的 qcow2 文件,文件名与输入同名但后缀为 .qcow2
# - 其余任意中间的 raw 文件在脚本结束时会自动删除
# - 强制在最终将 raw 转成 vhd 时,使用 -O vpc(QEMU 内部对 VHD 的识别)并指定 subformat=fixed,force_size
# - 对齐检查:利用 qemu-img info 获取大小,向上对齐到 MB 边界,并执行 qemu-img resize
# ============================================================
# -------------------------
# 可选配置
# -------------------------
QEMU_VERBOSE=true # 是否显示 qemu-img 的进度
# -------------------------
# 函数: 打印用法
# -------------------------
usage() {
echo "用法: $0 <输入镜像文件> <输出VHD文件>"
echo " 将 vmdk、qcow2 或 raw 格式磁盘镜像,对齐到 MB 边界后,"
echo " 转换为 Azure 兼容的固定大小 VHD(subformat=fixed)。"
echo ""
echo "示例:"
echo " $0 mydisk.vmdk mydisk-azure.vhd"
echo " $0 rhel-9.2.qcow2 rhel9-azure.vhd"
echo " $0 centos.raw centos-azure.vhd"
exit 1
}
# -------------------------
# 函数: 打印消息
# -------------------------
info() {
echo "[INFO] $*"
}
error() {
echo "[ERROR] $*" >&2
}
# -------------------------
# 检查命令行参数
# -------------------------
if [ "$#" -ne 2 ]; then
error "参数数量错误!"
usage
fi
INPUT_IMAGE="$1"
OUTPUT_VHD="$2"
# -------------------------
# 检查依赖
# -------------------------
if ! command -v qemu-img &> /dev/null; then
error "'qemu-img' 未安装或不可用,请先安装(如:sudo dnf install qemu-img)。"
exit 1
fi
if ! command -v gawk &> /dev/null; then
error "'gawk' 未安装或不可用,请先安装(如:sudo dnf install gawk)。"
exit 1
fi
# -------------------------
# 检查输入文件
# -------------------------
if [ ! -f "$INPUT_IMAGE" ]; then
error "输入镜像文件 $INPUT_IMAGE 不存在!"
exit 1
fi
# -------------------------
# 识别输入镜像格式
# -------------------------
INFO_JSON=$(qemu-img info --output=json "$INPUT_IMAGE")
if [ $? -ne 0 ]; then
error "无法获取镜像信息,请检查输入文件是否损坏或无效。"
exit 1
fi
FORMAT=$(echo "$INFO_JSON" | gawk 'match($0,/"format": "([^"]+)"/,a){print a[1]}')
if [ -z "$FORMAT" ]; then
error "无法识别输入镜像格式!请确保文件有效。"
exit 1
fi
info "检测到输入镜像格式: $FORMAT"
# 用于记录要删除的临时 raw 文件
TEMP_RAW_FILES=()
# -------------------------
# 步骤1: vmdk -> qcow2(若为 vmdk)
# 其余格式若非 qcow2/raw,可按需转 qcow2(示例中演示做法)
# -------------------------
INTERMEDIATE_QCOW2="$INPUT_IMAGE" # 如果输入就是 qcow2,这里直接用
if [ "$FORMAT" = "vmdk" ]; then
# 生成与输入同名但后缀为 .qcow2 的文件
dirpath="$(dirname "$INPUT_IMAGE")"
basename_noext="$(basename "$INPUT_IMAGE" .vmdk)" # 只去掉 .vmdk 后缀
INTERMEDIATE_QCOW2="$dirpath/${basename_noext}.qcow2"
info "输入是 vmdk,先转换为同名 qcow2:$INTERMEDIATE_QCOW2"
if [ "$QEMU_VERBOSE" = true ]; then
qemu-img convert -p -O qcow2 "$INPUT_IMAGE" "$INTERMEDIATE_QCOW2"
else
qemu-img convert -O qcow2 "$INPUT_IMAGE" "$INTERMEDIATE_QCOW2"
fi
if [ $? -ne 0 ]; then
error "vmdk 转 qcow2 失败!脚本终止。"
exit 1
fi
# 注意:与需求相符,vmdk -> qcow2 时,保留此 qcow2 并且与输入同名
# 因此此处不记录到临时文件清单,不会删除。
elif [ "$FORMAT" != "qcow2" ] && [ "$FORMAT" != "raw" ]; then
# 若是其他格式(如 vdi、vhd 等),演示做法:先转成中间 qcow2
# 这里可按需扩展
dirpath="$(dirname "$INPUT_IMAGE")"
base="$(basename "$INPUT_IMAGE")"
INTERMEDIATE_QCOW2="$dirpath/${base}.qcow2" # 仅示例,实际可再加工
info "输入镜像格式非 qcow2/raw/vmdk:先转换为 qcow2,并将该 qcow2 视为中间文件。"
if [ "$QEMU_VERBOSE" = true ]; then
qemu-img convert -p -O qcow2 "$INPUT_IMAGE" "$INTERMEDIATE_QCOW2"
else
qemu-img convert -O qcow2 "$INPUT_IMAGE" "$INTERMEDIATE_QCOW2"
fi
if [ $? -ne 0 ]; then
error "转换到 qcow2 失败,脚本终止!"
exit 1
fi
# 若走到此分支,一般是用户并未要求保留此 qcow2,可在最后删除
# 但示例中只特别强调 vmdk->qcow2 要保留
# 其余场景默认可视为临时文件
# (可根据实际需求决定是否删除,这里演示为临时文件)
fi
# -------------------------
# 步骤2: 若当前不是 raw,则 qcow2 -> raw
# -------------------------
INTERMEDIATE_RAW="$INTERMEDIATE_QCOW2"
if [ "$FORMAT" = "raw" ]; then
info "输入镜像本就是 raw,跳过 qcow2->raw 转换。"
INTERMEDIATE_RAW="$INPUT_IMAGE"
else
# 已经过 vmdk->qcow2 或 其他->qcow2 后,需要再转到 raw
if [ "$FORMAT" != "raw" ]; then
info "将 qcow2 转换为原始 raw 格式,以便进行对齐和最后的 vhd 转换。"
# 这里可用随机后缀避免冲突
TEMP_RAW="$(dirname "$INTERMEDIATE_QCOW2")/temp_raw_$$.raw"
if [ "$QEMU_VERBOSE" = true ]; then
qemu-img convert -p -O raw "$INTERMEDIATE_QCOW2" "$TEMP_RAW"
else
qemu-img convert -O raw "$INTERMEDIATE_QCOW2" "$TEMP_RAW"
fi
if [ $? -ne 0 ]; then
error "qcow2 转 raw 失败!"
exit 1
fi
INTERMEDIATE_RAW="$TEMP_RAW"
# 该 raw 文件要在脚本结束时删除
TEMP_RAW_FILES+=("$TEMP_RAW")
fi
fi
# -------------------------
# 步骤3: 对 raw 进行对齐检查 & resize
# -------------------------
info "对 $INTERMEDIATE_RAW 进行 MB 对齐检查..."
MB=$((1024*1024))
size=$(qemu-img info -f raw --output=json "$INTERMEDIATE_RAW" | gawk 'match($0,/"virtual-size": ([0-9]+)/,a){print a[1]}')
if [ -z "$size" ]; then
error "无法获取 $INTERMEDIATE_RAW 的 virtual-size!"
exit 1
fi
rounded_size=$(( (size + MB - 1) / MB * MB ))
remainder=$(( size % MB ))
if [ "$remainder" -eq 0 ]; then
info "镜像已对齐到 MB 边界,无需 resize。"
else
info "当前大小: $size 字节,不满足 MB 对齐。"
info "准备调整为对齐后大小: $rounded_size 字节..."
qemu-img resize "$INTERMEDIATE_RAW" $rounded_size
if [ $? -ne 0 ]; then
error "qemu-img resize 失败,请检查磁盘空间或权限!"
exit 1
fi
fi
# -------------------------
# 步骤4: 将对齐后的 raw 转换为 Azure 兼容的 fixed VHD (vpc)
# -------------------------
info "开始将 raw 转换为固定大小 VHD: $OUTPUT_VHD"
# vhd 在 qemu-img 中的格式名称为 "vpc"
# 强制 -f raw 以免 qemu-img 自动检测
# -O vpc 就是生成 VHD 格式,subformat=fixed,force_size 满足 Azure 要求
QEMU_OPTIONS="-f raw -O vpc -o subformat=fixed,force_size"
if [ "$QEMU_VERBOSE" = true ]; then
QEMU_OPTIONS="-p $QEMU_OPTIONS"
fi
qemu-img convert $QEMU_OPTIONS "$INTERMEDIATE_RAW" "$OUTPUT_VHD"
if [ $? -ne 0 ]; then
error "转换为 VHD (vpc) 失败,请检查磁盘空间或权限!"
exit 1
fi
info "转换成功,验证输出文件格式..."
if qemu-img info "$OUTPUT_VHD" | grep -q 'file format: raw'; then
info "输出文件 $OUTPUT_VHD 已成功转换为 Azure 兼容的固定大小 VHD。"
else
error "输出文件的格式验证失败,请手动检查!"
qemu-img info "$OUTPUT_VHD"
exit 1
fi
# -------------------------
# 步骤5: 清理中间的 raw 文件 (若有)
# 如果用户是 vmdk -> qcow2,则保留 qcow2,删除 raw
# 如果用户直接输入 qcow2 -> raw,也可删除 raw
# 如果用户输入 raw,则没有额外的 raw 文件可删
# -------------------------
if [ "${#TEMP_RAW_FILES[@]}" -gt 0 ]; then
info "删除脚本生成的临时 raw 文件: ${TEMP_RAW_FILES[*]}"
for file_path in "${TEMP_RAW_FILES[@]}"; do
if [ -f "$file_path" ]; then
rm -f "$file_path"
info "已删除: $file_path"
fi
done
fi
info "脚本执行完成!"
info "--------------------------------------------------------"
info "若输入是 vmdk,则中间生成的 qcow2 文件已保留:$INTERMEDIATE_QCOW2"
info "输出的 Azure VHD 文件:$OUTPUT_VHD"
info "可以将 $OUTPUT_VHD 上传或导入到 Azure。"
info "--------------------------------------------------------"
exit 0
本文版权归原作者zhaofujian所有,采用 CC BY-NC-ND 4.0 协议进行许可,转载请注明出处。