赞
踩
针对问题1,因为cinder的下载是通过python的request库自带的分片下载功能进行的,且无法进行限速。但我们的镜像存放是ceph,ceph支持对存储池级别限速,所以我们最终通过对存放镜像的存储池进行限速,限制镜像读取的速度,变相限制镜像下载速度来workaroud了。
针对问题2,cinder的一个配置项volume_copy_bps_limit,这个配置项是针对convert流程的一个限速,默认值是0,也就是不限速,我们通过这个配置项进行限速解决。
针对问题3,cinder本身有一个功能或者说机制,镜像盘缓存(image_volume_cache),这个功能会在每一个镜像第一次创建系统盘后,会多克隆一份留作缓存盘使用(这个缓存盘的大小就是上传镜像时设置的最小容量),之后再创建这个镜像的系统盘时,就可以直接在存储侧进行克隆,cinder则无需再次下载、转换。这么做能够大幅度提升速度及效率,不过有一点影响,就是每个镜像可能有需要在存储侧有一个缓存盘,有一点点磁盘空间的占用,不过现在这个时代,磁盘容量已经不是什么关键问题了。
本次分析从create_volume流程中cinder.volume.flows.manager.create_volume.CreateVolumeFromSpecTask开始,这也是创建云硬盘的最主要的部分之一。
镜像盘的创建流程(镜像下载、转换、缓存盘创建):
因为我们是从镜像创建系统盘,所以最先进入的就是_create_from_image
CreateVolumeFromSpecTask.execute # execute里主要有5中方式 # 创建没有数据空盘_create_raw_volume # 从云硬盘的快照创建新盘_create_from_snapshot # 从已有的云硬盘克隆新盘_create_from_source_volume # 从一个已有副本创建新盘_create_from_source_replica # 从镜像创建新盘_create_from_image ->CreateVolumeFromSpecTask._create_from_image #_create_from_image中主要有4中创建方式 # 直接调用driver中的clone_image接口,不常用。这个接口仅限于需要创建的盘和镜像是在同一个存储集群,且存储支持从镜像 # 直接创建出新盘。例如:glance和cinder都使用同一ceph存储,glance对 # 外提供镜像url实际是存放镜像的rbd设备的快照,而ceph是可以从一个rbd设 # 备的快照克隆到一个新rbd的 # 从_clone_image_volume调用driver._clone_image_volume接口,不常用。和clone_image接口有一点区别,这个接口克隆 # 的源必须是raw格式设备,所以当glance对外提 # 供的镜像也是一个raw设备时,可以调用此接口 # 若已经存在镜像缓存盘了_create_from_image_cache,开启了镜像缓存功能,镜像曾经通过最后一种方式创建过缓存盘时可用 ->CreateVolumeFromSpecTask._create_from_image_cache ->CreateVolumeFromSpecTask._create_from_source_volume ->driver.create_cloned_volume # 若上面三种都不满足时,需要从glance下载镜像到cinder ->image_utils.TemporaryImages.fetch ->image_utils.fetch_verify_image ->image_utils.fetch ->glance.GlanceImageService.download # 参见代码参考1.1 ->glance.GlanceClientWrapper.call # 返回一个迭代器,包含镜像数据,参见代码参考1.2 ->glanceclient.v2.images.Controller.data ->http_client.get # 这里的http_client有SessionClient, HTTPClient两种,便于理解 # 选择HTTPClient进行说明 ->glanceclient.common.http.HttpClient._request ->SessionClient.requset #得到resp ->HttpClient._handle_response(resp) #解析resp ->http._close_after_stream ->requsets.models.Response.iter_content # 将二进制流解析为迭代 # 器,参见参考1.3 ->data.write # 将迭代器中的数据写入临时文件,保存在/opt/cinder/conversion # 目录,参见代码参考1.1
代码参考1.1
# cinder/image/glance.py
class GlanceImageService(object):
def download(self, context, image_id, data=None):
...
try:
image_chunks = self._client.call(context, 'data', image_id) # 获取包含镜像数据的迭代器
except Exception:
_reraise_translated_image_exception(image_id)
if not data:
return image_chunks
else:
for chunk in image_chunks:
data.write(chunk) # 将迭代器中的数据写入本地临时文件
代码参考1.2:
# cinder/image/glance.py
class GlanceClientWrapper(object):
def call(self, context, method, *args, **kwargs):
...
controller = getattr(client,
kwargs.pop('controller', 'images')) # client是glanceclient库中的glanceclient.
# v2.client.py中的Client,没有controller属性,
# 但有self.images = images.Controller
return getattr(controller, method)(*args, **kwargs) # method是'data',
# 即返回glanceclient.v2.images.Controller.data()
...
代码参考1.3:
# requests/models.py class Response(object): def iter_content(self, chunk_size=1, decode_unicode=False): ... # Special case for urllib3. if hasattr(self.raw, 'stream'): try: for chunk in self.raw.stream(chunk_size, decode_content=True): yield chunk except ProtocolError as e: raise ChunkedEncodingError(e) except DecodeError as e: raise ContentDecodingError(e) except ReadTimeoutError as e: raise ConnectionError(e) else: # Standard file-like object. while True: chunk = self.raw.read(chunk_size) if not chunk: break yield chunk ...
CreateVolumeFromSpecTask._create_from_image ->image_utils.TemporaryImages.fetch # 下载好临时镜像保存在/opt/cinder/conversion目录 ->CreateVolumeFromSpecTask._create_from_image_download ->driver.create_volume # 若没有开启镜像缓存功能,空云硬盘容量大小为请求创建的容量, # 否则容量大小为镜像需要的最小容量空云硬盘 ->CreateVolumeFromSpecTask._copy_image_to_volume ->driver.copy_image_to_volume # 若存储厂商的驱动没有实现该接口,则会使用cinder/volume/driver.py基类中的 # copy_image_to_volume。 但部分存储有自己单独实现,例如ceph,是直接将临时 # 文件import到目标rbd设备。这里仅分析通用流程 ->driver.BaseVD._copy_image_data_to_volume ->image_utils.fetch_to_raw ->image_utils.fetch_to_volume_format # fetch_to_volume_format有两个实现, 两者均通过volume_copy_bps_limit进行限速,参见代码参考2.1 # 如果临时镜像不是qemu镜像,则使用volume_utils.copy_volume。这种最终使用dd, # 将临时文件写入挂载的空云硬盘 # 如果临时镜像是qemu镜像,则使用image_utils.convert_image。这种最终使用qemu-img convert, # 将临时文件写入挂载的空云硬盘 ->image_utils.convert_image ->image_utils._convert_image # 使用qemu-img convert命令
代码参考2.1:
# cinder/volume/driver.py class BaseVD(object): def set_throttle(self): # 在初始化时VolumeManager.init_host()中就会调用 bps_limit = ((self.configuration and self.configuration.safe_get('volume_copy_bps_limit')) or CONF.volume_copy_bps_limit) cgroup_name = ((self.configuration and self.configuration.safe_get( 'volume_copy_blkio_cgroup_name')) or CONF.volume_copy_blkio_cgroup_name) self._throttle = None if bps_limit: try: self._throttle = throttling.BlkioCgroup(int(bps_limit), cgroup_name) except processutils.ProcessExecutionError as err: LOG.warning(_LW('Failed to activate volume copy throttling: ' '%(err)s'), {'err': err}) throttling.Throttle.set_default(self._throttle) # cinder/image/image_utils.py def convert_image(source, dest, out_format, run_as_root=True, throttle=None): if not throttle: volume_utils.check_cgroup() throttle = throttling.Throttle.get_default() with throttle.subcommand(source, dest) as throttle_cmd: _convert_image(tuple(throttle_cmd['prefix']), source, dest, out_format, run_as_root=run_as_root) # cinder/volume/utils.py def copy_volume(src, dest, size_in_m, blocksize, sync=False, execute=utils.execute, ionice=None, throttle=None, sparse=False): if (isinstance(src, six.string_types) and isinstance(dest, six.string_types)): if not throttle: check_cgroup() throttle = throttling.Throttle.get_default() with throttle.subcommand(src, dest) as throttle_cmd: _copy_volume_with_path(throttle_cmd['prefix'], src, dest, size_in_m, blocksize, sync=sync, execute=execute, ionice=ionice, sparse=sparse) else: _copy_volume_with_file(src, dest, size_in_m)
CreateVolumeFromSpecTask._create_from_image
->image_utils.TemporaryImages.fetch # 下载好临时镜像保存在/opt/cinder/conversion目录
->CreateVolumeFromSpecTask._create_from_image_download # 将临时镜像写入目标云硬盘。若没有开启镜像缓存功能,
# 空云硬盘容量大小为请求创建的容量,
# 否则容量大小为镜像需要的最小容量空云硬盘
->cinder.volume.manager.VolumeManager._create_image_cache_volume_entry # 若开启了镜像缓存功能,则需要从
# _create_from_image_download创建的盘克隆出
# 一个新盘当作镜像缓存盘
->VolumeManager._clone_image_volume
->VolumeManager.create_volume # 新盘(缓存盘)的source_volid即为_create_from_image_download接口创建的云硬盘
->create_volume.get_flow
->CreateVolumeFromSpecTask.execute
->CreateVolumeFromSpecTask._create_from_source_volume
->driver.create_cloned_volume
->driver.extend_volume # 若_create_from_image_download创建的云硬盘容量不是请求的系统盘容量,则进行扩容
VolumeActionsController._volume_upload_image -> cinder.volume.api.API.copy_volume_to_image -> cinder.volume.api.API._merge_volume_image_meta -> cinder.volume.manager.VolumeManager.copy_volume_to_image # copy_volume_to_image有两种实现: # 1. _clone_image_volume_and_add_location 当使用cinder来存放镜像时,直接克隆一个新盘留作镜像 # 2. copy_volume_to_image 将盘挂载到cinder-volume节点上,再上传到glance,如果是in-use的盘,需要支持multiattach -> VolumeManager._clone_image_volume_and_add_location -> VolumeManager._clone_image_volume - VolumeManager.create_volume #创建一个新盘source_id设置为需要上传的volume.id -> driver.copy_volume_to_image #cinder/volume/driver.py里有实现,不部分厂商的驱动都没有单独再实现此接口,分为三步 -> cinder.volume.driver.BaseVD._attach_volume # 1. 挂盘 -> driver.create_export -> driver.initialize_connection #存储添加映射 -> driver._connect_device -> connector.connect_volume #调用os-brick扫盘 -> cinder.volume.volume_utils.upload_volume # 2. 上传 -> cinder.image.image_utils.upload_volume -> cinder.image.glance.GlanceImageService.update -> glance.GlanceClientWrapper.call -> glanceclient.v2.images.Controller.upload # 上传镜像data -> glance.GlanceClientWrapper.call('update') -> glanceclient.v2.images.Controller.update # 更新镜像metadata -> cinder.volume.driver.BaseVD._detach_volume # 3. 卸盘 -> connector.disconnect_volume #调用os-brick清理节点磁盘 -> driver.terminate_connection #存储解除映射 -> driver.remove_export
-> cinder.volume.manager.VolumeManager.retype -> volume_types.volume_types_diff # 如果两个volume_type(包括extra_specs,qos_specs,encryption)完全一致,则结束retype -> self.driver.retype # 如果目标type和源type是同一个后端,则调用driver.retype -> cinder.volume.manager.VolumeManager.migrate_volume # 如果driver.retype失败或者没有完成,则使用migrate_volume -> self.driver.migrate_volume # 先尝试调用驱动的migrate_volume -> cinder.volume.manager.VolumeManager._migrate_volume_generic # 如果驱动没有完成迁移,则使用cinder自带 # 方法_migrate_volume_generic -> rpc.create_volume # 两种实现: # 1. 如果源盘没有挂载信息,则cinder直接挂载两个盘并通过dd完成retype工作 -> VolumeManager._copy_volume_data -> cinder.volume.manager.VolumeManager._attach_volume # 挂载目标盘 -> cinder.volume.manager.VolumeManager._attach_volume # 挂载源盘 -> cinder.volume.volume_utils.copy_volume -> volume_utils._copy_volume_with_path # 通过dd进行拷贝 -> _detach_volume # 卸载目标盘 -> _detach_volume # 卸载源盘 -> VolumeManager.migrate_volume_completion -> rpcapi.update_migrated_volume # 调用driver.update_migrated_volume更新信息, # 如果driver没有单独实现,就将新盘的provider_location更新到源盘 -> cinder.objects.volume.finish_volume_migration # 将目标盘的信息除id, provider_location, # glance_metadata, volume_type外替换到源盘 -> rpcapi.delete_volume # 删除新盘 # 2. 如果源盘有挂载信息(in-use),则由nova主导完成retype工作 -> cinder.compute.nova.API.update_server_volume -> novaclient.v2.volumes.VolumeManager.update_server_volume
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。