1.openstack快照
1.1.OpenStack快照类别
注意:不要混淆为卷创建快照和为以卷作为后端存储的虚拟机创建快照
虚拟机快照(快照存放于glance):nova image-create <server> <name>
- cold snapshot:创建snapshot时,需暂停虚拟机。
- live snapshot with no downtime:创建snapshot时,无需暂停虚拟机。(目前未实现,存在的问题见此链接:http://blog.chinaunix.net/uid-29718549-id-4346700.html)
volume backed以外类型的虚拟机,在nova-api中调用API.snapshot接口创建快照(最终实现在createImage接口) volume backed的虚拟机,在nova-api中调用API.snapshot_volume_backed接口创建虚拟机快照
卷快照(LVM快照存放于本地): nova volume-snapshot-create <volume-id>
在nova-api中调用API.volume_snapshot_create接口创建虚拟机快照
1.2.openstack创建快照命令
nova image-create
usage: nova image-create [--show] [--poll] <server> <name>
Create a new image by taking a snapshot of a running server.
Positional arguments
<server>
Name or ID of server.
<name>
Name of snapshot.
Optional arguments
--show
Print image info.
--poll
Report the snapshot progress and poll until image creation is complete.
nova volume-snapshot-create
usage: nova volume-snapshot-create [--force <True|False>] [--display-name <display-name>] [--display-description <display-description>] <volume-id>
Add a new snapshot.
Positional arguments
<volume-id>
ID of the volume to snapshot
Optional arguments
--force <True|False>
Optional flag to indicate whether to snapshot a volume even if its attached to a server. (Default=False)
--display-name <display-name>
Optional snapshot name. (Default=None)
--display-description <display-description>
Optional snapshot description. (Default=None)
1.3.流程
cold snapshot 流程:
# Save the state and stop a running guest, then detach pci devices
$ virsh managedsave vm (managedsave流程见此链接:http://blog.chinaunix.net/uid-29718549-id-4772180.html)
# Create a qemu internal snapshot
$ qemu-img snapshot -c snap1 vm_snapshot
# Extract the internal snapshot, convert it to qcow2 and export it a file, then upload to glance
$ qemu-img convert -f qcow2 vm -O qcow2 vm_snapshot
# Start the guest again
$ virsh start vm
live snapshot 流程:
# Abort any failed/finished block operations:
$ virsh blockjob vm vda --abort
# Undefine a running domain. (Note: Undefining a running domain does not _kill_ the domain, it just converts it from persistent to transient.)
$ virsh undefine vm
# create a destination image with the original backing file and matching size of the instance root disk.
$ qemu-img create -f qcow2 vm_copy --backing_file=backing_file --size=root_disk_size
#Invoke 'virsh blockcopy' (This will take time, depending on the size of disk image vm1):
$ virsh blockcopy --domain vm vda vm_copy --wait --verbose
#Abort any failed/finished block operations:
$ virsh blockjob vm vda --abort
#Define the guest again (to make it persistent):
$ virsh define vm
#From the obtained new copy, convert the QCOW2 with a backing file to a qcow2 image with no backing file, then upload to glance:
$ qemu-img convert -f qcow2 -O raw vm_copy vm_convert
2.与传统快照的区别
对openstack而言,虚拟机的快照其实就是镜像。这是因为Openstack是采用创建镜像的方式创建快照(qemu-img convert -f qcow2 -O qcow2 <disk_path> <out_path>),而不是通过正统的virsh或者qemu的live snapshot创建快照函数( virsh snapshot-create-as --live ....)来实现的。虚拟机快照做完后以镜像形式存于glance。 openstack的快照与传统快照有以下 区别 :
- libvirt 主流快照实现: 采用virDomainSnapshotCreateXML()函数(CLI为virsh snapshot-create)。新建的快照与虚拟机有关联:若为内置快照,快照信息和虚拟机存在同一个qcow2镜像中;若为外置快照,新建一个qcow2文件,原虚拟机的disk将变为一个read only的模板镜像,新qcow2镜像仅记录与2.模板镜像的差异数据。这种快照包含快照链信息,可保留disk和ram信息,可回滚至快照点。
- openstack快照实现:openstack并未采用virDomainSnapshotCreateXML()来实现快照,而是单纯的对虚拟机镜像做转换和拷贝,生成一个与虚拟机无关联的镜像,最后上传至glance中。这种快照不包含快照链信息,只保留disk信息,无法回滚至快照点,只能采用该快照镜像创建一个新的虚拟机。
限制与缺点:
- 没有快照链信息,不支持revert恢复虚拟机到某一个快照点
- 只对系统盘进行快照,
- 不支持内存快照,不支持同时对虚拟机和磁盘做快照
- 需要用户进行一致性操作
- 不支持含元数据导出,
- 不支持含元数据导入
- 只支持虚拟机全量数据快照(与快照的实现方式有关,因为是通过image进行保存的)
- 过程较长(需要先通过存储快照,然后抽取并上传至glance)
- 快照以Image方式保存,而非以cinder卷方式保存,无法充分利用存储本身能力加快快照的创建和使用
- 当前限制openstack的虚拟机快照只快照root盘,不快照内存/CPU状态以及挂载磁盘。挂载磁盘需要事先卸载磁盘(数据盘),然后进行快照,然后再挂载磁盘
- 没有快照链信息
3.OpenStack快照源代码流程分析
3.1 nova/compute/api.py
1. # NOTE(melwitt): We don't check instance lock for snapshot because lock is
2. # intended to prevent accidental change/delete of instances
3. @wrap_check_policy
4. @check_instance_cell
5. @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED,
6. vm_states.PAUSED, vm_states.SUSPENDED])
7. def snapshot(self, context, instance, name, extra_properties=None):
8. """Snapshot the given instance.
9.
10. :param instance: nova.db.sqlalchemy.models.Instance
11. :param name: name of the snapshot
12. :param extra_properties: dict of extra image properties to include
13. when creating the image.
14. :returns: A dict containing image metadata
15. """
16. #_create_image函数中调用glance api创建image entry,为后将snapshot上传为镜像做准备,虽然镜像和snapshot在可以上传到glance作为镜像启动虚拟机,
17. #但是为了区分二者的不同,glance将镜像和snapshot标记为不同的类型:type=image 和 type=snapshot
18. image_meta = self._create_image(context, instance, name,
19. ',
20. extra_properties=extra_properties)
21.
22. # NOTE(comstud): Any changes to this method should also be made
23. # to the snapshot_instance() method in nova/cells/messaging.py
24. # 将任务状态(task state) 设置为:image_snapshot_pending
25. instance.task_state = task_states.IMAGE_SNAPSHOT_PENDING
26. instance.save(expected_task_state=[None])
27. #通过rpc调用nova/compute/rpcapi.py的snapshot_instance函数
28. self.compute_rpcapi.snapshot_instance(context, instance,
29. image_meta['id
3.2 nova/compute/rpcapi.py
1. #梳理下流程:
2. (1)用户发起create snapshot的请求;
3. (2)nova-api服务接收到这个请求并进行前期处理,即3.1中代码的处理流程;
4. (3)真正的snapshot操作是需要在nova-compute节点上执行的,所以nova-api需要向nova-compute发送message
5. #由于OpenStack环境中会有很多个nova-compute,所以需要通过server=_compute_host(None, instance)来获取虚拟机所在的host,并向其发送message。
6. def snapshot_instance(self, ctxt, instance, image_id):
7. = '3.0'
8. cctxt = self.client.prepare(server=_compute_host(None, instance),
9. =version)
10. .cast(ctxt, 'snapshot_instance',
11. =instance,
12. =image_id)
3.3 nova/virt/libvirt/driver.py
1. def snapshot(self, context, instance, image_id, update_task_state):
2. """Create snapshot from a running VM instance.
3.
4. This command only works with qemu 0.14+
5. ""
6. try:
7. = self._get_domain(instance)
8. except exception.InstanceNotFound:
9. raise exception.InstanceNotRunning(instance_id=instance['uuid'])
10.
11. = instance['image_ref']
12.
13. = compute_utils.get_image_metadata(
14. , self._image_api, base_image_ref, instance)
15.
16. = self._image_api.get(context, image_id)
17.
18. = libvirt_utils.find_disk(virt_dom)
19. = libvirt_utils.get_disk_type(disk_path)
20.
21. = CONF.libvirt.snapshot_image_format or source_format
22.
23. (bfilippov): save lvm and rbd as raw
24. if image_format == 'lvm' or image_format == 'rbd':
25. = 'raw'
26.
27. = self._create_snapshot_metadata(base,
28. ,
29. ,
30. ['name'])
31.
32. = uuid.uuid4().hex
33.
34. = LIBVIRT_POWER_STATE[virt_dom.info()[0]]
35.
36. (rmk): Live snapshots require QEMU 1.3 and Libvirt 1.0.0.
37. # These restrictions can be relaxed as other configurations
38. .
39. (dgenin): Instances with LVM encrypted ephemeral storage require
40. . Currently, checking for encryption is
41. .
42. is necessary in case this situation changes in the
43. .
44. #这里需要注意,解释了为啥现在是cold snapshot而不是live snapshot:
45. # 有人提过live snapshot的bug,社区认为live snapshot目前不稳定,所以默认条件下采用cold snapshot,并且是通过硬编码来实现的
46. # 看下面这个判断条件,成立的时候将live_snapshot = true,其中MIN_LIBVIRT_LIVESNAPSHOT_VERSION=1.3.0, 其实现在libvirt的最新版本
47. # 才到1.2.11, 所以这个live_snapshot的条件不满足,就变成了cold_snapshot
48. if (self._host.has_min_version(MIN_LIBVIRT_LIVESNAPSHOT_VERSION,
49. ,
50. )
51. and source_format not in ('lvm', 'rbd')
52. and not CONF.ephemeral_storage_encryption.enabled):
53. = True
54. is an idempotent operation, so make sure any block
55. . This operation also
56. , as opposed to the system as a
57. , has a new enough version of the hypervisor (bug 1193146).
58. try:
59. virt_dom.blockJobAbort(disk_path, 0)#live第一步
60. except libvirt.libvirtError as ex:
61. = ex.get_error_code()
62. if error_code == libvirt.VIR_ERR_CONFIG_UNSUPPORTED:
63. = False
64. else:
65. pass
66. else:
67. = False
68.
69. (rmk): We cannot perform live snapshots when a managedSave
70. is present, so we will use the cold/legacy method
71. for instances which are shutdown.
72. if state == power_state.SHUTDOWN:
73. = False
74.
75. (dkang): managedSave does not work for
76. #注意这里,如果live_snashot目前是false,在做snapshot之前先要执行:
77. #(1)_detach_pci_devices, 卸载虚拟机挂载的pci设备,比如数据盘
78. #(2)self._detach_sriov_ports, 卸载虚拟机挂载的SRIOV设备,比如支持SRIOV的网卡设备
79. if CONF.libvirt.virt_type != 'lxc' and not live_snapshot:
80. if state == power_state.RUNNING or state == power_state.PAUSED:
81. ._detach_pci_devices(virt_dom,
82. .get_instance_pci_devs(instance))
83. ._detach_sriov_ports(instance, virt_dom)
84. virt_dom.managedSave(0)#cold第一步
85. #判断虚拟机的后端存储是什么,不同的后端存储做snapshot是不同的,本地文件系统的话,默认qcow2
86. = self.image_backend.snapshot(instance,
87. ,
88. =source_format)
89.
90. if live_snapshot:
91. .info(_LI("Beginning live snapshot process"),
92. =instance)
93. else:
94. .info(_LI("Beginning cold snapshot process"),
95. =instance)
96. #更新任务的状态为:image_pending_upload, 大家都知道做完snapshot要上传
97. (task_state=task_states.IMAGE_PENDING_UPLOAD)
98. #目前做快照的过程是:
99. #(1)先在../data/nova/instance/snapshots目录下生成临时目录,比如nova/instances/snapshots/tmptHr585
100. #(2)然后将快照生成到这个目录,具体参见snapshot_backend.snapshot_extract(out_path, image_format)这个函数
101. #(3)生成完成后,通过glance api上传,具体参见 self._image_api.update
102. = CONF.libvirt.snapshots_directory
103. .ensure_tree(snapshot_directory)
104. .tempdir(dir=snapshot_directory) as tmpdir:
105. try:
106. = os.path.join(tmpdir, snapshot_name)
107. if live_snapshot:
108. (xqueralt): libvirt needs o+x in the temp directory
109. .chmod(tmpdir, 0o701)
110. self._live_snapshot(context, instance, virt_dom, disk_path,
111. , image_format, base)#热迁移
112. else:
113. #这个函数实际执行了一条命令: qemu-img convert -f qcow2 -O qcow2 disk_path out_path,算是生成了快照
114. snapshot_backend.snapshot_extract(out_path, image_format)
115. finally:
116. = None
117. (dkang): because previous managedSave is not called
118. for LXC, _create_domain must not be called.
119. if CONF.libvirt.virt_type != 'lxc' and not live_snapshot:
120. if state == power_state.RUNNING:
121. = self._create_domain(domain=virt_dom) ##恢复做快照之前虚拟机的状态
122. elif state == power_state.PAUSED:
123. = self._create_domain(domain=virt_dom,
124. =libvirt.VIR_DOMAIN_START_PAUSED)
125. if new_dom is not None:
126. ._attach_pci_devices(new_dom,
127. .get_instance_pci_devs(instance))
128. ._attach_sriov_ports(context, instance, new_dom)
129. .info(_LI("Snapshot extracted, beginning image upload"),
130. =instance)
131.
132. # Upload that image to the image service
133.
134. (task_state=task_states.IMAGE_UPLOADING,
135. =task_states.IMAGE_PENDING_UPLOAD)
136. .file_open(out_path) as image_file: ###将生成的快照上传到glance
137. ._image_api.update(context,
138. ,
139. ,
140. )
141. .info(_LI("Snapshot image upload complete"),
142. =instance)