news 2026/9/23 2:39:32

QEMU LUKS 分离卷头(Detached Header)完整指南:架构原理、qemu-img 创建与 libvirt QMP 热插拔实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
QEMU LUKS 分离卷头(Detached Header)完整指南:架构原理、qemu-img 创建与 libvirt QMP 热插拔实战
  • 虚拟化
  • 硬件仿真

【免费下载链接】qemu

Official QEMU mirror. Please see https://www.qemu.org/contribute/ for how to submit changes to QEMU. Pull Requests are disabled. Please only use release tarballs from the QEMU website.

项目地址:https://gitcode.com/gh_mirrors/qe/qemu
点击查看免费下载

LUKS 分离卷头(detached header)允许将卷头与密钥材料存放在独立于负载数据的磁盘/文件上,是 QEMU 自 9.0 起正式支持的能力(对应 QAPI 字段detached-header)。本文以 QEMU 仓库中的设计文档 docs/devel/luks-detached-header.rst 为主体,结合 crypto/、block/crypto.c 与 iotests 测试实现,系统讲解其设计动机、块设备图架构,并给出qemu-img创建、-blockdev启动配置与 libvirtvirsh qemu-monitor-command热插拔三条完整可运行的实操路径。读完本文,你将掌握分离卷头与普通 LUKS 卷的差异、QEMU 块层如何用header子节点关联两块存储,以及如何在创建和运行两个阶段把加密盘接入虚拟机。

背景:为什么需要把 LUKS 卷头单独存放

LUKS 格式本身支持将卷头存放在与负载(payload)不同的卷上。QEMU 的 LUKS 驱动(block/crypto.c + crypto/block-luks.c)扩展了这一能力,使 qemu-img 和 QEMU 块层都能读写这种"分离卷头"的加密卷。

普通 LUKS 卷在单块磁盘上的布局如下:

+-----------------------------------------------+ | | | | disk | header | key material | disk payload data | | | | | +-----------------------------------------------+

使用分离卷头后,需要两块磁盘:disk1存放卷头和密钥材料,disk2存放纯负载数据:

+--------------------------+ disk1 | header | key material | +--------------------------+ +---------------------+ disk2 | disk payload data | +---------------------+

这样做能带来四类实际收益(设计文档原文归纳):

  • 保密性(Secrecy):由于卷上没有卷头,disk2无法被识别为包含 LUKS 卷,不会暴露加密盘的身份。
  • 访问控制(Control):如果disk1的访问受到限制,即使有人拿到disk2也无法解锁。典型场景是把负载盘放在 NFS 上,但只向被指定的宿主机动态提供卷头访问权,从而限制哪些宿主机能从中启动 VM 实例。
  • 灵活性(Flexibility):应用数据卷往往已有固定大小,扩容来加加密层很不方便。此时可以单独存放 LUKS 卷头,直接复用现有存储卷作为负载。
  • 可恢复性(Recovery):卷头中一个比特的损坏可能让整个负载无法访问。分离卷头便于单独备份卷头;主卷头损坏时,仍可通过指向备份的分离卷头来解锁数据。

架构:块设备图上的两层父子关系

以 qcow2 加密为例,分离卷头在 QEMU 块设备图(block graph)中的拓扑如下(来自设计文档):

+-----------------------------+ Root node | foo[luks] | +-----------------------------+ | | file | header | | | +---------------------+ +------------------+ Child node |payload-format[qcow2]| |header-format[raw]| +---------------------+ +------------------+ | | file | file | | | +----------------------+ +---------------------+ Child node |payload-protocol[file]| |header-protocol[file]| +----------------------+ +---------------------+ | | Host storage Host storage

根节点foo[luks]有两个子节点:file(负载数据所在的 qcow2 格式节点,其下再挂 file 协议节点访问宿主存储)与header(LUKS 卷头和密钥材料所在的 raw 格式节点,同样经 file 协议节点落到另一份宿主存储)。

源码中这一拓扑的落点如下:

  • 在 block/crypto.c 的block_crypto_open_generic()中,驱动通过bdrv_open_child(..., "header", ...)BDRV_CHILD_METADATA角色打开header子节点,并将引用保存在BlockCrypto结构的BdrvChild *header字段(block/crypto.c)。
  • 一旦检测到header子节点存在,就会置位QCRYPTO_BLOCK_OPEN_DETACHED标志(block/crypto.c),再调用qcrypto_block_open()打开加密卷。
  • 在加密层内部,QCryptoBlock结构以bool detached_header字段记录这一状态(crypto/blockpriv.h)。

