Openstack liberty及mitaka中云主机快照实现对比分析

前几天写“ Openstack liberty 创建实例快照源码分析1”时,在文章末尾留了一个悬念:在mitaka版本中优化了以ceph rbd做后端存储的云主机快照实现方式,本文将结合源码对这一改进具体分析。

首先来简单回顾下,liberty中’从镜像启动的云主机的快照原理’: 在liberty中,不管是在线快照还是离线快照,都需要先在本地生成临时快照文件,然后再上传到glance,最后以镜像模板的形式存在,用户可以使用该镜像模板启动新的云主机。下面一起来看看mitaka中云主机快照实现的过程:

与liberty一样,在mitaka中云主机既能从镜像启动也能从启动盘(启动盘快照)启动,既支持在线快照也支持离线快照,所以根据组合,也有如下4中快照方式:

  • 镜像启动云主机的离线快照
  • 镜像启动云主机的在线快照
  • 磁盘启动云主机的离线快照
  • 磁盘启动云主机的在线快照

下面请看具体分析:

镜像启动云主机的离线(在线)快照

如果您通读源码会发现,在mitaka中执行云主机快照的代码逻辑与liberty中高度相似 - 相同的命令、相同的参数、相同的入口函数、相同的异常处理,仅有的差异在nova-compute服务调用的底层虚拟化程序(我这里是libvirt)的snapshot方法中,下面一起来看看该方法的实现:

#nova/virt/libvirt/driver.py/LibvirtDriver.snapshot
def snapshot(self, context, instance, image_id, update_task_state):
    """输入参数:
    context 该次请求的上下文
    instance InstanceV2实例对象
    image_id 快照id
    update_task_state ComputeManager._snapshot_instance中传入的
    用来更新实例状态的辅助函数
    """

    """省略异常处理代码
    如果实例已被删除(不存在)则抛异常
    """

    #通过libvirt接口获取表示云主机实例的virDomain对象
    guest = self._host.get_guest(instance)
    virt_dom = guest._domain

    #从glance数据库提取快照信息,该信息在nova-api中记录数据库
    snapshot = self._image_api.get(context, image_id)

    #从实例的xml配置文件中解析出系统磁盘信息
    #disk_path = 'rbd:vms/814a8ad8-9217-4c45-91c7-c2be2016e5da_disk'
    #source_format = raw
    disk_path, source_format = libvirt_utils.find_disk(virt_dom)
    #从磁盘路径中解析出后端存储类型,这里是rbd
    source_type = libvirt_utils.get_disk_type_from_path(disk_path)

    #修正后端存储类型及快照磁盘类型
    #如果未能从磁盘路径中解析出后端存储类型,就用磁盘格式类型作为后端类型
    #使用'snapshot_image_format '或者后端存储类型作为快照磁盘类型,
    #如果快照类型为lvm或者rbd,就修改为raw格式
    if source_type is None:
        source_type = source_format 
    image_format = CONF.libvirt.snapshot_image_format or source_type
    if image_format == 'lvm' or image_format == 'rbd':
            image_format = 'raw'

    """根据系统盘镜像属性,快照属性及快照磁盘格式生成快照属性字典,用来
    上传快照文件时更新glance数据库条目,属性字典信息如下:
    {
    'status': 'active', 
    'name': u'snapshot1', 
    'container_format': u'bare', 
    'disk_format': 'raw', 
    'is_public': False, 
    'properties': {
        'kernel_id': u'', 
        'image_location': 'snapshot', 
        'image_state': 'available', 
        'ramdisk_id': u'',
         'owner_id': u'25520b29dce346d38bc4b055c5ffbfcb'
         }
     }
    """ 
    metadata = self._create_snapshot_metadata(instance.image_meta,
                                                  instance,
                                                  image_format,
                                                  snapshot['name'])

    #本地的临时快照文件名,后端存储是lvm时,需要用到
    #如果是rbd克隆快照,则是创建在系统磁盘上的临时快照名
    snapshot_name = uuid.uuid4().hex    
    #获取实例电源状态,用来判断是执行在线快照还是离线快照
    state = guest.get_power_state(self._host)  

    """判断是执行在线快照还是离线快照,在线快照需要同时满足下面的条件:
    1. QEMU >= 1.3 && libvirt >= 1.0.0
    2. nova后端存储非lvm (在mitaka中ceph rbd支持在线快照了)
    3. 未开启外部存储加密功能 ephemeral_storage_encryption = False
    4. 未关闭在线快照disable_libvirt_livesnapshot = False
    """      
    if (self._host.has_min_version(MIN_LIBVIRT_LIVESNAPSHOT_VERSION,
                                       MIN_QEMU_LIVESNAPSHOT_VERSION,
                                       host.HV_DRIVER_QEMU)
         and source_type not in ('lvm', 'rbd')
         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 

     #关机状态,执行离线快照
     if state == power_state.SHUTDOWN:
         live_snapshot = False   

    #使实例进入saved的状态,实例被挂起为执行快照做准备
    #如果采用的是非lxc虚拟化程序,执行离线快照并且实例处于运行或者暂停
    #状态,在执行快照前需要卸载pci设备及sriov端口
    self._prepare_domain_for_snapshot(context, live_snapshot, state,
                                          instance)

    #根据后端存储创建驱动实例,我这里是Rbd
    snapshot_backend = self.image_backend.snapshot(instance,
                                                    disk_path,
                                        image_type=source_type) 

    #显示log
    if live_snapshot:
        LOG.info(_LI("Beginning live snapshot process"),
                     instance=instance)
    else:
        LOG.info(_LI("Beginning cold snapshot process"),
                     instance=instance)  

    #调用传进来的辅助函数,更新实例任务状态:等待镜像上传
    update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD)

    """省略异常处理代码
    如果不支持clone模式的快照或者未实现该方法,就回滚到liberty中的实现模
    式,如果是其他异常,则异常退出
    """
    #调用传进来的辅助函数,更新实例任务状态:上传镜像中
    update_task_state(task_state=
                    task_states.IMAGE_UPLOADING,
               expected_state=task_states.IMAGE_PENDING_UPLOAD)

    #调用Rbd.direct_snapshot创建快照,下文具体分析;如果配置后端存储
    #不支持clone快照,则会在异常处理代码中采用传统的方式实现快照,请参考
    #'Openstack liberty 创建实例快照源码分析1'中的分析      
    metadata['location'] = snapshot_backend.direct_snapshot(
                context, snapshot_name, image_format, image_id,
                instance.image_ref)

    """如果采用的是非lxc虚拟化程序,执行离线快照并且实例处于运行或者暂停
    状态,上文挂起了云主机并卸载了pci设备及sriov端口,快照完成后需要重新
    唤醒云主机并挂载设备
    去"""
    self._snapshot_domain(context, live_snapshot, virt_dom, state,
                                  instance)
    #在上文中已经将快照上传到glance,所以这里只是更新镜像的数据库
    #信息,如:location信息   
    """这里扩展一下:'Openstack libery启动云主机源码分析'一文中说
    如果采用的是ceph rbd后端并且镜像是raw格式,就采用clone的方式启动
    云主机 - 严格来说这不准确,因为clone方法还依赖于location路径来确定
    镜像的路径,所以准确的说法是:采用的是ceph rbd做后端存储并且镜像是
    raw格式同时也设置了locations属性,而location属性会在上传镜像或者
    快照时生成
    """                          
    self._image_api.update(context, image_id, metadata,
                                   purge_props=False)
    #打印日志
    LOG.info(_LI("Snapshot image upload complete"), instance=instance)

