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,安装方法:

  1. 配置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
  1. 安装

[root@localhost ~]# yum install qemu-kvm-rhev -y
测试机上有一台虚拟机

[root@localhost ~]# virsh list

 Id      Name                             State

----------------------------------------------------

10      vm                             running

虚拟机的磁盘文件为系统盘/data/vm.img,数据盘/data/data.img。

内存快照

  1. 创建快照

命令: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
  1. 回滚快照

命令: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

注:

  1. 只能对关机状态的虚拟机进行回滚快照;

  2. 内存快照做完后,如果虚拟机磁盘文件发生修改,可能会导致corruption。

磁盘内部快照

磁盘内部快照可以在虚拟机开机状态创建,但是创建过程中虚拟机处于paused状态,

  1. 创建快照

命令:virsh snapshot-create-as --domain vm --name vm1

这条命令执行后,虚拟机会变成paused状态

[root@localhost ~]# virsh list

 Id      Name                             State

----------------------------------------------------

 13      vm                             paused

等快照创建完成,会重新变回running。

  1. 查看快照

命令: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个缺点:

  1. 只支持qcow2格式的镜像文件;

  2. 创建快照虚拟机会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也会越来越长,变得难以维护。如果有些快照已经没用了可以进行删除。缩短这条链通常有两种思路:

  1. blockcommit,从top文件合并数据到base(下游镜像向backing file合并,称为“commit”);

  2. blockpull,从base文件合并数据到top(从backing file向下游镜像合并,称为“pull”)。截止目前只能将backing file合并至当前的active的镜像中,也就是说还不支持指定top的合并。

删除快照

在上面的backing chain中,如果我们要删除snapshot2,方法如下:

  1. blockcommit:把snapshot2的数据合并到snapshot1,合并完后backing chain变成了

base<-snapshot1<-snapshot2(内容为snapshot2+snapshot3)<-snapshot4*

  1. 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对外部快照的支持太弱,大部分操作需要我们人脑思考、手工操作。接下来研究的重点有以下几点:

  1. 测试外部快照创建时是否真正零停机时间;

  2. 虚拟机运行时进行快照文件合并对性能有何影响;

  3. 利用Python脚本封装外部快照的操作。