这一设计意味着:QEMU 块层并不要求负载必须是 qcow2——raw、qcow2 等任何可作为file子节点的格式都可以作为加密负载,卷头侧则统一以 raw 格式承载。

使用一:用 qemu-img 创建带分离卷头的 LUKS 盘

创建分离卷头与负载

第一步用qemu-img createdetached-header=true创建只含卷头的镜像文件;第二步创建负载镜像;第三步用 JSON 语法把它们组装成一个 LUKS 卷来查看信息:

# qemu-img create --object secret,id=sec0,data=abc123 -f luks \ -o cipher-alg=aes-256,cipher-mode=xts -o key-secret=sec0 \ -o detached-header=true test-header.img # qemu-img create -f qcow2 test-payload.qcow2 200G # qemu-img info 'json:{"driver":"luks","file":{"filename": \ "test-payload.img"},"header":{"filename":"test-header.img"}}'

三个命令的要点:

  • 第一个命令的-f luks -o detached-header=true是关键:block_crypto_co_create_luks()在解析选项时通过qemu_opt_get_bool_del(opts, "detached-header", false)读取该布尔选项(block/crypto.c),若为真则向加密层传递QCRYPTO_BLOCK_CREATE_DETACHED标志(block/crypto.c)。
  • 在加密层创建流程中,block->detached_header = flags & QCRYPTO_BLOCK_CREATE_DETACHED(crypto/block.c)。随后 crypto/block-luks.c 会做两件事:把payload_offset_sector置 0,表示卷头镜像本身从 0 扇区开始读写;并调用initfunc()为卷头镜像预留(header_sectors + QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS * split_key_sectors) * sector_size的空间,容纳卷头与全部密钥槽。
  • 第三个命令中file指向负载镜像、header指向卷头镜像,QEMU 通过qemu-img info的 JSON 协议描述即可同时解析两块镜像。qemu-img infoformat-specific输出中会包含detached-header字段(见下文测试章节),用于确认卷是否处于分离状态。

打开时如何判定"分离"状态

打开(而非创建)时,判定逻辑藏在负载偏移上:crypto/block-luks.c 中block->detached_header = (block->payload_offset == 0)。也就是说,LUKS 卷头中记录的payload_offset若为 0,则视为分离卷头——此时负载数据从文件起始处读写,而正常内嵌卷头情形下负载偏移等于卷头+密钥材料的长度。

QAPI 层面对齐

qemu-img info输出中的detached-header字段来自 QAPI 结构QCryptoBlockInfoLUKS,其定义位于 qapi/crypto.json,注释明确标注 "whether the LUKS header is detached (Since 9.0)"。该信息最终由 crypto/block-luks.c 的info->u.luks.detached_header = block->detached_header填充。设计文档与 QAPI 均将分离卷头能力标记为 QEMU 9.0 起引入。

使用二:启动 VM 时用 -blockdev 组装分离卷头 LUKS 盘

qemu-system-x86_64启动参数中,分离卷头盘需要四个块设备节点(存储层 ×2、格式层 ×2)再加一个 luks 格式层,共五个节点:

# qemu-system-x86_64 ... \ -object '{"qom-type":"secret","id":"libvirt-3-format-secret", \ "data":"abc123"}' \ -blockdev '{"driver":"file","filename":"/path/to/test-header.img", \ "node-name":"libvirt-1-storage"}' \ -blockdev '{"node-name":"libvirt-1-format","read-only":false, \ "driver":"raw","file":"libvirt-1-storage"}' \ -blockdev '{"driver":"file","filename":"/path/to/test-payload.qcow2", \ "node-name":"libvirt-2-storage"}' \ -blockdev '{"node-name":"libvirt-2-format","read-only":false, \ "driver":"qcow2","file":"libvirt-2-storage"}' \ -blockdev '{"node-name":"libvirt-3-format","driver":"luks", \ "file":"libvirt-2-format","header":"libvirt-1-format","key-secret": \ "libvirt-3-format-secret"}' \ -device '{"driver":"virtio-blk-pci","bus":XXX,"addr":YYY,"drive": \ "libvirt-3-format","id":"virtio-disk1"}'

节点组织与架构图的对应关系:

