KVM虚拟机快照研究(一)
KVM虚拟机的快照用来保存虚拟机在某个时间点的内存、磁盘或者设备状态,如果将来有需要可以把虚拟机的状态回滚到这个时间点。
根据被做快照的对象不同,快照可以分为磁盘快照和内存快照,两者加起来构成了一个系统还原点,记录虚拟机在某个时间点的全部状态;根据做快照时虚拟机是否在运行,快照又可以分为在线快照和离线快照。
磁盘快照根据存储方式的不同,又分为内部快照和外部快照:内部快照只支持qcow2格式的虚拟机镜像,把快照及后续变动都保存在原来的qcow2文件内;外部快照在创建时,快照被保存在单独一个文件中,创建快照时间点之后的数据被记录到一个新的qcow2文件中,原镜像文件成为新的qcow2文件的backing file(只读),在创建多个快照后,这些文件将形成一个链——backing chain。外部快照同时支持raw和qcow2格式的虚拟机镜像。
下文将分别具体介绍不同类型的KVM虚拟机快照。
操作环境:
-
操作系统:
[root@localhost ~]# cat /etc/redhat-release CentOS Linux release 7.4.1708 (Core)
-
Libvirt版本:
[root@localhost ~]# libvirtd --version libvirtd (libvirt) 3.2.0
-
qemu版本:
[root@localhost ~]# rpm -qa|grep qemu-kvm qemu-kvm-common-ev-2.3.0-29.1.el7.x86_64 qemu-kvm-ev-2.3.0-29.1.el7.x86_64
centos7.4的默认yum源中的qemu-kvm不支持在线创建外部快照,需要安装Redhat的qemu-kvm-ev,安装方法:
- 配置yum源
[root@localhost ~]# cat /etc/yum.repos.d/qemu-kvm-rhev.repo
[qemu-kvm-rhev]
name=oVirt rebuilds of qemu-kvm-rhev
baseurl=http://resources.ovirt.org/pub/ovirt-3.5/rpm/el7Server/
mirrorlist=http://resources.ovirt.org/pub/yum-repo/mirrorlist-ovirt-3.5-el7Server
enabled=1
skip_if_unavailable=1
gpgcheck=0
- 安装
[root@localhost ~]# yum install qemu-kvm-rhev -y
测试机上有一台虚拟机
[root@localhost ~]# virsh list
Id Name State
----------------------------------------------------
10 vm running
虚拟机的磁盘文件为系统盘/data/vm.img,数据盘/data/data.img。
内存快照
- 创建快照
命令:virsh save vm vm.snapshot1
[root@localhost ~]# virsh save vm vm.snapshot1
Domain vm saved to vm.snapshot1
创建完后虚拟机会关机:
[root@localhost ~]# virsh list --all
Id Name State
----------------------------------------------------
- vm shut off
- 回滚快照
命令:virsh restore vm.snapshot1
[root@localhost ~]# virsh restore vm.snapshot1
Domain restored from vm.snapshot1
[root@localhost ~]# virsh list
Id Name State
----------------------------------------------------
11 vm running
注:
-
只能对关机状态的虚拟机进行回滚快照;
-
内存快照做完后,如果虚拟机磁盘文件发生修改,可能会导致corruption。
磁盘内部快照
磁盘内部快照可以在虚拟机开机状态创建,但是创建过程中虚拟机处于paused状态,
- 创建快照
命令:virsh snapshot-create-as --domain vm --name vm1
这条命令执行后,虚拟机会变成paused状态
[root@localhost ~]# virsh list
Id Name State
----------------------------------------------------
13 vm paused
等快照创建完成,会重新变回running。
- 查看快照
命令:virsh snapshot-list –domain vm
[root@localhost ~]# virsh snapshot-list --domain vm
Name Creation Time State
------------------------------------------------------------
vm1 2018-03-06 10:37:57 +0800 running
快照回滚:virsh snapshot-revert --domain vm --snapshotname vm1
快照删除:virsh snapshot-delete --domain vm --snapshotname vm1
磁盘内部快照有2个缺点:
-
只支持qcow2格式的镜像文件;
-
创建快照虚拟机会paused,有停机时间,对于不能停机的线上业务来说是无法接受的。
磁盘外部快照
原理
假设虚拟机磁盘镜像文件为base,创建一个外部快照snapshot1,这时候的镜像之间的关系backing chain如下:
base<-snapshot1*
“*”表示目前active状态的镜像,base变为只读,snapshot1以base为backing file,虚拟机所有写入都发生在snapshot1,如果再创建一个外部快照snapshot2,backing chain会变成:
base<-snapshot1<-snapshot2*
snapshot2又以snapshot1为backing file,现在base和snapshot1都变成了只读。继续创建快照会加长这个backing chain:
base<-snapshot1<-snapshot2<-snapshot3<-snapshot4*
如果要回滚某个快照,就要把虚拟机使用的镜像指向该快照文件的backing file。例如,回滚到snapshot2,就要把虚拟机的镜像改为snapshot1;回滚到snapshot1,则要把虚拟机的镜像改为base。回滚到snapshot1会导致snapshot1之后的所有快照失效,因为他们在backing chain上游的backing file发生了变化(backing file只能是只读,如果数据发生变化,下游镜像也会失效)。
缩短链
随着快照数量变多,backing chain也会越来越长,变得难以维护。如果有些快照已经没用了可以进行删除。缩短这条链通常有两种思路:
-
blockcommit,从top文件合并数据到base(下游镜像向backing file合并,称为“commit”);
-
blockpull,从base文件合并数据到top(从backing file向下游镜像合并,称为“pull”)。截止目前只能将backing file合并至当前的active的镜像中,也就是说还不支持指定top的合并。
删除快照
在上面的backing chain中,如果我们要删除snapshot2,方法如下:
- blockcommit:把snapshot2的数据合并到snapshot1,合并完后backing chain变成了
base<-snapshot1<-snapshot2(内容为snapshot2+snapshot3)<-snapshot4*
- blockpull:把snapshot2的数据合并到snapshot3,合并完后backing chain变成了
base<-snapshot1<-snapshot3(内容为snapshot2+snapshot3)<-snapshot4*
具体操作
1. 创建外部快照
命令:virsh snapshot-create-as --domain vm --name snapshot1 --disk-only --atomic --no-metadata
--disk-only 有这个参数,snapshot-create-as命令就会创建磁盘外部快照;
--atomic 如果虚拟机有多个磁盘,则把为虚拟机所有磁盘创建快照的操作当做一个原子操作,要么全部成功,要么全部失败;
--no-metadata 不让libvirt记录快照的元数据。这个参数不是必须的,但是强烈建议使用,目前libvirt对外部快照支持不完整,只能创建,不能删除和回滚,如果要删除一个有外部快照的虚拟机,会出现以下报错:
[root@localhost ~]# virsh undefine vm
error: Failed to undefine domain test
error: Requested operation is not valid: cannot delete inactive domain with 1 snapshots
加上这个参数后,libvirt不再管理外部快照,删除和回滚都不会受影响了。
快照创建成功后,在虚拟机磁盘文件目录下会多出2个新文件vm.snapshot1和data.snapshot1,分别是系统盘和数据盘的快照文件,查看镜像信息可以看出,它们分别以原镜像为backing file,与之前原理中分析的一致:
[root@localhost data]# qemu-img info vm.snapshot1
image: vm.snapshot1
file format: qcow2
virtual size: 20G (21474836480 bytes)
disk size: 3.4M
cluster_size: 65536
backing file: /data/vm.img
backing file format: qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
refcount bits: 16
corrupt: false
[root@localhost data]# qemu-img info data.snapshot1
image: data.snapshot1
file format: qcow2
virtual size: 1.0G (1073741824 bytes)
disk size: 196K
cluster_size: 65536
backing file: /data/data.img
backing file format: qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
refcount bits: 16
corrupt: false
创建完后,虚拟机xml文件中使用的磁盘文件会libvirt自动被改成这两个新文件,这两个新文件处于active状态,原镜像变为只读。
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='none' io='native'/>
<source file='/data/vm.snapshot1'/>
<target dev='vda' bus='virtio'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
</disk>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='none' io='native'/>
<source file='/data/data.snapshot1'/>
<target dev='vdb' bus='virtio'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
</disk>
2. 回滚快照
Libvirt目前不支持回滚外部快照,只能纯手工操作。为了证明在回滚快照后虚拟机确实回到了快照记录的状态,我们在虚拟机中在/root下新建一个空文件test。然后关闭虚拟机并把虚拟机的磁盘改回vm.img和data.img,开机后会发现/root/test不见了,可以证明虚拟机文件系统回到了创建快照的时间点。
由上面的操作我们可以得出结论:回滚到某个快照,就是把虚拟机当前磁盘文件改为这个快照文件的backing file;快照名和快照文件名并不对应,例如创建snapshot1后产生的文件vm.snapshot1中记录的并不是快照snapshot1的内容,它的backing file才是。在下面介绍删除快照时,牢记这点尤其重要。
3.删除快照
在原理中已经介绍过,删除快照有blockcommit和blockpull两种思路,由于blockpull不支持指定top的合并,下面将只介绍blockcommit方式。我们先为虚拟机vm多创建几个快照,现在快照链为(以下操作都以系统盘为例,数据盘同理):
base<-snapshot1<-snapshot2<-snapshot3<-snapshot4*
qemu-img命令也可以查看链关系:
[root@localhost data]# qemu-img info --backing-chain vm.snapshot4
image: vm.snapshot4
file format: qcow2
virtual size: 20G (21474836480 bytes)
disk size: 452K
cluster_size: 65536
backing file: /data/vm.snapshot3
backing file format: qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
refcount bits: 16
corrupt: false
image: /data/vm.snapshot3
file format: qcow2
virtual size: 20G (21474836480 bytes)
disk size: 196K
cluster_size: 65536
backing file: /data/vm.snapshot2
backing file format: qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
refcount bits: 16
corrupt: false
image: /data/vm.snapshot2
file format: qcow2
virtual size: 20G (21474836480 bytes)
disk size: 196K
cluster_size: 65536
backing file: /data/vm.img
backing file format: qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
refcount bits: 16
corrupt: false
image: /data/vm.img
file format: qcow2
virtual size: 20G (21474836480 bytes)
disk size: 1.5G
cluster_size: 65536
Format specific information:
compat: 0.10
refcount bits: 16
现在我们要删除snapshot2,根据回滚快照时得出的结论,要回滚到snapshot2就是把虚拟机磁盘指向vm.snapshot1,所以删除snapshot2就要在不影响backing chain中其他文件的前提下,把vm.snapshot2的内容合并到vm.snapshot1,vm.snapshot1的内容发生了改变,也就不能回滚到snapshot2了,达到了删除快照的目的。操作命令如下:
virsh blockcommit --domain vm vda --base /data/vm.snapshot1 --top /data/vm.snapshot2 --wait –verbose
virsh blockcommit --domain vm vdb --base /data/data.snapshot1 --top /data/data.snapshot2 --wait –verbose
合并完后,使用qemu-img命令再次查看文件信息可以发现,vm.snapshot2已经不在backing chain中了:
[root@localhost data]# qemu-img info --backing-chain vm.snapshot4
image: vm.snapshot4
file format: qcow2
virtual size: 20G (21474836480 bytes)
disk size: 1.1M
cluster_size: 65536
backing file: /data/vm.snapshot3
backing file format: qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
refcount bits: 16
corrupt: false
image: /data/vm.snapshot3
file format: qcow2
virtual size: 20G (21474836480 bytes)
disk size: 388K
cluster_size: 65536
backing file: /data/vm.snapshot1
backing file format: qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
refcount bits: 16
corrupt: false
image: /data/vm.snapshot1
file format: qcow2
virtual size: 20G (21474836480 bytes)
disk size: 1.5M
cluster_size: 65536
backing file: /data/vm.img
backing file format: qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
refcount bits: 16
corrupt: false
image: /data/vm.img
file format: qcow2
virtual size: 20G (21474836480 bytes)
disk size: 1.5G
cluster_size: 65536
Format specific information:
compat: 0.10
refcount bits: 16
总结
三种快照中,只有磁盘外部快照可以不停机创建,所以这种快照最符合我们平时的需求,后续研究也重点关注外部快照。不幸的是libvirt对外部快照的支持太弱,大部分操作需要我们人脑思考、手工操作。接下来研究的重点有以下几点:
-
测试外部快照创建时是否真正零停机时间;
-
虚拟机运行时进行快照文件合并对性能有何影响;
-
利用Python脚本封装外部快照的操作。