创建 NBD 镜像时,为什么要把 backing 的所有镜像全部设置为 NBD?
因为镜像链的解析是 qemu driver 负责的,NBD 和本地 file 都只是独立的块设备,没有解析能力。
现象
创建 NBD 镜像时报错:
cmd [qemu-img create -f qcow2 -b nbd://10.0.0.10:54195 -F qcow2 /dev/qbd/vol/vol-example.img] on [local] failed: ret[1] out[] err[qemu-img: /dev/qbd/vol/vol-example.img: Could not open backing file: Could not open '/data/image/centos79x64a': No such file or directory
Could not open backing image.]. exec_time is [0.093]s
怪不得创建 NBD 镜像的时候,会把 backing 的所有镜像全部设置为 NBD。之前很费解。
有一个误区
以为通过 NBD 找到 nbd://10.0.0.10:54195 head 镜像,那么 NBD 镜像的 backing 镜像,自然通过 NBD head 链接一起使用了。
现在看起来压根不是。
真正的原理
backing 仅仅是元数据信息,真正要读写信息的,还是在使用端,而且是分开去读的。
元数据上,一串 backing 体现为一串 img。
但是在读端,这些就是独立的、一个一个的 img,读端需要挨个去读 img。
qcow2 镜像链的基本原理
qcow2 的链式存储(CoW - Copy on Write)依赖于文件头(Header)中的元数据指针:
- Header 记录:当镜像 B 以镜像 A 为 Backing File 创建时,镜像 B 的 Header 中会以字符串形式硬编码写入 A 的路径(如
/data/image/centos79x64a)。 - 读写路由(Read/Write Routing):
- 写操作:始终只写入最新的镜像 B(Top Image)。
- 读操作:先查镜像 B 的 Cluster 分配表(L1/L2 Table)。
- 如果该数据块已被修改,直接读取 B;
- 如果未修改(Unallocated),则由 QEMU 的 Block Driver 沿着 Backing 路径去打开并读取基础盘 A。
QEMU 存储驱动架构解析 qcow
qemu block driver 解析逻辑:
[本地 qemu-img]
│
├─► 1. 连接网络协议:nbd://10.0.0.10:54195
│
├─► 2. 读取 NBD 镜像的 Header 元数据
│ │
│ └─► 解析出 Backing 路径字符串:"/data/image/centos79x64a"
│
└─► 3. 本地 Block Driver 校验链条安全性
│
├─► Driver 试图在 [本地文件系统] 打开:/data/image/centos79x64a
│
└─► 结果:本地无此文件 ──► 报错终止!
镜像链的解析是 qemu driver 负责的。
不管是 NBD,还是本地 file,它们仅仅是一个独立的块设备,没有解析能力。
解析能力是 qemu 的 driver 才有的。
就像有一个 tree 的数据结构,这个 tree 本身没有解析能力。
有解析能力的是算法程序。
小结
- backing 只是元数据信息,真正读写时读端要挨个去读每个 img;
- 镜像链的解析是 qemu driver 负责的,NBD 和本地 file 都只是独立块设备;
- 所以创建 NBD 镜像时,要把 backing 的所有镜像全部设置为 NBD,否则读端无法解析链条;
- 理解 qcow2 的 CoW 机制和 qemu block driver 的解析逻辑,是理解这类问题的关键。