模板管理原理
QVMConsole 的模板系统是实现毫秒级创建虚拟机的核心基础设施。本文档从技术原理角度详细介绍模板的元数据结构、树形版本管理、克隆模式、删除策略和导入导出机制。
模板元数据系统
每个模板由两个文件组成:
| 文件 | 说明 |
|---|---|
<模板名>.qcow2 | 模板磁盘文件(qcow2 格式) |
<模板名>.meta.json | 模板元数据文件(由程序自动维护) |
元数据文件记录了模板的核心信息:
| 字段 | 说明 |
|---|---|
type | 模板类型:linux / windows / fnos / openwrt / other |
category | 二级分类(Linux 发行版、Windows 版本或 OpenWrt 衍生版) |
boot_type | 启动类型:bios / uefi |
boot_verified | 启动类型是否已确认验证 |
nvram_path | UEFI NVRAM 变量文件路径(默认同名 .nvram.fd) |
template_user | 模板中的默认用户名(用于克隆时的用户名重命名) |
cloud_init_mode | 初始化模式:nocloud / configdrive / fnos / openwrt / none |
post_boot_command | 克隆后的启动后命令(可选) |
post_boot_blocking | 启动后命令是否阻塞等待执行完成 |
default_config | 模板默认硬件配置(vCPU、内存、磁盘大小、磁盘总线、网卡模型、显示设备、CPU 拓扑模式、首次重启模式) |
template_uid | 模板族唯一标识(跨节点跟踪模板血缘) |
node_id | 当前模板节点唯一标识 |
parent_node_id | 父节点 ID(形成树形结构) |
root_node_id | 根节点 ID |
admin_name | 管理员侧名称 |
display_name | 用户侧显示文本 |
clone_visible | 是否允许普通用户克隆 |
disabled | 是否禁用克隆 |
md5 / sha256 | 磁盘文件校验哈希 |
file_size | 磁盘文件字节数 |
created_from_vm | 来源虚拟机名称 |
created_at | 创建时间 |
disk_compressed | 制作时是否压缩磁盘 |
source_transfer_mode | 源磁盘处理方式:copy(复制)/ move(移动) |
linux_init_status / linux_init_checked / linux_init_error | Linux 模板 cloud-init 依赖预处理状态(unknown / ready / failed) |
root_password 字段已废弃,仅用于读取旧版元数据;新制作的模板不再写入该字段。磁盘文件缺失 .meta.json 时,模板会按文件名推断类型,且 clone_visible 强制为 false(不允许普通用户克隆)。
模板文件和元数据文件在创建后会被自动设置为不可变属性(chattr +i),防止意外删除或修改。只有通过模板管理接口才能删除(删除前会先移除不可变属性)。
模板分类体系
系统支持以下模板类型和二级分类:
| 类型 | 二级分类 |
|---|---|
| Linux | Ubuntu(默认)、Debian、CentOS |
| Windows | WindowsServer2022(默认)、WindowsServer2025、Windows11、Windows10、WindowsServer2012R2、其它 |
| FnOS | 无二级分类 |
| OpenWrt | OpenWrt(默认)、iStoreOS |
| Other | 无二级分类 |
模板类型可通过以下方式确定:
- 用户在制作模板时显式指定
- 从模板名称自动推断:
- 名称包含
win/windows标识为 Windows - 名称包含
openwrt/lede/istoreos标识为 OpenWrt - 名称包含
fnos/nas标识为 FnOS
- 名称包含
从虚拟机制作模板时,若选择 other 类型,系统会强制关闭系统初始化(cloud_init_mode=none)并清空模板用户与启动后命令。
模板树形结构
QVMConsole 的模板系统采用树形链式结构管理模板版本关系。当从一个模板 A 克隆出虚拟机,再将该虚拟机制作为新模板 B 时,B 自动成为 A 的子节点。
每个模板节点在树中的属性:
| 属性 | 说明 |
|---|---|
level | 节点层级深度(根节点为 0) |
is_root | 是否为根节点 |
has_children | 是否有子模板 |
direct_vm_count | 直接关联的链式克隆虚拟机数量 |
tree_vm_count | 整棵子树中所有链式克隆虚拟机总数 |
children_count | 直接子模板数量 |
树形结构的构建过程
- 扫描模板目录下所有
.qcow2文件 - 加载每个模板的
.meta.json元数据 - 通过
parent_node_id字段建立父子关系 - 收集每个 VM 的模板来源信息(通过 libvirt XML 中的
template-source注解) - 按树形结构深度优先排序输出
启动类型自动检测
系统会自动检测模板磁盘的启动类型(BIOS 或 UEFI),检测逻辑如下:
UEFI 模板会自动复制 NVRAM 变量文件(.nvram.fd),克隆时为每个虚拟机创建独立的 NVRAM 副本。
模板默认配置继承
从虚拟机制作模板时,系统自动采集源 VM 的硬件配置作为模板默认配置(default_config):
| 配置项 | 采集方式 |
|---|---|
| vCPU 核心数 | 从 libvirt domain info 获取 |
| 内存大小 | 从 libvirt domain info 获取(转换为 GB) |
| 系统盘大小 | 从磁盘信息获取(qemu-img info) |
| 系统盘总线 | 从磁盘 XML 配置获取 |
| 网卡模型 | 从网络接口 XML 获取 |
| 视频模型 | 从 domain XML 解析 |
| CPU 拓扑模式 | 从 domain XML 解析 |
从模板克隆虚拟机时,这些默认配置会自动应用到创建表单,用户可按需覆盖。
模板制作磁盘策略
从虚拟机制作模板(POST /template/prepare)时,可以选择磁盘的压缩与转移方式:
| 配置项 | 取值 | 说明 |
|---|---|---|
| 压缩 | compress 布尔 | 压缩时使用 qemu-img convert -c 生成压缩 QCOW2 |
| 磁盘处理方式 | transfer_mode:copy(默认)/ move | 不压缩时选择复制或移动源系统盘 |
组合约束
| 组合 | 行为 |
|---|---|
| 不压缩 + 复制(默认) | 稀疏复制源磁盘并保留 backing 链,源虚拟机不受影响;兼容未传 transfer_mode 的旧调用方 |
| 压缩 | 生成压缩 QCOW2;若源虚拟机来自链式克隆,输出显式复用其直接父模板作为 backing,使物理磁盘链与 template_uid / parent_node_id / root_node_id 元数据保持一致 |
| 不压缩 + 移动 | 直接迁移源系统盘(保留原 backing),模板完整保存成功后删除源虚拟机 |
| 压缩 + 移动 | 非法组合,接口直接拒绝(移动磁盘时固定为不压缩) |
移动模式的安全保障
移动磁盘会删除源虚拟机,属于高风险操作(move_vm_disk_to_template 二次验证),并附加以下保障:
- 提交前校验源虚拟机未被锁定,锁定的虚拟机会被拒绝(提示先解锁)
- 移动过程中若模板落盘、依赖处理、校验或元数据写入失败,任务会尝试把系统盘移回原路径
- 只有模板校验和元数据保存成功后才开始删除源虚拟机;源虚拟机的快照、附加磁盘、凭据、锁、用户授权和缓存记录会随删除流程级联清理
移动模式不可逆:源虚拟机在模板保存成功后即被删除。请在目标模板可用之前不要中断任务;任务中途失败时系统会尽力移回磁盘,但请先确认源虚拟机状态再继续后续操作。
两种不压缩方式都会保留源磁盘的 backing,因此制作出的模板仍可作为子模板加入原模板族。
克隆模式
链式克隆
链式克隆通过 qemu-img create -f qcow2 -F qcow2 -b <模板路径> 创建基于模板的差分磁盘(backing file),磁盘只存储与模板的差异数据。
优点:创建速度快(毫秒级)、节省存储空间
缺点:依赖模板文件完整性,模板丢失将导致所有链式克隆虚拟机磁盘损坏
完整克隆
完整克隆通过 qemu-img convert -f qcow2 -O qcow2 将模板数据完整复制到新磁盘文件,与模板完全独立。
优点:独立运行,不依赖模板
缺点:创建较慢(取决于磁盘大小),占用完整存储空间
原生链式克隆
原生链式克隆是一种特殊模式,直接基于模板生成 backing 磁盘并启动虚拟机,不修改模板内的任何配置(主机名、用户名、密码等保持原样)。适用于快速测试和更新模板场景,仅管理员可用。
模板删除策略
系统提供三种模板删除策略:
级联删除(cascade)
删除模板及其整棵子树中的所有模板和关联虚拟机。适用于确认不再需要任何基于该模板的虚拟机和子模板。
提升子节点(promote_children)
将当前模板的子模板和直接关联 VM 的 backing file 通过 qemu-img rebase 指向父模板,然后删除当前节点。所有关联虚拟机必须已关机才能执行。
热提升子节点(promote_children_hot)
与普通提升类似,但支持运行中虚拟机热切换:
- 对于子模板:先复制一份临时文件,rebase 后通过
virsh blockcopy --pivot热切换运行中 VM - 对于直接关联 VM:通过
virsh blockpull将运行中 VM 拉平到父模板
热提升仅支持 running 或 shut off 状态的虚拟机,且不允许存在外部快照。
模板导入导出
导出机制
模板导出为异步任务(template_export),生成 .tar.gz 格式的模板包,包内结构:
<template>-template-export.tar.gz
├── manifest.json # 导出清单
├── <node_id_1>.qcow2 # 节点磁盘文件
├── <node_id_1>.meta.json # 节点元数据
├── <node_id_2>.qcow2
└── <node_id_2>.meta.json
manifest.json 包含:
- 版本号(v1)、导出时间、导出范围(
node/root) - 模板族 UID、根节点 ID
- 各节点的文件名、文件大小、MD5/SHA256 哈希校验值与完整元数据
支持两种导出范围:
- 单节点导出(
scope=node):仅导出指定模板节点 - 整棵子树导出(
scope=root):导出从根节点开始的整棵子树
导出时非 qcow2 格式的磁盘会自动转换为 qcow2 并重算哈希。模板包生成在模板导出目录(template_export_dir),通过 /template/download/:filename 下载;导出文件默认保留 24 小时,过期自动清理,也可手动删除。
导入机制
支持两种导入方式:
| 导入格式 | 说明 |
|---|---|
.tar.gz / .tgz | 标准模板包导入(含元数据和树形结构) |
.qcow2 | 旧版单文件导入(自动创建元数据,独立根节点) |
模板包分片上传
模板包通过分片上传接口(/template/upload/init、/upload/chunk、/upload/complete)上传,与"我的存储"共用同一套分片上传引擎:
- 秒传:初始化时按文件哈希与大小匹配已完成会话或已存在文件(抽样哈希校验),命中则直接完成
- 断点续传:初始化返回已接收分片列表,前端跳过已上传分片
- 完成校验:分片到齐后执行抽样哈希校验(2MB 窗口、1GB 步长、最少 3 个样本,尾部追加文件大小与文件名),校验失败会拒绝落盘
- 自动清理:上传会话 24 小时过期,后台每 30 分钟清理一次;可手动取消已上传的临时包
导入预览与确认
模板包导入采用"预览 → 确认"两步流程:
- 预览校验:解析
manifest.json,检查包完整性、每个节点的 MD5 和 SHA256 哈希、节点 ID 和文件名是否与本地模板冲突 - 模式判定:根据
template_uid判断是新建模板族(create)还是更新已有模板族(update);更新模式会先校验本地既有节点的完整性 - 确认导入:凭预览令牌提交异步导入任务;非 qcow2 磁盘自动转换为 qcow2;Linux 模板仅当依赖预处理未就绪时才补装 cloud-init 等依赖;导入后重算哈希、回填启动类型、落盘元数据并设置不可变属性
Linux 模板依赖预处理
Linux 模板要求磁盘内预装 cloud-init、growpart、QEMU Guest Agent 等克隆依赖。系统提供专门的预处理能力:
| 接口 | 说明 |
|---|---|
GET /template/:name/prepare-linux/check | 检查预处理链式依赖(若子树中仍存在链式克隆虚拟机,则禁止原地改写模板磁盘) |
POST /template/:name/prepare-linux | 提交异步任务(template_linux_prepare):离线安装依赖(使用来宾自带软件源)、重算哈希并把 linux_init_status 置为 ready |
从虚拟机制作模板时也会自动执行一次依赖预装;安装失败仅记录告警(linux_init_status=failed),不阻断模板制作。
VM 模板来源追踪
每个通过模板克隆的虚拟机,其 libvirt XML 中都会注入一个 template-source 自定义元数据注解,记录:
| 属性 | 说明 |
|---|---|
template_name | 来源模板名称 |
node_id | 来源模板节点 ID |
clone_mode | 克隆模式(linked / full) |
这个信息用于:
- 模板管理页面展示虚拟机关联关系
- 删除模板时确定受影响的虚拟机列表
- 从虚拟机再次制作模板时继承模板族血缘