--------------------------------------------------------------
接上文:nova/virt/libvirt/imagebackend.py/Rbd.direct_snapshot
def direct_snapshot(self, context, snapshot_name, image_format,
                        image_id, base_image_id):
    """mitaka中新增的接口,用来实现克隆快照
    context 请求上下文
    snapshot_name 快照uuid名
    image_format 快照的格式,这里是raw
    image_id 快照id
    base_image_id 系统磁盘uuid
    """      

    #通过RBDDriver获得ceph集群id
    fsid = self.driver.get_fsid()               
    #获取系统盘镜像所在的存储池,其实就是glance后端所使用的ceph存储池,
    #因为clone后,快照是要存储在glance上的
    parent_pool = self._get_parent_pool(context, base_image_id, fsid)

    """ Snapshot the disk and clone it into Glance's storage 
    pool. librbd requires that snapshots be set to "protected" 
    in order to clone them
    """
    #在系统盘上创建一个快照,因为rbd只能克隆快照;clone完成后会删除之
    self.driver.create_snap(self.rbd_name, snapshot_name, 
                                                protect=True)
    #构建系统盘+快照路径,为后文的clone做准备                                            
    location = {'url': 'rbd://%(fsid)s/%(pool)s/%(image)s/%(snap)s' %
                                dict(fsid=fsid,
                                pool=self.pool,
                                image=self.rbd_name,
                                snap=snapshot_name)}
     try:
         #调用clone接口将系统磁盘克隆到dest_pool的image_id文件
         #这里只是标记父子关系,所以clone操作很快
         self.driver.clone(location, image_id, dest_pool=parent_pool)
         # Flatten the image, which detaches it from the source 
         #snapshot
         #对clone出来的文件进行扁平化处理,这里执行真正的io拷贝,视
         #系统磁盘的大小,需要一定的时间
         self.driver.flatten(image_id, pool=parent_pool)
    finally:
         # all done with the source snapshot, clean it up
         #删除在系统盘上所做的快照
         self.cleanup_direct_snapshot(location)

    """ Glance makes a protected snapshot called 'snap' on 
    uploaded images and hands it out, so we'll do that too.  
    The name of the snapshot doesn't really matter, this just 
    uses what the glance-store rbd backend sets (which is not 
    """
    """在生成的快照image上创建一个名为'snap'的快照(读过‘Openstack 
    #libery启动云主机源码分析’的读者,应该记得如果使用的是ceph rbd做
    后端存储,从raw格式的镜像启动云主机也是通过clone的方式实现,所以这
    里要创建一个快照)
    """
    self.driver.create_snap(image_id, 'snap', pool=parent_pool,
                                protect=True)
    #返回最终的快照路径                            
    return ('rbd://%(fsid)s/%(pool)s/%(image)s/snap' %
                dict(fsid=fsid, pool=parent_pool, image=image_id))

快照过程分析完了,下面来总结下:

  • mitaka中代码封装得更好了,但是对于非ceph rbd后端存储的快照,在处理逻辑上多了一次异常跳转
  • mitaka中lvm后端存储的快照实现方式与liberty中一致
  • mitaka中ceph rbd后端存储的快照采用clone的方式实现,少了一次本地拷贝过程,理论上是liberty中性能的2x

最后mitaka中’磁盘启动云主机的快照’实现方式与liberty中一致,相关的内容请看下一篇博文’Openstack liberty 创建实例快照源码分析2’.

你可能感兴趣的:(Openstack)