Openstack Nova 源码分析 — Create instances (nova-conductor阶段)

       Nova 控制着一个个虚拟机的状态变迁和生命周期,这种对虚拟机生命周期的管理是由 nova-compute service 来完成的。


目录

前言

Nova 控制着一个个虚拟机的状态变迁和生命周期,这种对虚拟机生命周期的管理是由 nova-compute service 来完成的。
在了解 Nova 创建虚拟机的流程之前,需要先补充一些 Openstack 基本概念。

Instance

Instance 表示一个虚拟机,是虚拟化世界的个体,类似与现实世界中的人类。所以,相同的,Instance 也具有一些特征性的标识,也可以称之为属性。如下:
1. 一个唯一的 ID 去标识 Instance
2. 一些描述 Instance 规格特征的信息。EG. Size/内存/InstanceName
3. 有字段去表示 Instance 运行在哪一台 Host
4. 有字段去表示 Instance Status
5. 有字段去表示 Create | Delete Instance 的时间

Nova 在 /opt/stack/nova/nova/objects/instance.py 中对 Instance 进行了描述。
NOTE/opt/stack/nova/nova/objects/ 该目录下存放数据库表对象文件,一个 Class 映射到一个张数据库表。

86   class _BaseInstance(base.NovaPersistentObject, base.NovaObject,
                        base.NovaObjectDictCompat):
        fields = {
            'id': fields.IntegerField(),
                
            'user_id': fields.StringField(nullable=True),
            'project_id': fields.StringField(nullable=True),
                
     ...

Flavor

在 Create Instance 之前,需要为 Instance 指定一组资源(Disk/Memory/VCPU/RootDisk/EphemeralDisk/Swap)。
nova-compute 在执行创建之前,需要通过这些资源配置来判断是否由足够的 Host 资源来实现创建。这组资源的设置就是 flavor,即创建虚拟机的规格。每个 Instance 对象的 instance_type_id 字段就表示该 Instance 所拥有的 flavor 。

执行 Commands :nova flavor-list 可以查看 Nova 默认 flavor 的信息 ; nova flavor-create 创建新的 flavor 。

Instance Status

Instance 拥有三个字段与描述其状态有关,这些状态刚被用于程序流的条件判断。

  • power_status: 使用 libvirt 或 Virt Driver 提供的接口从 Hypervisor 中获取的 Instance Power Status。

    • Running
    • Shutdown
    • NoState
  • vm_state: 虚拟机的稳定状态。

    • Active:正常运行
    • Suspended:停止运行
  • task_state:过渡状态,与 Compute API 的执行密切相关,表示 Instance 正在执行什么任务,只有在执行任务的时候才会有 task_state 。

Virt Driver

Openstack Nova 仅仅是作为云计算虚拟机的管理工具,其本身并不提供任何的虚拟化技术,而是交由具体的 Hypervisor 来实现虚拟机的创建和管理。因此,nova-compute 需要和不同的 Hpyervisor 进行交互,并使用 Virt Driver 来作为这种交互的支撑,其代码实现存放在 /opt/stack/nova/nova/virt/ 目录下。

目前,Nova 实现了 Hyper-V/Libvirt/VMware/Xen 这四种 Virt Dirver 。可以通过配置选项 compute_dirver 来指定使用哪一种 Virt Driver 。

compute_driver = libvirt.LibvirtDriver
compute_driver=vmwareapi.VMwareVCDriver

EXAMPLE:nova-compute 通过 Virt Dirver 来调用 Libvirt 库中提供的 API 来实现虚拟机的管理。
这里写图片描述

从上图可以看出,nova-compute 必须部署在 Linux+KVM 的 Host 上,当然,现在的 Linux Kernel Version 大多都嵌入了 KVM 虚拟化技术。

Resource Tracker

nova-compute 需要在数据库中存储 Host 的资源使用情况,以便于 nova-scheduler 获取作为选择 Host 依据的数据。Node Project 使用 ConputeNode 数据库表对象来保存 Compute Node的配置信息和资源使用情况(/opt/stack/nova/nova/objects/compute_node.py)。所以 nova-compute 会为每一个 Host 创建一个 ResourceTracker 对象,用于更新 ComputeNode 对象在数据库中对应的 compute_nodes 表。

有两种更新*compute_nodes表的方式:*

  • Claim 机制:在创建 Instance 之前,预先测试 Host 的可用资源能否满足 Create Instance。如果满足,则首先更新数据库,将虚拟机申请的资源从可用资源中减去。 如果后来创建失败或者将虚拟机删除时,会再通过 Claim 将原来减去的部分再添加到可用资源中去。实现:nova.compute.resource_tracker:instance_claims

  • Periodic Task:在 nova.compute.manager.ComputeManager 中有个周期性任务 update_available_resource() 用于更新 Host 的资源数据。

NOTE:上面两种数据库更新方式并不冲突。Claim 实在数据库当前数据的基础上去计算并更新,可以保证数据库里可用资源的及时更新。 Periodic Task 是为了数据库内信息的准确性,它每次执行都会通过 Hypervisor 去获取 Host 的信息,并将这些信息更新到数据库中。前者是在 Create Instance 的时候执行,后者是周期性执行。

nova-conductor

Conductor Service 的加入,使得 nova-compute 和数据库解耦,为数据库提供了一重安全保障,提高了对数据库的访问效率,而且还可以实现升级数据库 schema 的同时无需上级 nova-compute 。所以 nova-compute 所有访问数据库的操作都是交由 nova-conductor 去完成。

随着 nova-conductor 的不断完善,它还需要承担部分原来属于 nova-compute 的 TaskAPI 的任务。TaskAPI 主要包含了耗时较长的任务,例如:Create Instance/Migrate Instance 等等。

Create Instance(nova-conductor阶段)

Create Instance 属于 TaskAPI 任务,耗时较长,由 nova-conductor 来承担。但是需要注意的是,对 Instance 管理的流程可以分为俩个阶段,就是调度阶段和执行阶段。调度阶段由 Openstack 负责,主要是 Nova Project 中的几个 Services 来协同完成。而执行阶段则由 Hypervisor 来具体实现。Openstack 仅仅扮演了 管理者的角色。

这里写图片描述

  • nova.conductor.rpcapi 中定义一个 build_instances() RPC 接口函数:
301     def build_instances(self, context, instances, image, filter_properties,                                 
               admin_password, injected_files, requested_networks,
               security_groups, block_device_mapping, legacy_bdm=True):
           image_p = jsonutils.to_primitive(image)
           version = '1.10'
           if not self.client.can_send_version(version):
               version = '1.9'
               if 'instance_type' in filter_properties:
                   flavor = filter_properties['instance_type']
                   flavor_p = objects_base.obj_to_primitive(flavor)
                  filter_properties = dict(filter_properties,
                                           instance_type=flavor_p)
          kw = {'instances': instances, 'image': image_p,
                 'filter_properties': filter_properties,
                 'admin_password': admin_password,
                 'injected_files': injected_files,
                 'requested_networks': requested_networks,
                 'security_groups': security_groups}
          if not self.client.can_send_version(version):
              version = '1.8'
              kw['requested_networks'] = kw['requested_networks'].as_tuples()
          if not self.client.can_send_version('1.7'):
              version = '1.5'
              bdm_p = objects_base.obj_to_primitive(block_device_mapping)
              kw.update({'block_device_mapping': bdm_p,
                         'legacy_bdm': legacy_bdm})
           
          cctxt = self.client.prepare(version=version)
          cctxt.cast(context, 'build_instances', **kw)
# /opt/stack/nova/nova/conductor/manager.py
# 创建虚拟机属于 TaskAPI 任务,所有的 TaskAPI 都交由 nova-conductor 来处理,所以 manager.py 的实现在 Conductor 中
 class ComputeTaskManager(base.Base):                                                             
       """Namespace for compute methods.
  2  
  3     This class presents an rpc API for nova-conductor under the 'compute_task'
  4     namespace.  The methods here are compute operations that are invoked
  5     by the API service.  These methods see the operation to completion, which
  6     may involve coordinating activities on multiple compute nodes.
  7     """
    ... 
     def __init__(self):                                                     
           super(ComputeTaskManager, self).__init__()
           self.compute_rpcapi = compute_rpcapi.ComputeAPI()           
           self.image_api = image.API()
           self.servicegroup_api = servicegroup.API()
           self.scheduler_client = scheduler_client.SchedulerClient()  
           self.notifier = rpc.get_notifier('compute', CONF.host)

        # 其中 Scheduler client 是对 Scheduler_rpcapi 的封装,本质上是一个 Scheduler 提供的 rpcapi:
        #    nova.manager.ComputeTaskManager:scheduler_client 
        #        ==> nova.scheduler.client.__init__:__init__ 
        #             ==> nova.scheduler.client.query.SchedulerQueryClient:__init__
        #                 ==> self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI()
    ...

        # 1.(被调用) HTTP Request ==> nova.api ==> RPC cast ==> nova-conductor (nova.conductor.manager:ComputeTaskManager.build_instances() 是 nova.conductor.rpcapi.build_instances RPC 接口的实际功能实现函数)     
        # 参数传递过程: nova-api 调用 conductor.rpcapi:build_instances() 并传入实参 
        #                 ==> 将实参和其他信息打包发送到消息队列(数据流形式) 
        #                    ==> 将实参传入 conductor.manager:build_instances() ; 
     def build_instances(self, context, instances, image, filter_properties,    
               admin_password, injected_files, requested_networks,
               security_groups, block_device_mapping=None, legacy_bdm=True):
           # TODO(ndipanov): Remove block_device_mapping and legacy_bdm in version
           #                 2.0 of the RPC API.
         request_spec = scheduler_utils.build_request_spec(context, image,
                                                             instances)
            # request_spec 是一个字典类型,包含了详细的虚拟机信息,nova-scheduler 依据这些信息来选择一个最佳的主机并返回给 nova-conductor

    ... 
                # 2.(调用) nova-conductor 通过 RPC call 方式调用 nova-scheduler 的接口函数: nova.scheduler.rpcapi:select_destinations()
                # 通过 nova-scheduler 来获取 HOSTS 
             hosts = self.scheduler_client.select_destinations(context,                               
                       request_spec, filter_properties)
    ...

                # 3.(调用) nova-conductor ==> RPC cast ==> nova-compute (nova.compute.build_and_run_instance())  
             self.compute_rpcapi.build_and_run_instance(context,                
                       instance=instance, host=host['host'], image=image,
                       request_spec=request_spec,
                       filter_properties=local_filter_props,
                       admin_password=admin_password,
                       injected_files=injected_files,
                       requested_networks=requested_networks,
                       security_groups=security_groups,
                       block_device_mapping=bdms, node=host['nodename'],
                       limits=host['limits'])

在 经过一番远程调用之后,终于进入到了 nova-compute 调用 Virt Dirver 的阶段。



有一个清醒的头脑比有一个聪明的头脑更重要;有一种良好的习惯比有一种熟练的技巧更实用;有一股青春活力比有一副健全的臂膀更有力;有一身勇气和胆识比有一门知识更强劲。感动往往发生在一刹那间:一个眼神可能让你忆念一世;一次资助可能让你感动一生;一句祝福可能让你温馨一世;一点宽容可能让你感激终生。健康的才是美丽的,合适的才是最好的,常新的才是迷人的,平凡的才是伟大的,坚韧的才是长久的,真实的才是永恒的。