从镜像启动的云主机在线快照
之前介绍了‘镜像启动云主机的离线快照’,接下来介绍‘镜像启动云主机的在线快照’,在线快照的处理与离线快照的处理大体上一样,只是nova-compute在处理的时候有差异,差别代码如下(代码路径:nova/virt/libvirt/driver.py/LibvirtDriver.snapshot):
def snapshot(self, context, instance, image_id, update_task_state):
if (self._host.has_min_version(hv_type=host.HV_DRIVER_QEMU)
and source_type not in ('lvm')
and not CONF.ephemeral_storage_encryption.enabled
and not CONF.workarounds.disable_libvirt_livesnapshot):
live_snapshot = True
# Abort is an idempotent operation, so
make sure any block
# jobs which may have failed are ended. This operation also
# confirms the running instance, as opposed to the system as a
# whole, has a new enough version of the hypervisor (bug 1193146).
try:
guest.get_block_device(disk_path).abort_job()
except libvirt.libvirtError as ex:
error_code = ex.get_error_code()
if error_code == libvirt.VIR_ERR_CONFIG_UNSUPPORTED:
live_snapshot = False
else:
pass
else:
live_snapshot = False
可以发现在做live_snapshot的时候,会调用“abort_job()”来停止系统盘上的所有任务,即就是调用“_guest._domain.blockJobAbort(self._disk, flags=flags)”。
接着还是会做生成临时目录的操作,即:
with utils.tempdir(dir=snapshot_directory) as tmpdir:
try:
out_path = os.path.join(tmpdir, snapshot_name)
LOG.info('out_path:
%s', out_path)
if live_snapshot:
# NOTE(xqueralt):
libvirt needs o+x in the tempdir
os.chmod(tmpdir, 0o701)
self._live_snapshot(context, instance, guest, disk_path, out_path, source_format,
image_format, instance.image_meta)
else:
root_disk.snapshot_extract(out_path, image_format)
finally:
self._snapshot_domain(context, live_snapshot, virt_dom, state, instance)
LOG.info("Snapshot
extracted, beginning image upload",
instance=instance)
因为是在线快照,满足live_snapshot的条件,首先会给临时的快照目录701的访问权限,接着调用方法“self._live_snapshot”来做具体的快照操作,其中传递的参数为:
l context:上下文,主要是安全及权限方面的内容
l instance:实例的对象
l guest:实例对象对应的虚机domain
l disk_path,source_format分别表示系统盘的路径及格式类型
l out_path,image_format分别表示临时快照文件路径及格式类型
l image_meta:是镜像的metadata信息
实际上是在调用后端存储驱动执行快照,例如执行使用ceph则是使用Rbd.snapshot_extract,内部实现为调用'qemu-img convert'拷贝系统磁盘到out_path文件中,命令如下:
qemu-img convert -O raw rbd:vms/814a8ad8-9217-4c45-91c7-c2be2016e5da_disk:id=cinder:conf=/etc/ceph/ceph.conf' /opt/stack/data/nova/instances/snapshots/tmptR6hog/e44639af86434069b38f835847083697 -f raw
在线快照实际上是调用“_live_snapshot”,“_live_snapshot”内容如下:
def _live_snapshot(self, context, instance, guest, disk_path, out_path,
source_format, image_format, image_meta):
"""Snapshot an instance without
downtime."""
# 创建一个BlockDevice对象
dev = guest.get_block_device(disk_path)
# 获取实例的xml配置
xml = guest.get_xml_desc(dump_inactive=True, dump_sensitive=True)
try:
# 快照前结束所有磁盘作业
dev.abort_job()
except Exception:
pass
# 通过qemu-img获取磁盘的virtual size
src_disk_size = libvirt_utils.get_disk_size(disk_path, format=source_format)
# 通过qemu-img info获取系统磁盘的backing_file
src_back_path =
libvirt_utils.get_disk_backing_file(disk_path,
format=source_format,
basename=False)
# 组装一个完整的快照文件名称
disk_delta = out_path
+ '.delta'
"""
接着通过create_cow_image创建一个cow文件,实际上是调用qemu-img create来完成的,命令如下:
qemu-img create -f qcow2 -o backing_file=$src_back_path
cluster_size=$cluster_size size=$src_disk_size disk_delta
"""
libvirt_utils.create_cow_image(src_back_path, disk_delta, src_disk_size)
quiesced = False
try:
self._set_quiesced(context, instance, image_meta, True)
quiesced = True
except exception.NovaException
as err:
if self._requires_quiesce(image_meta):
raise
LOG.info('Skipping quiescing instance: %(reason)s.',
{'reason': err}, instance=instance)
try:
# NOTE (rmk): blockRebase cannot be
executed on persistent
#
domains, so we need to temporarily undefine it.
#
If any part of this block fails, the domain is
#
re-defined regardless.
# 有支持持久配置和支持uefi的实例不支持rebase,需要先undefine掉相关配置
if guest.has_persistent_configuration():
support_uefi = self._has_uefi_support()
guest.delete_configuration(support_uefi)
# disk_delta指的是在上文中创建的qcow2格式的cow文件。
dev.rebase(disk_delta, copy=True, reuse_ext=True, shallow=True)
# 判断当前是否有磁盘任务未结束,如果有则sleep 0.5秒,知道所有任务结束
while not dev.is_job_complete():
time.sleep(0.5)
dev.abort_job()
libvirt_utils.chown(disk_delta, os.getuid())
finally:
# 重新恢复实例的xml文件
self._host.write_instance_config(xml)
if quiesced:
self._set_quiesced(context, instance, image_meta, False)
# Convert the delta (CoW) image with a backing file to a flat
# image with no
backing file.
libvirt_utils.extract_snapshot(disk_delta, 'qcow2',
out_path, image_format)
上面执行在线快照的代码中调用到了“libvirt_utils.create_cow_image(src_back_path, disk_delta, src_disk_size)”,这一句才是做快照的底层实现,如下:
def create_cow_image(backing_file, path, size=None):
"""Create COW image
Creates a COW image
with the given backing file
:param
backing_file: Existing image on which to base the COW image
:param path:
Desired location of the COW image
"""
base_cmd = ['qemu-img', 'create', '-f', 'qcow2']
cow_opts = []
if backing_file:
cow_opts += ['backing_file=%s' % backing_file]
base_details = images.qemu_img_info(backing_file)
else:
base_details = None
# Explicitly inherit the value of 'cluster_size' property of a
qcow2
# overlay image from
its backing file. This can be useful in cases
# when people create a
base image with a non-default 'cluster_size'
# value or cases when
images were created with very old QEMU
# versions which had a
different default 'cluster_size'.
if base_details and base_details.cluster_size
is not None:
cow_opts += ['cluster_size=%s' % base_details.cluster_size]
if size is not None:
cow_opts += ['size=%s' % size]
if cow_opts:
# Format as a comma separated list
csv_opts = ",".join(cow_opts)
cow_opts = ['-o', csv_opts]
cmd = base_cmd +
cow_opts + [path]
execute(*cmd)
可以看出底层实际上还是调用“qemu-img create –f qcow2 ****”来实现的。
在线快照源码就分析到这里,与离线快照最大的区别就是:在线快照不会挂起实例;相同点是:都需要先在本地生成临时快照文件,再上传到glance。