目录
扩展阅读
The QCOW2 Image Format
为什么用ls和du显示出来的文件大小有差别?
Qcow2镜像格式解析
ROW/COW 快照技术原理解析
RAW
KVM 虚拟化中使用的镜像格式通常为 RAW 和 QCOW2 两种格式.
RAW 的原意是「未被加工的」, 所以 RAW 格式镜像文件又被称为 原始镜像 或 裸设备镜像, 从这些称谓可以看出, RAW 格式镜像文件能够直接当作一个块设备, 以供 GuestOS 使用. 也就是说 KVM 的 GuestOS 可以直接从 RAW 镜像中启动, 就如 HostOS 直接从硬盘中启动一般.
块设备: IO 设备中的一类, 将信息存储在固定大小的块中, 并且每个块都有自己的地址, 常用的块设备有硬盘.
因为 RAW 镜像文件赤裸裸的特性带来了下列好处:
- 使用 dd 指令创建一个 File 就能够模拟 RAW 镜像文件
- 性能较 QCOW2 要更高
- 支持裸设备的原生特性, 例如: 直接挂载
- 能够随意转换格式, 甚至作为其他两种格式转换时的中间格式
- 能够使用 dd 指令来追加 RAW 镜像文件的空间
相对的, RAW 镜像文件也具有一个非常大的缺陷, 就是不支持快照. 所以才有了后来 QCOW 和 QCOW2 的发展.
QCOW2 (QEMU-COW 2)
(摘自官方文档)QEMU copy-on-write format with a range of special features, including the ability to take multiple snapshots, smaller images on filesystems that don’t support sparse files, optional AES encryption, and optional zlib compression
QEMU-COW 镜像文件具有一系列特性, 支持包括 多重快照(能够创建基于之前镜像的新镜像, 速度更快), 占用更小的存储空间(不支持稀疏特性, 不会预先分配指定 Size 的存储空间), 可选的 AES 加密方式, 可选的 zlib 压缩方式 等功能.
QCOW2 镜像格式是 KVM-QEMU 支持的磁盘镜像格式之一, 其表现形式为在一个系统文件中模拟一个具有一定 Size 的块设备.
QCOW2 Header
每个 QCOW2 镜像文件都会以一个格式固定的 Header 开始.
typedef struct QCowHeader {
uint32_t magic;
uint32_t version;
# Version number 版本号 (valid values are 2 and 3)
uint64_t backing_file_offset;
# backing_file_offset: 表示 backing file 文件绝对路径的字符串相对于 QCOW2 镜像文件起始位置的偏移量
uint32_t backing_file_size;
# 因为上述的 backing file 文件绝对路径的字符串不是以'\0'结束的
# 所以需要通过 backing_file_size 来指出该字符串的长度
# 如果当前 Header 的镜像是一个 COW 镜像, 则存在 backing file 文件, 否则没有
uint32_t cluster_bits;
# cluster_bits: cluster 的位数, 表示 cluster 的大小
# 1 << cluster_bits (1 向左移 cluster_bits 位得到的就是 cluster 的大小)
# 一般是 512 byte <= cluster_bits <= 2 MB
uint64_t size; /* in bytes */
# size: 镜像文件以块设备呈现时的 Size(byte)
uint32_t crypt_method;
# crypt_method: 1 表示开启采用了 AES 加密;0 表示没有加密
uint32_t l1_size;
# L1 table(1 级索引) 可用的 8 字节项个数
uint64_t l1_table_offset;
# L1 table 相对于镜像文件在存储中起始位置的偏移量, 需要与 cluster 对齐
uint64_t refcount_table_offset;
# refcount table(引用计数表) 相对于镜像文件在存储中起始位置的偏移量, 需要与 cluster 对齐
uint32_t refcount_table_clusters;
# refcount table 占用多少个 cluster
uint32_t nb_snapshots;
# 镜像文件中所包含的快照数量
uint64_t snapshots_offset;
# snapshot table 相对于镜像文件在存储中起始位置的偏移量, 需要与 cluster 对齐
} QCowHeader;
NOTE 1: 上述所有的偏移量数值, 都是为了帮助 QCOW2 镜像文件定位相应的 metadata table.
NOTE 2: QCOW2 镜像文件格式的块设备数据都被储存在一个个 cluster 中, 而 QCOW2 Header 也是保存在一个 cluster 中
NOTE 3: L1 和 L2 tables 结合使用能够实现将磁盘镜像地址映射到镜像文件偏移
NOTE 4: 每一个 cluster 都有一个引用计数值, 当引用计数值为 0 时, 该 cluster 能够被删除
QCOW2 的 COW 特性
一个 QCOW2 镜像能够用于保存其它 QCOW2 镜像(模板镜像)的变化, 这样是为了能够保证原有镜像的内容不被修改, 这就是所谓的增量镜像. 增量镜像看着就像是一个独立的镜像文件, 其所有数据都是从模板镜像获取的. 仅当增量镜像中 clusters 的内容与模板镜像对应的 clusters 不一样时, 这些 clusters 才会被保存到增量镜像中.
当要从增量镜像中读取一个 cluster 时, QEMU 会先检查这个 cluster 在增量镜像中有没有被分配新的数据(被改变了). 如果没有, 则会去读模板镜像中的对应位置.
QCOW2 的快照
从原理的层面上来说, 一个增量镜像可以近似的当作一个快照, 因为增量镜像相对于模板镜像而言, 就是模板镜像的一个快照. 可以通过创建多个增量镜像来实现创建多个模板镜像的快照, 每一个快照(增量镜像)都引用同一个模板镜像. 需要注意的是, 因为模板镜像不能够修改, 所以必须保持为 read only, 而增量镜像则为可读写. 但需要注意的是增量镜像并不是真正的 QCOW2 镜像快照, 因为「真快照」是存在于一个镜像文件里的.
qemu-img 的基本使用
列举一些常用的 qemu-img 指令.
- 创建 QCOW2 镜像文件:
qemu-img create
# 指定容量为 4G
fanguiju@fanguiju:~$ qemu-img create -f qcow2 test.qcow2 4G
Formatting 'test.qcow2', fmt=qcow2 size=4294967296 encryption=off cluster_size=65536 lazy_refcounts=off
- 查看 QCOW2 镜像文件信息:
qemu-img info
fanguiju@fanguiju:~$ qemu-img info test.qcow2
image: test.qcow2
file format: qcow2
virtual size: 4.0G (4294967296 bytes)
disk size: 196K
cluster_size: 65536
Format specific information:
compat: 1.1
lazy refcounts: false
- 创建 QCOW2 的快照:
qemu-img snapshot
fanguiju@fanguiju:~$ qemu-img snapshot -c snap1 test.qcow2
# 再次查看 QCOW2 镜像文件信息, 可以看见 Snapshot list
fanguiju@fanguiju:~$ qemu-img info test.qcow2
image: test.qcow2
file format: qcow2
virtual size: 4.0G (4294967296 bytes)
disk size: 204K
cluster_size: 65536
Snapshot list:
ID TAG VM SIZE DATE VM CLOCK
1 snap1 0 2017-04-07 17:14:24 00:00:00.000
Format specific information:
compat: 1.1
lazy refcounts: false
- 再次创建 QCOW2 的快照2:
fanguiju@fanguiju:~$ qemu-img snapshot -c snap2 test.qcow2
fanguiju@fanguiju:~$ qemu-img info test.qcow2
image: test.qcow2
file format: qcow2
virtual size: 4.0G (4294967296 bytes)
disk size: 212K
cluster_size: 65536
Snapshot list:
ID TAG VM SIZE DATE VM CLOCK
1 snap1 0 2017-04-07 17:14:24 00:00:00.000
2 snap2 0 2017-04-07 17:17:54 00:00:00.000
Format specific information:
compat: 1.1
lazy refcounts: false
- 删除 QCOW2 镜像文件的快照:
fanguiju@fanguiju:~$ qemu-img snapshot -d snap1 test.qcow2
fanguiju@fanguiju:~$ qemu-img info test.qcow2
image: test.qcow2
file format: qcow2
virtual size: 4.0G (4294967296 bytes)
disk size: 208K
cluster_size: 65536
Snapshot list:
ID TAG VM SIZE DATE VM CLOCK
2 snap2 0 2017-04-07 17:17:54 00:00:00.000
Format specific information:
compat: 1.1
lazy refcounts: false
- 恢复 QCOW2 镜像文件的快照:
fanguiju@fanguiju:~$ qemu-img snapshot -a snap2 test.qcow2
- 创建基于 QCOW2 镜像文件的镜像
创建一个基于镜像 1(test.qcow2) 的镜像 2(test_1.qcow2), test.qcow2 成为了 test_1.qcow2 的 backing file. 对 test_1.qcow2 所作的 I/O 操作都不会影响到 test.qcow2. test.qcow2 仍能够作为其他镜像的 backing file. 前提是 test.qcow2 不被修改.
NOTE: 如果作为 backing file 的镜像文件被修改了, 那么会影响到所有基于它的镜像文件.
fanguiju@fanguiju:~$ qemu-img create -b test.qcow2 -f qcow2 test_1.qcow2
Formatting 'test_1.qcow2', fmt=qcow2 size=4294967296 backing_file='test.qcow2' encryption=off cluster_size=65536 lazy_refcounts=off
# 可以看出镜像 test_1.qcow2 的 backing file 是 test.qcow2
fanguiju@fanguiju:~$ qemu-img info test_1.qcow2
image: test_1.qcow2
file format: qcow2
virtual size: 4.0G (4294967296 bytes)
disk size: 196K
cluster_size: 65536
backing file: test.qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
- * 让 QCOW2 镜像文件脱离 backing file*:
qemu-img convert
convert 用于转换镜像文件的格式, 当源镜像和目的镜像的格式均为 qcow2 时, 就相当于将源镜像的当前状态复制到目标镜像.
同时也因为被转换出来的目的镜像不会包含任何源镜像的快照, 所以目的镜像能够摆脱 backing file.
# 将 test_1.qcow2 的状态复制到 test_1-merge.qcow2
fanguiju@fanguiju:~$ qemu-img convert -p -f qcow2 test_1.qcow2 -O qcow2 test_1-merge.qcow2
(100.00/100%)
fanguiju@fanguiju:~$ qemu-img info test_1.qcow2
image: test_1.qcow2
file format: qcow2
virtual size: 4.0G (4294967296 bytes)
disk size: 196K
cluster_size: 65536
backing file: test.qcow2
Format specific information:
compat: 1.1
fanguiju@fanguiju:~$ qemu-img info test_1-merge.qcow2
image: test_1-merge.qcow2
file format: qcow2
virtual size: 4.0G (4294967296 bytes)
disk size: 196K
cluster_size: 65536
Format specific information:
compat: 1.1
lazy refcounts: false
- 更改 QCOW2 镜像文件的 backing file
fanguiju@fanguiju:~$ qemu-img create -f qcow2 base.qcow2 1G
Formatting 'base.qcow2', fmt=qcow2 size=1073741824 encryption=off cluster_size=65536 lazy_refcounts=off
fanguiju@fanguiju:~$ qemu-img info test_1.qcow2
image: test_1.qcow2
file format: qcow2
virtual size: 4.0G (4294967296 bytes)
disk size: 196K
cluster_size: 65536
backing file: test.qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
fanguiju@fanguiju:~$ qemu-img rebase test_1.qcow2 -b base.qcow2
fanguiju@fanguiju:~$ qemu-img info test_1.qcow2
image: test_1.qcow2
file format: qcow2
virtual size: 4.0G (4294967296 bytes)
disk size: 196K
cluster_size: 65536
backing file: base.qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
- QCOW2 镜像文件转换成 RAW 格式:
fanguiju@fanguiju:~$ qemu-img convert test.qcow2 -O raw test.img
RAW 与 QCOW2 的区别
两者的区别之一就是是否具有稀疏(Sparse File)特性. (关于 Sparse File 和 holes 的详细介绍, 请查阅扩展阅读.)
RAW 支持 Sparse File, 其内部块中含有若干的 holes. 这些 holes 会被 HostOS Filesystems 管理. 如果我们创建了一个 RAW(20G) 镜像文件, 使用 ls 指令查看该文件大小为 20G, 但如果使用 du 指令来查看其大小时就会变得很小, 这是因为 RAW(20G) 文件中存在若干的 holes.
EXAMPLE:
$> qemu-img info rhel6_0.img
image: rhel6_0.img
file format: raw
virtual size: 20G (21474836480 bytes)
disk size: 1.0G
$> ls -lh rhel6_0.img
-rw-r--r-- 1 root root 20G Apr 7 23:15 rhel6_0.img
$> du -h rhel6_0.img
1.1G rhel6_0.img
相对的, 如果创建一个同为 20G 的 QCOW2 镜像文件, 无论是使用 ls 还是 du 指令查看到的文件 Size 都应该是一致的. 这说明 QCOW2 镜像文件除了含有作为一个块设备所需要的数据信息之外, 其自身还包含了内部块分配信息的记录.
EXAMPLE:
$> qemu-img info rhel6_0
image: rhel6_0
file format: qcow2
virtual size: 20G (21474836480 bytes)
disk size: 1.0G
cluster_size: 262144
Format specific information:
compat: 1.1
lazy refcounts: false
$> ls -lh rhel6_0
-rw-r--r-- 1 root root 1.1G Apr 6 19:26 rhel6_0
$> du -h rhel6_0
1.1G rhel6_0
需要注意的是: 虽然 Sparse File 特性会导致镜像文件的内部块中存在 holes, 但实际上 holes 是不会占用存储空间的, 无论是 RAW(Support Sparse File) 还是 QCOW2(Don’t Support Sparse Files), 两者的磁盘利用率相等, 因为物理硬盘的块数量是固定的, 不会受到 holes 的影响. 虽说 holes 不会影响最终的磁盘使用率, 不过 holes 能够引起某些应用进程的「误解」. 例如: 上面已经举例的 ls 指令, 除此之外, 在 scp RAW 镜像文件时, 会消耗更大的网络 I/O. 同样的, tar RAW 镜像文件时也会消耗更长的时间和 CPU. 这也算是 RAW 的一大缺点了, 一般的解决方法就是将 RAW 转换为 QCOW2 之后再进行压缩或传输.
当然, Sparse File 也是具有其优势的:
「The advantage of sparse files is that storage is only allocated when actually needed: disk space is saved, and large files can be created even if there is insufficient free space on the file system.」
Sparse FIle 的优势在于: 存储只有在实际需要时, 空间才会被分配. 存储的实际空间被保留了起来, 所以即使在文件系统上显示以及没有足够的可用空间时, 仍然可以创建大文件.
除了 Sparse File 特性的区别之外, 使用 RAW 启动的虚拟机会比 QCOW2 启动的虚拟机 I/O 效率更高一些(25%), 所以如果追求性能的话建议选用 RAW 格式.
$(function () { $('pre.prettyprint code').each(function () { var lines = $(this).text().split('\n').length; var $numbering = $('<ul/>').addClass('pre-numbering').hide(); $(this).addClass('has-numbering').parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($('<li/>').text(i)); }; $numbering.fadeIn(1700); }); });