节点驱动作用对应架构图角色
libvirt-1-storagefile打开卷头镜像文件header-protocol[file]
libvirt-1-formatraw以 raw 格式解析卷头header-format[raw]
libvirt-2-storagefile打开负载镜像文件payload-protocol[file]
libvirt-2-formatqcow2以 qcow2 格式解析负载payload-format[qcow2]
libvirt-3-formatluks通过file+header绑定两者并解密foo[luks](根节点)

关键点:

  • luks 节点的file指向 qcow2 格式节点,header指向 raw 格式节点,key-secret指向解密用的 secret 对象。这个header参数正是 block/crypto.c 中bdrv_open_child(..., "header", ...)所消费的选项。
  • secret 对象通过data直接给出口令;生产环境建议改用file指向密钥文件以避免口令出现在命令行中。
  • busaddr需按实际 PCI 拓扑填写,示例中用XXX/YYY占位。

使用三:运行时向运行中的 VM 热插拔分离卷头盘(libvirt QMP)

若 VM 已由 libvirt 管理,可在运行期间通过virsh qemu-monitor-command以 QMP 消息逐步完成热插拔。整个过程与-blockdev参数一一对应,共 7 步。

第 1 步:注入解密 secret

# virsh qemu-monitor-command vm '{"execute":"object-add", \ "arguments":{"qom-type":"secret", "id": \ "libvirt-4-format-secret", "data":"abc123"}}'

第 2 步:添加卷头协议节点(file)

# virsh qemu-monitor-command vm '{"execute":"blockdev-add", \ "arguments":{"node-name":"libvirt-1-storage", "driver":"file", \ "filename": "/path/to/test-header.img" }}'

第 3 步:添加卷头 raw 格式节点

# virsh qemu-monitor-command vm '{"execute":"blockdev-add", \ "arguments":{"node-name":"libvirt-1-format", "driver":"raw", \ "file":"libvirt-1-storage"}}'

第 4 步:添加负载协议节点(file)

# virsh qemu-monitor-command vm '{"execute":"blockdev-add", \ "arguments":{"node-name":"libvirt-2-storage", "driver":"file", \ "filename":"/path/to/test-payload.qcow2"}}'

第 5 步:添加负载 qcow2 格式节点

# virsh qemu-monitor-command vm '{"execute":"blockdev-add", \ "arguments":{"node-name":"libvirt-2-format", "driver":"qcow2", \ "file":"libvirt-2-storage"}}'

第 6 步:添加 luks 格式节点,用 header 字段关联卷头

# virsh qemu-monitor-command vm '{"execute":"blockdev-add", \ "arguments":{"node-name":"libvirt-3-format", "driver":"luks", \ "file":"libvirt-2-format", "header":"libvirt-1-format", \ "key-secret":"libvirt-2-format-secret"}}'

注意:设计文档第 6 步示例中的key-secret写作libvirt-2-format-secret,与第 1 步创建的libvirt-4-format-secret名称不一致,实操时应保持 secret id 前后统一(例如统一改为libvirt-4-format-secret),否则解锁会因找不到 secret 而失败。这也提醒我们:header参数指定的是卷头格式节点,而key-secret指定的是口令对象,两者职责不同。

第 7 步:热插拔 virtio-blk 设备

# virsh qemu-monitor-command vm '{"execute":"device_add", \ "arguments": {"driver":"virtio-blk-pci", \ "drive": "libvirt-3-format", "id":"virtio-disk2"}}

至此,运行中的 VM 内会新增一块由分离卷头解密、qcow2 负载的加密 virtio 磁盘。要撤销可依次device_delblockdev-del逆序拆除节点。

源码级佐证:iotests 测试如何验证整个链路

仓库自带的集成测试 tests/qemu-iotests/tests/luks-detached-header(归入rw auto测试组,随qemu-iotests运行)覆盖了与本文完全一致的三种场景,可作为实操模板:

  • 创建阶段:测试同时使用两条路径创建分离卷头:一是blockdev-create"driver": imgfmt, "header": "luks-2-header-storage", "file": "luks-2-payload-storage"),二是qemu_img_create("-f", "luks", ..., "-o", "detached-header=true", ...),验证了 CLI 与 QMP 两条创建路径等价。
  • qemu-img info校验test_img_creation()断言普通 LUKS 盘的format-specific.data["detached-header"]False,而 raw 负载与 qcow2 负载两种分离卷头盘均为True,正好印证上文 QAPI 字段与打开判定逻辑。
  • I/O 验证test_detached_luks_header()分别对普通盘、raw 负载分离盘、qcow2 负载分离盘执行write -P/read -P模式写读,并在tearDown()中检查日志里是否出现 "Pattern verification failed"。测试里 qemu-io 的write -P 41(raw 负载)、write -P 42(qcow2 负载)等模式写读全部通过,说明分离卷头下的加解密路径与普通卷完全一致。

