在Ceph中创建虚拟机流程改进之分析(转)

来源:互联网 发布:银行业数据治理的问题 编辑:程序博客网 时间:2024/05/22 12:53

作为个人学习笔记分享,有任何问题欢迎交流!

最近在Gerrit中看到一个changehttps://review.openstack.org/#/c/94295/ , 它主要是对当前在Ceph中创建虚拟机的流程的改进。如果glance的backend是ceph, 则nova创建虚拟机到RBD的流程是这样的:

通过glance从ceph中下载image --> 本地 --> 复制image到rbd

 

这个change的目的就是:不需要下载到本地,直接在rbd中复制image,以提高虚拟机创建的速度。

 

以前只知道nova从glance下载image,作为虚拟机的base,却没有仔细的了解过这个过程,正好借这个机会,看一看nova创建虚拟机到rbd过程中关于image的部分。

1 nova创建VMimage的流程

 经过nova-scheduler选择节点后,创建VM的请求到达了nova-compute,即nova/compute/manager.pyComputeManager._run_instance()

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. def _run_instance(self, context, request_spec,  
  2.                       filter_properties, requested_networks, injected_files,  
  3.                       admin_password, is_first_time, node, instance,  
  4.                       legacy_bdm_in_spec):  
  5.    

关于镜像的元数据就保存在request_spec,

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. image_meta = request_spec['image']  

取得元数据后就开始build_instance()了,但是下面的过程与image没太大关系,所以从简带过。

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. def _build_instance(self, context, request_spec, filter_properties,  
  2.             requested_networks, injected_files, admin_password,   is_first_time,  
  3. node, instance, image_meta, legacy_bdm_in_spec)  
  4. ---->  
  5. def _spawn(self, context, instance, image_meta, network_info,  
  6.                block_device_info, injected_files, admin_password,  
  7.                set_access_ip=False)  
  8. ---->  
  9.    
  10. self.driver.spawn(context, instance, image_meta,  
  11.                               injected_files, admin_password,  
  12.                               network_info,  
  13.                               block_device_info)  

这里的driver就是你用的Hypervioser, 我用的是KVM,所以这个driver.spawn=nova/virt/libvirt/driver.py:LibvirtDriver.spawn():

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. def spawn(self, context, instance, image_meta, injected_files,  
  2.               admin_password,network_info=None,  block_device_info=None):  
  3.        disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,  
  4.                                             instance,  
  5.                                             block_device_info,  
  6.                                             image_meta)  
  7.         self._create_image(context, instance,  
  8.                            disk_info['mapping'],  
  9.                            network_info=network_info,  
  10.                            block_device_info=block_device_info,  
  11.                            files=injected_files,  
  12.                            admin_pass=admin_password)  

那么这个disk_info['mapping']是什么呢?这里有一个方法,我们可以从test_libvirt_blockinfo.py里找到答案,所以结合测试用例来看代码真的很有用。在Nova/tests/virt/libvirt/test_libvirt_blockinfo.py:

LibvirtBlockInfoTest.test_get_disk_mapping_simple_swap()里可以看到:

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. expect = {  
  2.       'disk': {'bus''virtio''dev''vda',  
  3.                'type''disk''boot_index''1'},  
  4.       'disk.local': {'bus''virtio''dev''vdb''type''disk'},  
  5.       'root': {'bus''virtio''dev''vda',  
  6.                'type''disk''boot_index''1'}  
  7.       }  

Expect就是期望disk_info['mapping']的样子。

 

下面就开始创建VM的image了:

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1.     def _create_image(self, context, instance,  
  2.                       disk_mapping, suffix='',  
  3.                       disk_images=None, network_info=None,  
  4.                       block_device_info=None, files=None,  
  5.                       admin_pass=None, inject_files=True):  
  6. #下面这个函数就是返回VM image格式的相关类,位于 #libvirt/imagebackend.py中,这里image是rbd, 返回的就是Rbd    #类。  
  7. def image(fname, image_type=CONF.libvirt.images_type):  
  8.             return self.image_backend.image(instance,  
  9.                                      fname + suffix, image_type)  
  10. ......  
  11.    
  12.   if not booted_from_volume:  
  13.             root_fname = imagecache.get_cache_fname(disk_images, 'image_id')#以image id作为文件名  
  14.             size = instance['root_gb'] * units.Gi  
  15.    
  16.             if size == 0 or suffix == '.rescue':  
  17.                 size = None  
  18.    
  19. #这里有点复杂,用到了回调函数fetch_image,这里的cache是Rbd的父类#Image类的cache(),主要功能是从模板,也就是glance的image, 为VM创建一个image.  
  20.            image('disk').cache(fetch_func=libvirt_utils.fetch_image,  
  21.                                 context=context,  
  22.                                 filename=root_fname,  
  23.                                 size=size,  
  24.                                 image_id=disk_images['image_id'],  
  25.                                 user_id=instance['user_id'],  
  26.                                project_id=instance['project_id'])  
  27. #可以在cache()中看到:  
  28.         if not self.check_image_exists() or not os.path.exists(base):  
  29.             self.create_image(fetch_func_sync, base, size,  
  30.                               *args, **kwargs)  

