nova組件、架構、代碼目錄以及建立虛擬機流程

##1. nova主要組件及功能 ##node

  按功能劃分其主要組件有:算法

(1) 虛擬機管理: nova-api、nova-compute、nova-scheduler數據庫

(2) 虛擬機VNC及日誌管理: nova-console、nova-consoleauth編程

(3) 數據庫管理: nova-conductor後端

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

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驅動,其體系架構如圖所示。app

                

  Libvirt的管理功能既包括本地管理,也包括遠程管理,其管理功能以下:
  1)域的管理:包括對節點上的域的各個生命週期的管理,如啓動、中止、暫停、保存、回覆和動態遷移。也包括對多種設備類型的熱插拔操做,包括磁盤、網卡、內存和CPU,固然不一樣的Hypervisor對這些熱插拔的支持程度也有所不一樣。

  2)遠程節點管理:只要物理節點上運行了libvirtd這個守護進程,遠程的管理程序就能夠鏈接到該節點進程管理操做,通過認證和受權以後,全部的libvirt功能均可以被訪問和使用。Libvirt支持多種網絡遠程傳輸類型,如SSH、TCP套接字、Unix domain socket、支持TLS的加密傳輸等。

  3)存儲管理:任何運行了libvirtd守護進程的主機,均可以經過libvirt來管理不一樣的存儲,如建立不一樣格式的虛擬機鏡像(qcow二、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

相關文章
相關標籤/搜索