此外,打开分离卷头时加密层会做针对性优化:include/crypto/block.hQCRYPTO_BLOCK_OPEN_DETACHED标志的注释明确说明 "the open process will be optimized to skip the LUKS payload overlap check"(跳过 LUKS 负载重叠检查),即分离场景下无需校验负载是否与卷头区域重叠。

已知限制与展望

设计文档在 TODO 一节中记录了一项待办:

  1. 支持 VM 内共享同一分离 LUKS 卷头:目前一个卷头节点对应一个 luks 节点,多个盘共享同一卷头的场景(例如同一密钥加密的多个负载盘共用一个卷头文件)尚未实现,属于后续演进方向。

实际使用时还需注意:分离卷头盘的备份策略要分别对待——负载盘可以随意备份/复制,但卷头文件必须妥善保管并单独备份(这正是分离带来的恢复性优势);若卷头丢失,即使拥有负载盘也无法解密数据。

小结

  • 概念:分离卷头把 LUKS 卷头与密钥材料从负载中剥离,获得保密性、访问控制、灵活性与可恢复性四类收益。
  • 架构:QEMU 块层以luks根节点 +file(负载)/header(卷头)两个子节点组织,header子节点以BDRV_CHILD_METADATA角色挂载(block/crypto.c)。
  • 判定:创建侧由detached-header=true选项驱动(block/crypto.c),打开侧以payload_offset == 0判定(crypto/block-luks.c),qemu-img info通过detached-header字段(QEMU 9.0+)对外暴露(qapi/crypto.json)。
  • 实操:创建用qemu-img create -o detached-header=true,启动用 5 节点-blockdev链,热插拔用 7 步 QMP 消息,全部流程可对照 tests/qemu-iotests/tests/luks-detached-header 验证。

相关参考文件:设计文档、块层驱动、加密层 LUKS 实现、QAPI 定义、加密层标志、集成测试。

  • 虚拟化
  • 硬件仿真

【免费下载链接】qemu

Official QEMU mirror. Please see https://www.qemu.org/contribute/ for how to submit changes to QEMU. Pull Requests are disabled. Please only use release tarballs from the QEMU website.

项目地址:https://gitcode.com/gh_mirrors/qe/qemu
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 2:34:59

食品干燥技术:热风与红外耦合的Comsol仿真实践

1. 食品干燥技术概述食品干燥是食品加工中最基础也最关键的环节之一。作为一名在食品工程领域摸爬滚打十多年的从业者,我深知干燥工艺对食品品质的决定性影响。传统热风干燥虽然设备简单、成本低廉,但普遍存在能耗高、时间长、营养损失大等问题。而红外干…

作者头像 李华
网站建设 2026/9/23 2:34:10

瞳孔虹膜分割数据集实战:从标注验证到Unet/YOLO-seg训练全流程解析

简介:面向医学图像分割与计算机视觉研究者,提供一套高分辨率瞳孔与虹膜分割数据集。数据集中于人眼区域图像,统一为640640分辨率,标注图采用灰度mask格式,像素值0、1、2分别对应背景、瞳孔和虹膜,可直接用于…

作者头像 李华
网站建设 2026/9/23 2:32:30

AI服务的SLO与影子流量-在上线前证明它没变笨

摘要 传统服务的 SLO 通常围绕可用性与延迟,用在 AI 服务上会漏掉最重要的一环:质量。一个响应快、从不报错但答案越来越差的系统,在传统监控下表现完美。本文拆解 AI 服务应当定义的四类 SLO、为什么尾延迟比平均延迟更重要、影子流量如何用…

作者头像 李华
网站建设 2026/9/23 2:30:36

Akka 与 GraalVM Native Image:构建本地可执行文件的完整指南

Akka 与 GraalVM Native Image:构建本地可执行文件的完整指南 【免费下载链接】akka-core A platform to build and run apps that are elastic, agile, and resilient. SDK, libraries, and hosted environments. 项目地址: https://gitcode.com/gh_mirrors/ak/a…

作者头像 李华