那么现在到class Rbd下可以找到create_image():

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. def create_image(self, prepare_template, base, size, *args, **kwargs):  
  2.        if self.rbd is None:  
  3.            raise RuntimeError(_('rbd python libraries not found'))  
  4.   
  5.        if not os.path.exists(base):  
  6.            prepare_template(target=base, max_size=size, *args, **kwargs)##这里的prepare_temple()就是 libvirt_utils.fetch_image啦。  

libvirt_utils.fetch_image=nova/virt/libvirt/utils.fetch_image():

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. def fetch_image(context, target, image_id, user_id, project_id, max_size=0):  
  2.     """Grab image."""  
  3.     images.fetch_to_raw(context, image_id, target, user_id, project_id,  
  4.                         max_size=max_size)  
  5. --->  
  6.    
  7. def fetch_to_raw(context, image_href, path, user_id, project_id, max_size=0):  
  8.     path_tmp = "%s.part" % path  
  9.     fetch(context, image_href, path_tmp, user_id, project_id,  
  10.           max_size=max_size)  
  11.    
  12. ---->  
  13. def fetch(context, image_href, path, _user_id, _project_id, max_size=0):  
  14. (image_service, image_id) = glance.get_remote_image_service(  
  15. context, image_href)#从glance获取image  
  16. with fileutils.remove_path_on_error(path):  
  17.         #这里就是把image_id的数据download到path了。Download()位于   #nova/image/glance.py。  
  18.         image_service.download(context, image_id, dst_path=path)  

回到class Rbd的create_image()中,

libvirt_utils.import_rbd_image(*args)把path的image数据写入rbd,至此,整个流程就到这里结束了。

2 Change中的改进

现在回到文章开始中提到的那个change, 看看它是怎么实现的。

首先它在fetch_to_raw中加入了一个判断。

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. def fetch_to_raw(context, image_href, path, user_id, project_id,  max_size=0):  
  2. #判断backend是否具有‘direct_fetch’的属性,如果有,则直接返回#direct_fetch()  
  3.     if backend and hasattr(backend, 'direct_fetch'):  
  4.         try:  
  5.             return backend.direct_fetch(context, image_href)  
  6.         except exception.ImageUnacceptable:  
  7.             LOG.debug(_('could not fetch directly, falling back to download'))  

给Rbd类添加了一个属性:

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1.     def direct_fetch(self, context, image_href):  
  2. #判断driver是否支持layering(分层  
  3. #http://ceph.com/docs/firefly/dev/rbd-layering/ ,指的是块设备  
  4. #的cow克隆,支持快速创建image)  
  5.         if not self.driver.supports_layering():  
  6.             reason = _('installed version of librbd does not support cloning')  
  7.             raise exception.ImageUnacceptable(image_id=image_href,  
  8.                                               reason=reason)  
  9.    
  10.         image_meta, locations = images.get_meta(context, image_href)  
  11.         LOG.debug(_('Image locations are: %(locs)s') % {'locs': locations})  
  12.    
  13.         if image_meta.get('disk_format'not in ['raw''iso']:  
  14.             reason = _('Image is not raw format')  
  15.             raise exception.ImageUnacceptable(image_id=image_href,  
  16.                                               reason=reason)  
  17. #克隆镜像(http://ceph.com/docs/master/rbd/librbdpy/)  
  18.         for location in locations:  
  19.             if self.driver.is_cloneable(location, image_meta):  
  20.                 return self.driver.clone(location, self.rbd_name)  
  21.    
  22.         reason = _('No image locations are accessible')  
  23.         raise exception.ImageUnacceptable(image_id=image_href, reason=reason)  

这样就不需要想1中的那样先把image下载到local, 在写到rbd中,直接在rbd中克隆,从而提高了虚拟机的创建速度。

3总结

借这个机会既熟悉了创建VM时的image流程,又熟悉了ceph的用法,同时学习了高手们是怎么实现一个功能的,看来review的益处大大的呀。:)

0 0
原创粉丝点击