nova组件、架构、代码目录以及创建虚拟机流程

##1. nova主要组件及功能 ##

  按功能划分其主要组件有:

(1) 虚拟机管理: nova-api、nova-compute、nova-scheduler

(2) 虚拟机VNC及日志管理: nova-console、nova-consoleauth

(3) 数据库管理: nova-conductor

(4) 安全管理: nova-consoleauth、nova-cert

2.nova代码目录结构

3.nova体系架构

  就目前而言,nova主要由四个核心服务组成,分别是API、compute、conductor以及scheduler,它们之间均通过AMQP消息队列进行通信。

##4. 虚拟化技术 ##

1. KVM
  KVM(Kernel-based Virtual Machine),是集成到Linux内核的Hypervisor,是X86构架且硬件支持虚拟化技术(Intel VT或AMD-V)的Linux全虚拟化解决方案。它是Linux的一个很小的模块,利用Linux做大量的事情,如任务调度、内存管理和硬件设备交互等。
  KVM是一个独特的管理程序,通过将KVM作为一个内核模块实现,在虚拟环境下Linux内核集成管理程序将其作为一个可加载的模块,可以简化程序和提升性能。在这种模式下,每个虚拟机都是一个常规的Linux调度程序进行调度。
  每个Guest OS都是通过 /dev/kvm 设备映射的,它们拥有自己的虚拟地址空间,该空间映射到主机内核的物理地址空间。I/O 请求通过主机内核映射到在主机上(hypervisor)执行 QEMU 进程。

                

2. libvirt
  Libvirt是管理虚拟机和其他虚拟化功能,比如存储管理、网络管理的软件集合。它主要包括三部分:一个通用的API库、一个守护进程libvirtd和一个命令行工具virsh。其主要目标是为各种虚拟机化工具提供一套方便、可靠的编程接口,用单一的方式管理多种不同的虚拟化提供方式。
  Libvirt支持多种虚拟化方案,既支持包括KVM、QEMU、Xen、VMware、VirtualBox等在内的平台虚拟化方案,又支持OpenVZ、LXC等Linux容器虚拟化系统,还支持用户态Linux(UML)的虚拟化。
  Libvirt对多种不同的Hypervisor的支持是通过一种基于驱动程序的架构来实现的。其对不同的Hypervisor提供了不同的驱动:对Xen有Xen的驱动,对QEMU/KVM有QEMU驱动,对VMware有VMware驱动,其体系架构如图所示。

                

  Libvirt的管理功能既包括本地管理,也包括远程管理,其管理功能如下:
  1)域的管理:包括对节点上的域的各个生命周期的管理,如启动、停止、暂停、保存、回复和动态迁移。也包括对多种设备类型的热插拔操作,包括磁盘、网卡、内存和CPU,当然不同的Hypervisor对这些热插拔的支持程度也有所不同。

  2)远程节点管理:只要物理节点上运行了libvirtd这个守护进程,远程的管理程序就可以连接到该节点进程管理操作,经过认证和授权之后,所有的libvirt功能都可以被访问和使用。Libvirt支持多种网络远程传输类型,如SSH、TCP套接字、Unix domain socket、支持TLS的加密传输等。

  3)存储管理:任何运行了libvirtd守护进程的主机,都可以通过libvirt来管理不同的存储,如创建不同格式的虚拟机镜像(qcow2、raw、vmdk等)、挂载NFS共享存储系统、查看现有的LVM卷组、创建新的LVM卷组和逻辑卷、对磁盘设备分区、挂载iSCSI共享存储等等。当然同样支持对存储管理也是支持远程管理的。

  4)网络管理:任何运行了libvirtd守护进程的主机,都可以通过libvirt来管理物理和逻辑网络接口。包括列出现有的网络接口,配置网络接口,创建虚拟网络接口。网络接口的桥接,VLAN管理,NAT网络设置,为虚拟机分配虚拟网络接口等。

##5. 虚拟机创建过程 ##

                   图1 启动虚拟机nova与其他组件的交互过程

                   图2 启动虚拟机nova内部组件的交互过程

  下面重点观察虚拟机创建的整个流程(50个步骤):
  (1) Keystone

  (2) Nova-api

  nova-api起到cloud controller的作用,为所有的API查询提供一个接口,引发多数 业务流程的活动(比如运行一个实例)。其中create()方法是将REST接口参数映射到内部接口compute-api.create(),其中部分代码如下(/nova/api/openstack/compute/servers.py):

