创建虚拟机是个很好的应用实例,因为几乎包括了所有主要组件的应用。我们可以看到虚拟创建流程图如下,很复杂,所以进行拆解,我们今天主要看标红这部分(下载镜像)
在理创建虚拟机时glance之前,我们先看看glance本身主要分几块,有个大概了解
glance 主要职责是:
- 提供api让用户能够查询和获取镜像的元数据和镜像本身
- 支持多种方式存储镜像,包括普通的文件系统、Swift、Ceph 等;
- 对实例执行快照创建新的镜像。
查询镜像、上传镜像、下载镜像这些都是glance的日常操作,我们会以获取镜像为例,先简单介绍一下glance组件
由图2我们可以看到:
1.外部来的请求统一都由glance-api处理,继续秉承openstack大家族的一贯风格。主要的职责又分为两部分
1.1 做一些对请求的解析工作(如分析出版本号)
1.2 另外一部分提供实际的服务(如与镜像上传下载的后端存储接口交互),实际服务glance-api的请求转发原则是:
- 如果是与镜像 metadata(元数据)相关的操作,glance-api 会把请求转发给 glance-registry;
- 如果是与镜像自身存取相关的操作,glance-api 会把请求转发给该 image 的存储后端。
2.glance-registry主要负责存储或者获取镜像的元数据,与MySQL数据库进行交互。例如镜像的大小和类型。glance-registry也可以简单的再细分为两部分,API和具体的Server。
这里的元数据是指镜像相关的一些信息(如id,size, status,location, checksum, min_disk, min_ram, owner等),有了这些数据,一是我们就可以判断这个镜像是不是我们想要的那个,二是这样才能获取到location去后端存储去取这个镜像。
3.后端存储(支持很多种,用那种用户自己选),用来真正存放镜像本体的backend。因为glance 自己并不存储镜像。
下面这些是glance 支持的许多种后端存储的一些,比如:
- A directory on a local file system:这是默认配置,在本地的文件系统里进行保存镜像。
- GridFS:使用MongoDB存储镜像。
- Ceph RBD:使用Ceph的RBD接口存储到Ceph集群中。
- Amazon S3:亚马逊的S3。
- Sheepdog:专为QEMU/KVM提供的一个分布式存储系统。
- OpenStack Block Storage (Cinder)
- OpenStack Object Storage (Swift)
- HTTP:可以使用英特网上的http服务获取镜像。这种方式只能只读。
- VMware ESX/ESXi or vCenter。
具体使用哪种 backend,可以在 /etc/glance/glance-api.conf 中配置。
glance主要干嘛的讲了,咱们又接着讲创建虚拟机时glance干了点啥。
我们展开glance这部分可以看到,她内部的逻辑如下,从上面的图1我们可以知道,创建虚拟机的时候是compute的nova组件向glance发起请求,因为创建虚拟机需要从glance获取镜像。(图三省略了组件之间通信经过RabbitMQ的过程)
ps:所以这里结合咱们的图,就可以看出来了,咱们虚拟机创建需要的涉及获取镜像元数据和获取镜像本身,而不是直接一步到位从存储直接拿,没有元数据拿不到地址信息没办法取。
大致流程就是咱们这个图3这样的,下面咱们来串一串涉及的几个主要的函数。
在此我插一句题外话,我满网去搜从glance下载镜像的文章材料的时候,出来的全是glance的介绍还有上传镜像创建镜像的,下载镜像的材料基本没有,没啥逻辑线参考,所以下面的内容就属于我参考上传还有注释摸索的,正确性不保证啊,仅供参考。
继续,从图3我们可以看到,是计算节点的nova组件向glance发出了获取镜像请求,所以我们将从这里切入去看源码。#在openStack\nova-stable-queens\nova\compute\api.py中我们可以看到创建虚拟机的函数
ps:当然这并不是指在这个函数就完成了创建虚拟机的事情,openstack里面很多地方用到了非纯责任链模式。就是一个传一个,搞得定的自己搞,搞不定的传给下一个
_create_instance这个函数只是将创建虚拟机这个大任务,他所能完成的部分(向glance发出获取虚拟机镜像的请求)完成,不能完成的部分继续交给他的下家
#创建虚拟机,由此函数compute节点向glance发出获取镜像请求
def _create_instance(self, context, instance_type,
image_href, kernel_id, ramdisk_id,
min_count, max_count,
display_name, display_description,
key_name, key_data, security_groups,
availability_zone, user_data, metadata, injected_files,
admin_password, access_ip_v4, access_ip_v6,
requested_networks, config_drive,
block_device_mapping, auto_disk_config, filter_properties,
reservation_id=None, legacy_bdm=True, shutdown_terminate=False,
check_server_group_quota=False, tags=None,
supports_multiattach=False):
#获取镜像源数据(包括image_id、)
if image_href:
image_id, boot_meta = self._get_image(context, image_href)
else:
image_id = None
boot_meta = self._get_bdm_image_metadata(
context, block_device_mapping, legacy_bdm)
self._check_auto_disk_config(image=boot_meta,
auto_disk_config=auto_disk_config)
#检查flavor配置与镜像规格是否匹配
self._checks_for_create_and_rebuild(context, image_id, boot_meta,
instance_type, metadata, injected_files,
block_device_mapping.root_bdm())
return instances, reservation_id
_get_image()函数,调用了image_api.get去调用manager.py
#获取镜像函数
def _get_image(self, context, image_href):
if not image_href:
return None, {}
#实例化一个compute API对象image_api调用manager
image = self.image_api.get(context, image_href)
return image['id'], image
这个image_api不是凭空冒出来的,是实例化了一个compute API类 image_api
@profiler.trace_cls("compute_api")
class API(base.Base):
"""API for interacting with the compute manager."""
def __init__(self, image_api=None, network_api=None, volume_api=None,
security_group_api=None, **kwargs):
self.image_api = image_api or image.API()
在openStack\nova-stable-queens\nova\compute\manager.py中,_build_and_run_instance函数会调用objects.ImageMeta.from_dict(image)获取image元数据
def _build_and_run_instance(self, context, instance, image, injected_files,
admin_password, requested_networks, security_groups,
block_device_mapping, node, limits, filter_properties,
request_spec=None):
# 获取镜像名
image_name = image.get('name')
self._check_device_tagging(requested_networks, block_device_mapping)
#获取镜像元数据
image_meta = objects.ImageMeta.from_dict(image)
从此compute节点的nova组件,正式将消息发给glance。在openStack\glance-stable-queens\glance\api\v1\router.py里面,请求被glance.api.v1.router:API的对象处理。
然后我们看一下router.py文件的API类。它继承了wsgi.Router,可以根据REST请求的URL以及请求的类型(GET、PUT等)将请求映射给不同对象的不同方法进行处理。
class API(wsgi.Router):
"""WSGI router for Glance v1 API requests."""
def __init__(self, mapper):
reject_method_resource = wsgi.Resource(wsgi.RejectMethodController())
然后wsgi开始请求映射,请求最终都是被images.py、image_data.py、schemas.py、image_access.py以及image_tags.py中的controller类的不同方法处理。 所以上面下载镜像的请求会被glance/api/v1/images.py中的Controller类的index()函数处理
images_resource = images.create_resource()
mapper.connect("/",
controller=images_resource,
action="index")
mapper.connect("/images",
controller=images_resource,
action='index',
conditions={'method': ['GET']})
mapper.connect("/images",
controller=images_resource,
action='create',
conditions={'method': ['POST']})
mapper.connect("/images",
controller=reject_method_resource,
action='reject',
allowed_methods='GET, POST')
mapper.connect("/images/detail",
controller=images_resource,
action='detail',
conditions={'method': ['GET', 'HEAD']})
根据图3我们可以看到,glance-registry要做的事情是向数据库去查询合适的镜像,Controller类的index()函数接到了这个任务,我们可以由注释看到这是在查询镜像列表,通过get_images_list函数,从数据库获取image元数据
def index(self, req):
"""
Returns the following information for all public, available images:
* id -- The opaque image identifier
* name -- The name of the image
* disk_format -- The disk image format
* container_format -- The "container" format of the image
* checksum -- MD5 checksum of the image data
* size -- Size of image data in bytes
:param req: The WSGI/Webob Request object
:returns: The response body is a mapping of the following form
::
{'images': [
{'id': <ID>,
'name': <NAME>,
'disk_format': <DISK_FORMAT>,
'container_format': <DISK_FORMAT>,
'checksum': <CHECKSUM>,
'size': <SIZE>}, {...}]
}
"""
self._enforce(req, 'get_images')
params = self._get_query_params(req)
try:
images = registry.get_images_list(req.context, **params)
except exception.Invalid as e:
raise HTTPBadRequest(explanation=e.msg, request=req)
return dict(images=images)
这里会返回一堆image的元数据,我们可以根据拿到的这些image检出需要的镜像信息, 然后去后端存储里面下载真正我们需要的镜像,下载镜像的操作又从openStack\glance-stable-queens\glance\api\v1\images.py文件里的def show开始分为4步
所以在 def show() 中我们可以看到
1. 先调用image_meta = self.get_active_image_meta_or_error(req, id)获得image_meta
image_meta = self.get_active_image_meta_or_error(req, id)
2.然后确认image_meta.get('size') 不等于0,调用 self._get_from_store 获取 该 image的数据
def show(self, req, id):
"""
Returns an iterator that can be used to retrieve an image's
data along with the image metadata.
:param req: The WSGI/Webob Request object
:param id: The opaque image identifier
:raises HTTPNotFound: if image is not available to user
"""
self._enforce(req, 'get_image')
try:
image_meta = self.get_active_image_meta_or_error(req, id)
except HTTPNotFound:
# provision for backward-compatibility breaking issue
# catch the 404 exception and raise it after enforcing
# the policy
with excutils.save_and_reraise_exception():
self._enforce(req, 'download_image')
else:
target = utils.create_mashup_dict(image_meta)
self._enforce(req, 'download_image', target=target)
self._enforce_read_protected_props(image_meta, req)
if image_meta.get('size') == 0:
image_iterator = iter([])
else:
#通过_get_from_store获取image元数据
image_iterator, size = self._get_from_store(req.context,
image_meta['location'])
image_iterator = utils.cooperative_iter(image_iterator)
image_meta['size'] = size or image_meta['size']
image_meta = redact_loc(image_meta)
return {
'image_iterator': image_iterator,
'image_meta': image_meta,
}
拿到我们需要的这个image id 我们就可以确定出唯一的一个image,但是这并不能直接从后端存储里面下载镜像,还需要用这个id去查到这个image的地址,从后端存储下载镜像还需要store,如图
2.在 _get_from_store()函数中,又调用 glance_store.location.get_location_from_uri, 从 image uri 中获取 location,存入loc
这个glance-store 是images.py开始就导入的,glance-store 向 glance-api 提供文件 backend.py 作为 store 操作的统一入口
"""
/images endpoint for Glance v1 API
"""
import copy
import glance_store as store
import glance_store.location
3.调用get_store_from_uri, 获取所用的 store
4.通过store调用 store.get 获取 image data,最后返回镜像。就此完成了镜像下载操作
这里的store实际上是一个image_factory对象,在glance中,一般通过wsgi通过分发dispatch的请求,会生成一个image_factory对象和一个image_repo对象,image_factory对应后端store进行镜像的存储等操作
def _get_from_store(context, where, dest=None):
try
#调用glance_store.location.get_location_from_uri,
#从 image uri 中获取 location
loc = glance_store.location.get_location_from_uri(where)
#调用get_store_from_uri获取所需的image对象
src_store = store.get_store_from_uri(where)
if dest is not None:
src_store.READ_CHUNKSIZE = dest.WRITE_CHUNKSIZE
#调用src_store.get获取镜像image对象的image_data和image_size数据
image_data, image_size = src_store.get(loc, context=context)
except store.RemoteServiceUnavailable as e:
raise HTTPServiceUnavailable(explanation=e.msg)
except store.NotFound as e:
raise HTTPNotFound(explanation=e.msg)
except (store.StoreGetNotSupported,
store.StoreRandomGetNotSupported,
store.UnknownScheme) as e:
raise HTTPBadRequest(explanation=e.msg)
image_size = int(image_size) if image_size else None
#返回镜像image_data以及镜像的size
return image_data, image_size
ps:get_store_from_uri和store.get 这两个函数都无法进一步点进去看了,openstack官网文档里表示glance-store这部分代码已经提取出去了
但是他留下了函数说明:get_store_from_uri()通过给定一个URI,返回存储对象