本文将梳理 QEMU/KVM 快照相关的知识,以及在 OpenStack Nova 中使用 libvirt 来对 QEMU/KVM 虚机做快照的过程。
1. QEMU/KVM 快照
1.1 概念
QEMU/KVM 快照的定义:快照就是将虚机在某一个时间点上的磁盘、内存和设备状态保存一下,以备将来之用。它包括以下几类:
磁盘快照:磁盘的内容(可能是虚机的全部磁盘或者部分磁盘)在某个时间点上被保存,然后可以被恢复。
磁盘快照有两种:
内部快照 - 使用单个的 qcow2 的文件来保存快照和快照之后的改动。这种快照是 libvirt 的默认行为,现在的支持很完善(创建、回滚和删除),但是只能针对 qcow2 格式的磁盘镜像文件,而且其过程较慢等。
外部快照 - 快照是一个只读文件,快照之后的修改是另一个 qcow2 文件中。外置快照可以针对各种格式的磁盘镜像文件。外置快照的结果是形成一个 qcow2 文件链:original <- snap1 <- snap2 <- snap3。这里有文章详细讨论外置快照。
磁盘数据的保存状态:
在一个运行着的系统上,一个磁盘快照很可能只是崩溃一致的(crash-consistent) 而不是完整一致(clean)的,也是说它所保存的磁盘状态可能相当于机器突然掉电时硬盘数据的状态,机器重启后需要通过 fsck 或者别的工具来恢复到完整一致的状态(类似于 Windows 机器在断电后会执行文件检查)。(注:命令 qemu-img check -f qcow2 --output=qcow2 -r all filename-img.qcow2 可以对 qcow2 和 vid 格式的镜像做一致性检查。)
对一个非运行中的虚机来说,如果上次虚机关闭的时候磁盘是完整一致的,那么其被快照的磁盘快照也将是完整一致的。
内存状态(或者虚机状态):只是保持内存和虚机使用的其它资源的状态。如果虚机状态快照在做和恢复之间磁盘没有被修改,那么虚机将保持一个持续的状态;如果被修改了,那么很可能导致数据corruption。
系统还原点(system checkpoint):虚机的所有磁盘的快照和内存状态快照的集合,可用于恢复完整的系统状态(类似于系统休眠)。
关于 崩溃一致(crash-consistent)的附加说明:
应该尽量避免在虚机I/O繁忙的时候做快照。这种时候做快照不是可取的办法。
vmware 的做法是装一个 tools,它是个 PV driver,可以在做快照的时候挂起系统
似乎 KVM 也有类似的实现 QEMU Guest Agent,但是还不是很成熟,可参考 http://wiki.libvirt.org/page/Qemu_guest_agent
快照还可以分为 live snapshot(热快照)和 Clod snapshot:
Live snapshot:系统运行状态下做的快照
Cold snapshot:系统停止状态下的快照
libvit 做 snapshot 的各个 API:
分别来看看这些 API 是如何工作的:
-
virDomainSnapshotCreateXML (virDomainPtr domain, const char * xmlDesc, unsigned int flags)
作用:根据 xmlDesc 指定的 snapshot xml 和 flags 来创建虚机的快照。
其内部实现根据虚机的运行状态有两种情形:
对运行着的虚机,API 使用 QEMU Monitor 去做快照,磁盘镜像文件必须是 qcow2 格式,虚机的 CPU 被停止,快照结束后会重新启动。
对停止着的虚机,API 调用 qemu-img 方法来操作所有磁盘镜像文件。
这里有其实现代码,可见其基本的实现步骤:
- virDomainSave 相关的几个 API
这几个API 功能都比较类似:
Features/SnapshotsMultipleDevices 这篇文章(http://wiki.qemu.org/Features/SnapshotsMultipleDevices)讨论同时对多个磁盘做快照的问题。
1.2 使用 virsh 实验
1.2.1 virsh save 命令
对运行中的 domain d-2 运行 “virsh save” 命令。命令执行完成后,d-2 变成 “shut off” 状态。
看看 domain 的磁盘镜像文件和 snapshot 文件:
内存数据被保存到 raw 格式的文件中。
要恢复的时候,可以运行 “vish restore d-2.snap1” 命令从保存的文件上恢复。
1.2.2 virsh snapshot-create/snapshort-create-as
先看看它的用法:
其中一些参数,比如 --atomic,在一些老的 QEMU libary 上不支持,需要更新它到新的版本。根据 这篇文章(http://wiki.qemu.org/Features/SnapshotsMultipleDevices),atomic 应该是 QEMU 1.0 中加入的。
(1)默认的话,该命令创建虚机的所有磁盘和内存做内部快照,创建快照时虚机处于 paused 状态,快照完成后变为 running 状态。持续时间较长。
每个磁盘的镜像文件都包含了 snapshot 的信息:
你可以运行 snapshot-revert 命令回滚到指定的snapshot。
virsh # snapshot-revert instance-0000002e 1433950148
根据 这篇文章(http://www.redhat.com/archives/libvir-list/2012-October/msg01390.html),libvirt 将内存状态保存到某一个磁盘镜像文件内 (”state is saved inside one of the disks (as in qemu's 'savevm'system checkpoint implementation). If needed in the future,we can also add an attribute pointing out which disk saved the internal state; maybe disk='vda'.)
(2)可以使用 “--memspec” 和 “--diskspec” 参数来给内存和磁盘外部快照。这时候,在获取内存状态之前需要 Pause 虚机,就会产生服务的 downtime。
(3)可以使用 “--disk-only” 参数,这时会做所有磁盘的外部快照,但是不包含内存的快照。不指定快照文件名字的话,会放在原来的磁盘文件所在的目录中。多次快照后,会形成一个外部快照链,新的快照使用前一个快照的镜像文件作为 backing file。
而第一个外部快照的镜像文件是以虚机的原始镜像文件作为 backing file 的:
目前还不支持回滚到某一个extrenal disk snapshot。这篇文章(https://lists.fedoraproject.org/pipermail/virt/2015-March/004236.html) 谈到了一个workaround。
(4)还可以使用 “--live” 参数创建系统还原点,包括磁盘、内存和设备状态等。使用这个参数时,虚机不会被 Paused(那怎么实现的?)。其后果是增加了内存 dump 文件的大小,但是减少了系统的 downtime。该参数只能用于做外部的系统还原点(external checkpoint)。
注意到加 “--live” 生成的快照和不加这个参数生成的快照不会被链在一起:
注意到加 “--live” 生成的快照和不加这个参数生成的快照不会被链在一起:
综上所述,对于 snapshot-create-as 命令来说,
可以使用 sanpshot-revert 命令来回滚到指定的系统还原点,不过得使用 “-force” 参数:
1.3 外部快照的删除
目前 libvirt 还不支持直接删除一个外部快照,可以参考 这篇文章 介绍的 workaround。
2. OpenStack 中的快照
OpenStack Snapshot 可分为下面的几种情形:
2.1 对 Nova Instance 进行快照
(1)对从镜像文件启动的虚机做快照
只将运行当中的虚机的 Root disk (第一个vd 或者 hd disk) 做成 image,然后上传到 glance 里面
Live Snapshot:对满足特定条件(QEMU 1.3+ 和 Libvirt 1.0.0+,以及 source_format not in ('lvm', 'rbd') and not CONF.ephemeral_storage_encryption.enabled and not CONF.workarounds.disable_libvirt_livesnapshot,以及能正常调用 libvirt.blockJobAbort ,其前提条件可参考这文章)的虚机,会进行 Live snapshot。Live Snapshot 允许用户在虚机处于运行状态时不停机做快照。
Cold Snapshot:对不能做 live snapshot 的虚机做 Cold snapshot。这种快照必须首先 Pause 虚机。
(2)对从卷启动的虚机做快照
对虚机的每个挂载的 volume 调用 cinder API 做 snapshot。
Snapshot 出的 metadata 会保存到 glance 里面,但是不会有 snapshot 的 image 上传到 Glance 里面。
这个 snapshot 也会出现在 cinder 的数据库里面,对 cinder API 可见。
2.2 对卷做快照
调用 cinder driver api,对 backend 中的 volume 进行 snapshot。
这个 snapshot 会出现在 cinder 的数据库里面,对 cinder API 可见。
3. 从镜像文件启动的 Nova 虚机做快照
严格地说,Nova 虚机的快照,并不是对虚机做完整的快照,而是对虚机的启动盘(root disk,即 vda 或者 hda)做快照生成 qcow2 格式的文件,并将其传到 Glance 中,其作用也往往是方便使用快照生成的镜像来部署新的虚机。Nova 快照分为 Live Snapshot (不停机快照)和 Clold Snapshot (停机快照)。
3.1 Nova Live Snapshot
满足 2.1.1 中所述条件时,运行命令 ”nova image-create
找到虚机的 root disk (vda 或者 hda)。
在 CONF.libvirt.snapshots_directory 指定的文件夹(默认为 /var/lib/nova/instances/snapshots)中创建一个临时文件夹,在其中创建一个 qcow2 格式的 delta 文件,其文件名为 uuid 字符串,该文件的 backing file 和 root disk 文件的 backing file 相同 (下面步骤 a)。
调用 virDomainGetXMLDesc 来保存 domain 的 xml 配置。
调用 virDomainBlockJobAbort 来停止对 root disk 的活动块操作 (Cancel the active block job on the given disk)。
调用 virDomainUndefine 来将 domain 变为 transimit 类型的,这是因为 BlockRebase API 不能针对 Persistent domain 调用。
调用 virDomainBlockRebase 来将 root disk image 文件中不同的数据拷贝到 delta disk file 中。(下面步骤 b)
步骤 6 是一个持续的过程,因为可能有应用正在向该磁盘写数据。Nova 每隔 0.5 秒调用 virDomainBlockJobInfo API 来检查拷贝是否结束。
拷贝结束后,调用 virDomainBlockJobAbort 来终止数据拷贝。
调用 virDomainDefineXML 将domain 由 transimisit 该回到 persistent。
调用 qemu-img convert 命令将 delta image 文件和 backing file 变为一个 qcow2 文件 (下面步骤 c)
将 image 的元数据和 qcow2 文件传到 Glance 中。
来看看其中的一个关键 API int virDomainBlockRebase (virDomainPtr dom, const char * disk, const char * base, unsigned long bandwidth,unsigned int flags)
该 API 从 backing 文件中拷贝数据,或者拷贝整个 backing 文件到 @base 文件。Nova 中的调用方式为:domain.blockRebase(disk_path, disk_delta, 0,libvirt.VIR_DOMAIN_BLOCK_REBASE_COPY |libvirt.VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT |libvirt.VIR_DOMAIN_BLOCK_REBASE_SHALLOW)默认的话,该 API 会拷贝整个@disk 文件到 @base 文件,但是使用 VIR_DOMAIN_BLOCK_REBASE_SHALLOW 的话就只拷贝差异数据(top data)因为 @disk 和 @base 使用相同的 backing 文件。VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT 表示需要使用已经存在的 @base 文件因为 Nova 会预先创建好这个文件。
简单的示意图:
这里(https://gist.github.com/rmk40/4617831) 有个过程的 PoC 代码描述该过程。
这里 (https://kashyapc.fedorapeople.org/virt/openstack/nova_live_snapshot_libvirt_analysis.txt)有该过程的完整 libvirt 日志分析。
这里 (http://wiki.qemu.org/Features/SnapshotsMultipleDevices)有文章讲 Libvirt Features/SnapshotsMultipleDevices。
3.2 Nova Cold Snapshot
当虚机不在运行中时或者不满足 live snapshot 的条件的情况下,Nova 会执行 Cold snapshot。其主要过程如下:
(1)当虚机处于 running 或者 paused 状态时:
detach PCI devices
detach SR-IOV devices
调用 virDomainManagedSave API 来将虚机 suspend 并且将内存状态保存到磁盘文件中。
(2)调用 qemu-img convert 命令将 root disk 的镜像文件转化一个相同格式的镜像文件。
(3)调用 virDomainCreateWithFlags API 将虚机变为初始状态
(4)将在步骤1 中卸载的 PCI 和 SR-IOV 设备重新挂载回来
(5)将元数据和 qcow2 文件传到 Glance 中
4. 从 volume 启动的 Nova 实例的快照
(0)从卷启动虚机,并且再挂载一个卷,然后运行 nova image-create 命令。
(1)从 DB 获取该虚机的块设备( Block Devices Mapping)列表。
(2)对该列表中的每一个卷,依次调用 Cinder API 做快照。对 LVM Driver 的 volume 来说,执行的命令类似于 " lvcreate --size 100M --snapshot --name snap /dev/vg00/lvol1“。
(3)将快照的 metadata 放到 Glance 中。(注:该 image 只是一些属性的集合,比如 block
device mapping, kernel 和 ramdisk IDs 等,它并没有 image 数据, 因此其 size 为 0。)
5. 当前 Nova snapshot 的局限
Nova snapshot 其实只是提供一种创造系统盘镜像的方法。不支持回滚至快照点,只能采用该快照镜像创建一个新的虚拟机。
在虚机是从 image boot 的时候,只对系统盘进行快照,不支持内存快照,不支持系统还原点 (blueprint:https://blueprints.launchpad.net/nova/+spec/live-snapshot-vms)
Live Snapshot 需要用户进行一致性操作:http://www.sebastien-han.fr/blog/2012/12/10/openstack-perform-consistent-snapshots/
只支持虚拟机内置(全量)快照,不支持外置(增量)快照。这与当前快照的实现方式有关,因为是通过 image 进行保存的。
从 image boot 的虚机的快照以 Image 方式保存到 Glance 中,而非以 Cinder 卷方式保存。
过程较长(需要先通过存储快照,然后抽取并上传至 Glance),网络开销大。
那为什么 Nova 不实现虚机的快照而只是系统盘的快照呢?据说,社区关于这个功能有过讨论,讨论的结果是不加入这个功能,原因主要有几点:
这应该是一种虚拟化技术的功能,不是云计算平台的功能。
openstack 由于底层要支持多种虚拟化的技术,某些虚拟化技术实现这种功能比较困难。
创建的 VM state snapshot 会面临 cpu feature 不兼容的问题。
目前 libvirt 对 QEMU/KVM 虚机的外部快照的支持还不完善,即使更新到最新的 libvirt 版本,造成兼容性比较差。
作者:刘世民(Sammyliu)
博客:http://www.cnblogs.com/sammyliu/