@wsgi.response(202)
@extensions.expected_errors((400, 403, 409, 413))
@validation.schema(schema_server_create_v20, '2.0', '2.0')
@validation.schema(schema_server_create, '2.1')
# 对外提供创建虚拟机的接口nova-api.create()
def create(self, req, body):
……
# 调用compute-api创建虚拟机
(instances, resv_id) = self.compute_api.create(context,
                       inst_type,
                       image_uuid,
                       display_name=name,
                       display_description=name,
                       metadata=server_dict.get('metadata',{}),
                       admin_password=password,
                       requested_networks=requested_networks,
                       check_server_group_quota=True,
                       **create_kwargs)

  (3) Nova-scheduler

  scheduler收到客户端发来的select_destinations请求消息后,调用nova/scheduler/filter_scheduler.py.FilterScheduler.select_destinations(),部分代码如下:

def    select_destinations(self, context, request_spec, filter_properties):
    """Selects a filtered set of hosts and nodes."""
    # TODO(sbauza): Change the select_destinations method to accept a
    # RequestSpec object directly (and add a new RPC API method for passing
    # a RequestSpec object over the wire)
    spec_obj = objects.RequestSpec.from_primitives(context,
                                                   request_spec,
                                                   filter_properties)
    self.notifier.info(
        context, 'scheduler.select_destinations.start',
        dict(request_spec=spec_obj.to_legacy_request_spec_dict()))

    num_instances = spec_obj.num_instances
	#根据过滤条件选择合适的主机,返回一个host列表
    selected_hosts = self._schedule(context, spec_obj)
	………

  从以上代码可以看出,该方法首先调用了_schedule()方法选取符合条件的主机,返回一个主机列表,_schedule()方法通过get_filtered_hosts()方法得到一个满足各种过滤条件的主机集合,然后通过get_weighed_hosts()方法得到一个最优weigh_hosts集合,通常情况选择第一个host作为目标。

       

  (4) Nova-compute

  nova-conductor通过调用nova/compute/rpcapi.py.ComputeAPI.build_and_run_instance()方法,该方法通过远程调用,将消息传给nova/compute/manager.py.ComputeManager.build_and_run_instance()方法,该方法进一步调用_do_build_and_run_instance()方法,最后调用_build_and_run_instance()方法,代码和注释如下:

def _build_and_run_instance(self, context, instance, image, injected_files,
        admin_password, requested_networks, security_groups,
        block_device_mapping, node, limits, filter_properties):
        image_name = image.get('name')
    self._notify_about_instance_usage(context, instance, 'create.start',extra_usage_info={'image_name': image_name})
    try:
        #获取/创建ResourceTracker实例,为后续的资源申请做准备
        rt = self._get_resource_tracker(node)
        #limits包含node的内存,磁盘等资源配额信息,验证node中的资源是否满足
        #该次启动请求,资源不足则抛出异常,可以在日志文件中看到类似的INFO log
        # ”Attempting claim: memory 2048 MB, disk 20 GB“
        with rt.instance_claim(context, instance, limits):
            # NOTE(russellb) It's important that this validation be done
            # *after* the resource tracker instance claim, as that is where
            # the host is set on the instance.
            self._validate_instance_group_policy(context, instance,
                    filter_properties)
            #为云主机申请网络资源,完成块设备验证及映射,更新实例状态
            with self._build_resources(context, instance,
                    requested_networks, security_groups, image,
                    block_device_mapping) as resources:
                instance.vm_state = vm_states.BUILDING
                instance.task_state = task_states.SPAWNING
                # NOTE(JoshNang) This also saves the changes to the
                # instance from _allocate_network_async, as they aren't
                # saved in that function to prevent races.
                instance.save(expected_task_state=
                        task_states.BLOCK_DEVICE_MAPPING)
                block_device_info = resources['block_device_info']
                network_info = resources['network_info']
                LOG.debug('Start spawning the instance on the hypervisor.',
                          instance=instance)
                with timeutils.StopWatch() as timer:
                    #调用hypervisor的spawn方法启动云主机实例,这里使用的是
                    #libvirt;所以这里跳转到`nova/virt/libvirt/driver.py/
                    #LibvirtDriver.spawn,见下面的分析
                    self.driver.spawn(context, instance, image,
                                      injected_files, admin_password,
                                      network_info=network_info,
                                      block_device_info=block_device_info)
                LOG.info(_LI('Took %0.2f seconds to spawn the instance on the hypervisor.'), timer.elapsed(),
                         instance=instance)

  接下来分析nova/virt/libvirt/driver.py/LibvirtDriver.spawn()方法,该方法主要有三个作用分别是:从glance下载镜像(如果本地_base目录没有的话),然后上传到后端存储;生成libvirt xml文件;调用libvirt启动实例。其代码和对应的注释如下:

