• OpenStack-----Nova源码分析之创建虚拟机


    Nova源码解析学习:

    一、浏览代码的工具:

    1. Vim+各种插件
    2. Eclipse+PyDev插件
    3. Spyder

    二、源码地图:

    1、每个子项目的源码根目录下都有一个setup.py和setuo.cfg文件

    2、Setup.py中的setup函数有大量的参数需要设置(包括项目的名称、作者、版本等),setup.cfg文件的出现将setup函数解脱出来,它使用pdf工具去读取和过滤setup.cfg中的数据,将解析后的结果作为自己的参数。

    3、Setup.cfg文件的内容由很多section组成,能帮助我们更好的去理解代码的section唯有entry_points(对于一个Python包来说,entry points可以简单地理解为通过setuptools注册的外部可以直接调用的接口)。

    4、Entry points都是在运行时动态导入的,有点类似于可扩展的插件。

    5、Entry points组中有一个特殊的存在-console_scripts,里面的每一个entry points都表示一个可执行脚本会被生成并且安装。

    1. console_scripts =
    2.     nova-api = nova.cmd.api:main  
    3.     nova-api-metadata = nova.cmd.api_metadata:main  
    4.     nova-api-os-compute = nova.cmd.api_os_compute:main
    5.     nova-compute = nova.cmd.compute:main
    6.     nova-conductor = nova.cmd.conductor:main
    7.     nova-manage = nova.cmd.manage:main
    8.     nova-novncproxy = nova.cmd.novncproxy:main
    9.     nova-policy = nova.cmd.policy:main
    10.     nova-rootwrap = oslo_rootwrap.cmd:main
    11.     nova-rootwrap-daemon = oslo_rootwrap.cmd:daemon
    12.     nova-scheduler = nova.cmd.scheduler:main
    13.     nova-serialproxy = nova.cmd.serialproxy:main
    14.     nova-spicehtml5proxy = nova.cmd.spicehtml5proxy:main
    15.     nova-status = nova.cmd.status:main

    由此可知nova项目安装后会包含21个可执行程序

    三、结构分析:

    1、console_scripts中主要服务:

    1. nova-api:Nova对外提供的RESTful API服务,目前提供两种API服务,即nova-api-metadata和nova-apipos-compute
    2. Nova-api根据配置文件/etc/nova/nova.conf的enable_apis选项设置启动这两种服务。
    3. nova-api-metadata :接受虚拟机实例metadata(元数据)相关请求,目前这部分工作由Neutron项目完成,而nova-api-metadata API服务只在多计算节点部署,并且使用nova-network情况下使用。
    4. nova-api-os-compute :Openstack API服务
    5. nova-compute:Computer服务
    6. nova-conductor:Conductor服务
    7. nova-manage :提供很多与Nova维护和管理相关的功能,比如用户创建,VPN管理等。
    8. nova-novncproxy:Nova提供了 novncproxy代理支持用户通过vnc访问虚拟机。提供完整的vnc访问功能,涉及几个Nova服务(nova-consoleauth提供认证授权, nova-novncproxy用于支持基于浏览器的vnc客户端,nova-xvpvncproxy用于支持基于Java的vnc客户端。)
    9. nova-rootwrap :用于在Openstack在运行过程中以root身份运行某些shell命令
    10. nova-scheduler :Scheduler服务。

    Nova-api会读取api-paste.ini,从中加载整个WSGI stack。最终API的入口点都位于nova.api.openstack.compute路径中。

    1. 通常一个服务的目录都会包含api.py、rpcapi.py、manager.py,这三个是最最重要的模块。

    api.py: 通常是供其它组件调用的封装库

    rpcapi.py:这个是RPC请求的封装,或者说是RPC封装的client端,该模块封装了所有RPC请求调用。

    manager.py: 这个才是真正服务的功能实现,也是RPC的服务端,即处理RPC请求的入口,实现的方法和rpcapi实现的方法一一对应。

    Nova中各个服务之间的通信使用了基于AMQP实现的RPC机制,其中nova-compute、 nova-conductor 和 nova-scheduler 在启动时都会注册一个 RPC Server,而 nova-api 因为Nova内部并没有服务会调用它提供的接口,所以无需注册。

    比如对一个虚拟机执行关机操作的流程为:

    API节点:

    Nova-api接收用户请求-》nova-api调用compute/api.py-》compute/api调用rpcapi.py-》rpcapi.py向目标计算机发起stop_instance()RPC请求

    计算节点:

    收到MQ RPC消息-》解析stop_instance()请求-》调用compute/manager.py的callback方法stop_instance()-》调用libvirt关机虚拟机

    1. Openstack项目的目录结构是按照功能划分的,而不是服务组件,因此并不是所有的目录都能由对应的组件。

    Nova主要目录结构解析:

    cmd:这是服务的启动脚本,即所有服务的main函数。看服务怎么初始化,就从这里开始。

    db: 封装数据库访问API,目前支持的driver为sqlalchemy,还包括migrate repository。

    conf:Nova的配置项声明都在这里,想看Nova配置的作用和默认值可以从这个目录入手。

    locale: 本地化处理。

    image: 封装image API,其实就是调用python-glanceclient。

    network: 封装网络服务接口,根据配置不同,可能调用nova-network或者neutron。

    volume: 封装数据卷访问接口,通常是Cinder的client封装,调用python-cinderclient。

    virt: 这是所有支持的hypervisor驱动,主流的如libvirt、xen等。

    objects: 对象模型,封装了所有实体对象的CURD操作,相对直接调用db的model更安全,并且支持版本控制。

    policies: policy校验实现。

    tests: 单元测试和功能测试代码。

    注:

    1、 pbr库是一个使用统一方式管理setuptools包的库。pbr库通过一个setup钩子函数读取并过滤setup.cfg中的数据,以填充默认值并提供更多合理的操作;然后将结果作为参数返回给setup.py。

    2、OpenStack社区将所有组件中的具有共性的组件剥离出来,并统一放在oslo组件下。oslo中的组件不仅可以在OpenStack项目中使用,也可以单独作为第三方工具包供其他项目使用。

    3、oslo.config项目是oslo组件中用于OpenStack配置文件的一个项目

    4、oslo项目创建了oslo.policy子项目为所有OpenStack服务提供RBAC策略实施支持。

    • 创建虚拟机过程简要分析

    需要注意的是Nova支持同时创建多台虚拟机,因此在调度时需要选择多个宿主机。

    Nova-api

    1、入口为nova/api/openstack/compute/servers.py的create方法,该方法检查了一堆参数以及policy后,调用compute_api的create方法。

    1. def create(self, req, body):
    2.     """Creates a new server for a given user."""
    3.     context = req.environ['nova.context']
    4.     server_dict = body['server']
    5.     password = self._get_server_admin_password(server_dict)
    6.     name = common.normalize_name(server_dict['name'])
    7.     description = name
    8. ............
    9. flavor_id = self._flavor_id_from_req_data(body)
    10. try:
    11.     flavor = flavors.get_flavor_by_flavor_id(
    12.     flavor_id, ctxt=context, read_deleted="no")
    13.     supports_multiattach = common.supports_multiattach_volume(req)
    14.     supports_port_resource_request = \
    15.     common.supports_port_resource_request(req)
    16.     instances, resv_id = self.compute_api.create(
    17.         context,
    18.         flavor,
    19.         image_uuid,
    20.         display_name=name,
    21.         display_description=description,
    22.         hostname=hostname,
    23.         availability_zone=availability_zone,
    24.         forced_host=host, forced_node=node,
    25.         metadata=server_dict.get('metadata', {}),
    26.         admin_password=password,
    27.         check_server_group_quota=True,
    28.         supports_multiattach=supports_multiattach,
    29.         supports_port_resource_request=supports_port_resource_request,
    30.         **create_kwargs)
    31. ......................
    1. 这里的compute_api即nova/compute/api.py模块,找到该模块的create方法,该方法会创建数据库记录、检查参数等,然后调用compute_task_api中_create_instance的build_instances方法:
    1. self.compute_task_api.schedule_and_build_instances(
    2.     context,
    3.     build_requests=build_requests,
    4.     request_spec=request_specs,
    5.     image=boot_meta,
    6.     admin_password=admin_password,
    7.     injected_files=injected_files,
    8.     requested_networks=requested_networks,
    9.     block_device_mapping=block_device_mapping,
    10.     tags=tags)
    1. compute_task_api即conductor的api.pyself.compute_task_api=conductor.ComputeTaskAPI。conductor的api并没有执行什么操作,直接调用了conductor_compute_rpcapi的build_instances方法nova/conductor/api:
    1. def schedule_and_build_instances(self, context, build_requests,
    2.                                  request_spec, image,
    3.                                  admin_password, injected_files,
    4.                                  requested_networks, block_device_mapping,
    5.                                  tags=None):
    6.     self.conductor_compute_rpcapi.schedule_and_build_instances(
    7.         context, build_requests, request_spec, image,
    8.         admin_password, injected_files, requested_networks,
    9.         block_device_mapping, tags)
    1. 该方法就是conductor RPC API,即nova/conductor/rpcapi.py模块,该方法除了一堆的版本检查,剩下的就是对RPC调用的封装,代码只有两行在build_instance方法中:
    1. cctxt = self.client.prepare(version=version)
    2. cctxt.cast(context, 'build_instances', **kwargs)

    其中cast表示异步调用,build_instances是远程调用的方法,kwargs是传递的参数。参数是字典类型,没有复杂对象结构,因此不需要特别的序列化操作。

    Ps:截至到现在,虽然目录由api->compute->conductor,但仍在nova-api进程中运行,直到cast方法执行,该方法由于是异步调用,因此nova-api任务完成,此时会响应用户请求,虚拟机状态为building。

    nova-conductor

    前景摘要:由于是向nova-conductor发起的RPC调用,而前面说了接收端肯定是manager.py因此进程跳到nova-conductor服务

    1. 入口为nova/conductor/manager.py的build_instances方法,该方法首先调用了_schedule_instances方法,该方法调用了scheduler_client的select_destinations方法:
    1. def _schedule_instances(self, context, request_spec,
    2.                         instance_uuids=None, return_alternates=False):
    3.     scheduler_utils.setup_instance_group(context, request_spec)
    4.     with timeutils.StopWatch() as timer:
    5.         host_lists = self.query_client.select_destinations(
    6.             context, request_spec, instance_uuids, return_objects=True,
    7.             return_alternates=return_alternates)
    8.     LOG.debug('Took %0.2f seconds to select destinations for %s '
    9.               'instance(s).', timer.elapsed(), len(instance_uuids))
    10.     return host_lists

    scheduler_client和compute_api以及compute_task_api都是一样对服务的client SDK调用,不过scheduler没有api.py,而是有个单独的client目录,实现在client目录的__init__.py,

    2、这里仅仅是调用query.py下的SchedulerQueryClient的select_destinations实现,然后又很直接地调用了scheduler_rpcapi的select_destinations方法,终于又到了RPC调用环节(nova/scheduler/client/query)

    1. def select_destinations(self, context, spec_obj, instance_uuids,
    2.         return_objects=False, return_alternates=False):
    3.     return self.scheduler_rpcapi.select_destinations(context, spec_obj,
    4.             instance_uuids, return_objects, return_alternates)

    3、毫无疑问,RPC封装同样是在scheduler的rpcapi中实现在select_destinations方法中。该方法RPC调用代码如下:

    return cctxt.call(ctxt, 'select_destinations', **msg_args)

    注意这里调用的call方法,即同步RPC调用,此时nova-conductor并不会退出,而是堵塞等待直到nova-scheduler返回。因此当前状态为nova-conductor为blocked状态,等待nova-scheduler返回,nova-scheduler接管任务。

     nova-scheduler

    同理找到scheduler的manager.py模块的select_destinations方法,该方法会调用driver方法,这里的driver其实就是调度算法实现,通常用的比较多的就是Filter Scheduler算法,对应filter_scheduler.py模块,该模块首先通过host_manager拿到所有的计算节点信息,然后通过filters过滤掉不满足条件的计算节点,剩下的节点通过weigh方法计算权值,最后选择权值高的作为候选计算节点返回。最后nova-scheduler返回调度结果的hosts集合,任务结束,返回到nova-conductor服务。

    nova-condutor

    回到scheduler/manager.py的build_instances方法,nova-conductor等待nova-scheduler返回后,拿到调度的计算节点列表。因为可能同时启动多个虚拟机,因此循环调用了compute_rpcapi的build_and_run_instance方法。

    看到xxxrpc立即想到对应的代码位置,位于compute/rpcapi模块,该方法向nova-compute发起RPC请求位于build_and_run_instance方法中:

    cctxt.cast(ctxt, 'build_and_run_instance', **kwargs)

    nova-compute

    1. 到了nova-compute服务,入口为compute/manager.py,找到build_and_run_instance方法,该方法调用了driver的spawn方法
    1. def _build_and_run_instance(self, context, instance, image, injected_files,
    2.         admin_password, requested_networks, security_groups,
    3.         block_device_mapping, node, limits, filter_properties,
    4.         request_spec=None, accel_uuids=None):
    5. ......................
    6. self.driver.spawn(context, instance, image_meta,
    7.                   injected_files, admin_password,
    8.                   allocs, network_info=network_info,
    9.                   block_device_info=block_device_info,
    10.                   accel_info=accel_info)
    11. ......................

    1. 这里的driver就是各种hypervisor的实现,所有实现的driver都在virt目录下,入口为driver.py,比如libvirt driver实现对应为virt/libvirt/driver.py,找到spawn方法,该方法拉取镜像创建根磁盘、生成xml文件、define domain,启动domain等。最后虚拟机完成创建。nova-compute服务结束。
    1. def spawn(self, context, instance, image_meta, injected_files,
    2.           admin_password, allocations, network_info=None,
    3.           block_device_info=None, power_on=True, accel_info=None):
    4. .....................

  • 相关阅读:
    Git常用命令
    DevOps转型的意义:加速创新、提高效率
    关于knike4j接口文档信息泄露的处理记录
    虹科案例 | 虹科HiveMQ助力实现百万辆汽车智能互联
    JVM成神之路(十二) -- Jvm性能优化指南
    多线程的概念
    Java连接FTP服务器上传文件报错
    GIT代码迁移和仓库镜像
    web前端面试-- 在 JavaScript 中 bind , apply 和 call 的区别
    【备忘】清理Office缓存
  • 原文地址:https://blog.csdn.net/m0_51734025/article/details/125422544