##1. nova主要組件及功能 ##node
按功能劃分其主要組件有:算法
(1) 虛擬機管理: nova-api、nova-compute、nova-scheduler數據庫
(2) 虛擬機VNC及日誌管理: nova-console、nova-consoleauth編程
(3) 數據庫管理: nova-conductor後端
(4) 安全管理: nova-consoleauth、nova-certapi
就目前而言,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