def spawn(self, context, instance, image_meta, injected_files,
          admin_password, network_info=None, block_device_info=None):
    #根据image字典信息创建`nova/objects/image_meta.py/ImageMeta对象
    image_meta = objects.ImageMeta.from_dict(image_meta)
    #根据模拟器类型,获取块设备及光驱的总线类型,默认使用kvm,所以:
    #块设备,默认使用virtio;光驱,默认使用ide;并且根据
    #block_device_info设置设备映射,最后返回包含
    #{disk_bus,cdrom_bus,mapping}的字典
    disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
                                        instance,
                                        image_meta,
                                        block_device_info)
	#从glance下载镜像(如果本地_base目录没有的话),然后上传到后端存储,具体分析
	#见后文
    self._create_image(context, instance,
                       disk_info['mapping'],
                       network_info=network_info,
                       block_device_info=block_device_info,
                       files=injected_files,
                       admin_pass=admin_password)
    #生成libvirt xml文件,具体分析见后文
    xml = self._get_guest_xml(context, instance, network_info,
                              disk_info, image_meta,
                              block_device_info=block_device_info,
                              write_to_disk=True)
    #调用libvirt启动实例,具体分析见后文
    self._create_domain_and_network(context, xml, instance, network_info, disk_info,
                                    block_device_info=block_device_info)
    LOG.debug("Instance is running", instance=instance)

  (5) Image

  (6) Libvirt driver定义XML

	<domain type='qemu' id='4'>
	<name>instance-00000003</name>
	<uuid>16b6d553-0c28-47c7-8f9f-a2f24a2d990d</uuid>
	<metadata>
<nova:instance xmlns:nova="http://openstack.org/xmlns/libvirt/nova/1.0">
  <nova:package version="12.0.1"/>
  <nova:name>vm1</nova:name>
  <nova:creationTime>2016-08-11 20:21:21</nova:creationTime>
  <nova:flavor name="m1.tiny">
    <nova:memory>512</nova:memory>
    <nova:disk>1</nova:disk>
    <nova:swap>0</nova:swap>
    <nova:vcpus>1</nova:vcpus>
  </nova:flavor>
  <nova:owner>
    <nova:user uuid="b6c95f2ce8d7468da31fb9f1146e0b83">admin</nova:user>
    <nova:project uuid="a500ff316c974920a7cee90497d5ff52">admin</nova:project>
  </nova:owner>
  <nova:root type="image" uuid="00885aa9-48c7-46ec-92e8-6153fb1c7d9e"/>
</nova:instance>
 </metadata>
 <memory unit='KiB'>524288</memory>
 <currentMemory unit='KiB'>524288</currentMemory>

                         xml文件实例

  接下来分析如何生成libvirt xml配置,其代码位于nova/virt/libvirt/driver.py/LibvirtDriver._get_guest_xml()。该方法的主要逻辑为:根据配置生成虚拟机配置字典;将生成的配置字典转换为xml格式;最后将xml保存到本地。代码和注释如下:

def _get_guest_xml(self, context, instance, network_info, disk_info,
                   image_meta, rescue=None,
                   block_device_info=None, write_to_disk=False):
    # NOTE(danms): Stringifying a NetworkInfo will take a lock. Do
    # this ahead of time so that we don't acquire it while also
    # holding the logging lock.
    network_info_str = str(network_info)
    msg = ('Start _get_guest_xml '
           'network_info=%(network_info)s '
           'disk_info=%(disk_info)s '
           'image_meta=%(image_meta)s rescue=%(rescue)s '
           'block_device_info=%(block_device_info)s' %
           {'network_info': network_info_str, 'disk_info': disk_info,
            'image_meta': image_meta, 'rescue': rescue,
            'block_device_info': block_device_info})
    # NOTE(mriedem): block_device_info can contain auth_password so we
    # need to sanitize the password in the message.
    LOG.debug(strutils.mask_password(msg), instance=instance)
    #获取虚拟机的配置信息
    conf = self._get_guest_config(instance, network_info, image_meta,
                                  disk_info, rescue, block_device_info,
    #将配置信息转换成xml格式                              context)
    xml = conf.to_xml()

    if write_to_disk:
        #获取计算节点上保存虚拟机镜像文件的路径
        instance_dir = libvirt_utils.get_instance_path(instance)
        #获取xml定义文件存储路径
        xml_path = os.path.join(instance_dir, 'libvirt.xml')
        #写入虚拟机xml定义文件
        libvirt_utils.write_to_file(xml_path, xml)

    LOG.debug('End _get_guest_xml xml=%(xml)s',
              {'xml': xml}, instance=instance)
    return xml

  (7) Neutron

                       vlan模式计算节点网络拓扑图

  (8) KVM

  前面的准备工作就绪,现在就可以启动虚拟机了,其实现是通过调用LibvirtDriver类中_create_domain_and_network方法,该方法主要实现虚拟机和虚拟网络的创建。其代码和注释如下:

def _create_domain_and_network(self, context, xml, instance, 
                               network_info,
                               disk_info, 
                               block_device_info=None,
                               power_on=True, reboot=False,
                               vifs_already_plugged=False):
#获取块设备映射,block_device_mapping=[]
block_device_mapping = driver.block_device_info_get_mapping(
        block_device_info)
#获取image的metadata
image_meta = objects.ImageMeta.from_instance(instance)

#如果开启了磁盘加密,就用指定的加密算法加密磁盘
# block_device_mapping=[],忽略相关的代码
for vol in block_device_mapping:
    .......
#vif_plugging_timeout=300(默认5分钟)
#检查neutron网络事件,如果vif是非active状态,就需要处理plug事件
timeout = CONF.vif_plugging_timeout
if (self._conn_supports_start_paused and
    utils.is_neutron() and not
    vifs_already_plugged and power_on and timeout):
        events = self._get_neutron_events(network_info)
    else:
        events = []
 #pause = true
 pause = bool(events)
 guest = None
 #忽略try{ }except处理代码
 #在启动云主机前,需要先准备好虚拟网卡
 #调用ComputeVirtAPI.wait_for_instance_event处理neutron网络
 #事件,这里是network-vif-plugged事件,在
 #wait_for_instance_event中启动eventlet线程处理事件,并等待结束
 #如果发生异常,则调用self._neutron_failed_callback处理。
 with self.virtapi.wait_for_instance_event(
            instance, events, deadline=timeout,
            error_callback=self._neutron_failed_callback):
      #安装虚拟网卡(使用的是bridge,最终调用的是
      #LibvirtGenericVIFDriver.plug_bridge方法)
      self.plug_vifs(instance, network_info)
      #设置基本的iptables规则
      self.firewall_driver.setup_basic_filtering(instance,
                                           network_info)
      #为云主机设置网络过滤规则,防火墙策略
     self.firewall_driver.prepare_instance_filter(instance,
                                           network_info)
      with self._lxc_disk_handler(instance, image_meta,
                                        block_device_info, 
                                        disk_info):
           #调用libvirt库启动虚拟机
           #xml是云主机xml配置,pause=true,power_on=true
           #这里使用的是qemu-kvm,所以先会通过qemu:///system连接
           #hypervisor,然后执行define,最后启动云主机
           guest = self._create_domain(
                    xml, pause=pause, power_on=power_on)
     #no-ops
     self.firewall_driver.apply_instance_filter(instance,
                                             network_info)
 # Resume only if domain has been paused
 if pause:
     guest.resume()
 return guest

  (9) Cinder

转载于:https://my.oschina.net/u/1179767/blog/752233

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值