nova-compute是管理和配置虛擬機的入口,在所有compute機器上都需要該服務來創建和管理虛擬機。
nova-compute服務的入口在 nova.cmd.compute:main ,其啟動過程與其他nova服務類似。
簡單看下它是如何啟動的, binary='nova-compute', topic='nova.compute.rpcapi' , manager=nova.compute.manager.ComputeManager ,manager類在初始化的時候會創建與其他服務交互的api實例:
class ComputeManager(manager.Manager): """Manages the running instances from creation to destruction.""" target = messaging.Target(version='3.32') def __init__(self, compute_driver=None, *args, **kwargs): """Load configuration options and connect to the hypervisor.""" self.virtapi = ComputeVirtAPI(self) self.network_api = network.API() self.volume_api = volume.API() self.image_api = image.API() self._last_host_check = 0 self._last_bw_usage_poll = 0 self._bw_usage_supported = True self._last_bw_usage_cell_update = 0 self.compute_api = compute.API() self.compute_rpcapi = compute_rpcapi.ComputeAPI() self.conductor_api = conductor.API() self.compute_task_api = conductor.ComputeTaskAPI() self.is_neutron_security_groups = ( openstack_driver.is_neutron_security_groups()) self.consoleauth_rpcapi = consoleauth.rpcapi.ConsoleAuthAPI() self.cells_rpcapi = cells_rpcapi.CellsAPI() self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI() self._resource_tracker_dict = {} self.instance_events = InstanceEvents() super(ComputeManager, self).__init__(service_name="compute", *args, **kwargs) # NOTE(russellb) Load the driver last. It may call back into the # compute manager via the virtapi, so we want it to be fully # initialized before that happens. self.driver = driver.load_compute_driver(self.virtapi, compute_driver) #libvirt.LibvirtDriver self.use_legacy_block_device_info = \ self.driver.need_legacy_block_device_info
在service.start()中會初始化主機上的虛擬機和安全組,並開啟RPC服務
def start(self): verstr = version.version_string_with_package() LOG.audit(_('Starting %(topic)s node (version %(version)s)'), {'topic': self.topic, 'version': verstr}) self.basic_config_check() # Initialization for a standalone compute service # init instances and iptables self.manager.init_host() self.model_disconnected = False ctxt = context.get_admin_context() try: # init conductor api self.service_ref = self.conductor_api.service_get_by_args(ctxt, self.host, self.binary) self.service_id = self.service_ref['id'] except exception.NotFound: try: self.service_ref = self._create_service_ref(ctxt) except (exception.ServiceTopicExists, exception.ServiceBinaryExists): # NOTE(danms): If we race to create a record with a sibling # worker, don't fail here. self.service_ref = self.conductor_api.service_get_by_args(ctxt, self.host, self.binary) """After the service is initialized, but before we fully bring the service up by listening on RPC queues, make sure to update our available resources (and indirectly our available nodes). """ self.manager.pre_start_hook() if self.backdoor_port is not None: self.manager.backdoor_port = self.backdoor_port LOG.debug("Creating RPC server for service %s", self.topic) # start rpc api target = messaging.Target(topic=self.topic, server=self.host) endpoints = [ self.manager, baserpc.BaseRPCAPI(self.manager.service_name, self.backdoor_port) ] endpoints.extend(self.manager.additional_endpoints) serializer = objects_base.NovaObjectSerializer() self.rpcserver = rpc.get_server(target, endpoints, serializer) self.rpcserver.start() self.manager.post_start_hook() LOG.debug("Join ServiceGroup membership for this service %s", self.topic) # Add service to the ServiceGroup membership group. self.servicegroup_api.join(self.host, self.topic, self) if self.periodic_enable: if self.periodic_fuzzy_delay: initial_delay = random.randint(0, self.periodic_fuzzy_delay) else: initial_delay = None self.tg.add_dynamic_timer(self.periodic_tasks, initial_delay=initial_delay, periodic_interval_max= self.periodic_interval_max)
這樣,nova-compute服務就起來了,而之后接收到的請求都會到 nova.compute.manager.ComputeManager 類來處理。
最常使用的KVM虛擬化需要配置driver為libvirt.LibvirtDriver(所有支持的driver有 libvirt.LibvirtDriver, xenapi.XenAPIDriver, fake.FakeDriver, baremetal.BareMetalDriver, vmwareapi.VMwareVCDriver, hyperv.HyperVDriver).
下面來看看libvirt是如何創建虛擬機的:
def spawn(self, context, instance, image_meta, injected_files, admin_password, network_info=None, block_device_info=None): disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type, instance, block_device_info, image_meta) # 獲取鏡像信息 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) # 創建卷、網絡,並創建虛擬機 self._create_domain_and_network(context, xml, instance, network_info, block_device_info) LOG.debug("Instance is running", instance=instance) def _wait_for_boot(): """Called at an interval until the VM is running.""" state = self.get_info(instance)['state'] if state == power_state.RUNNING: LOG.info(_LI("Instance spawned successfully."), instance=instance) raise loopingcall.LoopingCallDone() # 等待虛擬機狀態變為RUNNING timer = loopingcall.FixedIntervalLoopingCall(_wait_for_boot) timer.start(interval=0.5).wait()
_create_domain_and_network為實際的創建設備和創建虛擬機函數
def _create_domain_and_network(self, context, xml, instance, network_info, block_device_info=None, power_on=True, reboot=False, vifs_already_plugged=False): """Do required network setup and create domain.""" # 查詢卷的映射關系 block_device_mapping = driver.block_device_info_get_mapping( block_device_info) # 掛載卷 for vol in block_device_mapping: connection_info = vol['connection_info'] disk_info = blockinfo.get_info_from_bdm( CONF.libvirt.virt_type, vol) # 根據volume類型,調用其connect_volume方法掛載卷 # 支持的類型包括: # [ # 'iscsi=nova.virt.libvirt.volume.LibvirtISCSIVolumeDriver', # 'iser=nova.virt.libvirt.volume.LibvirtISERVolumeDriver', # 'local=nova.virt.libvirt.volume.LibvirtVolumeDriver', # 'fake=nova.virt.libvirt.volume.LibvirtFakeVolumeDriver', # 'rbd=nova.virt.libvirt.volume.LibvirtNetVolumeDriver', # 'sheepdog=nova.virt.libvirt.volume.LibvirtNetVolumeDriver', # 'nfs=nova.virt.libvirt.volume.LibvirtNFSVolumeDriver', # 'aoe=nova.virt.libvirt.volume.LibvirtAOEVolumeDriver', # 'glusterfs='nova.virt.libvirt.volume.LibvirtGlusterfsVolumeDriver', # 'fibre_channel=nova.virt.libvirt.volume.LibvirtFibreChannelVolumeDriver', # 'scality=nova.virt.libvirt.volume.LibvirtScalityVolumeDriver', # ] conf = self._connect_volume(connection_info, disk_info) # cache device_path in connection_info -- required by encryptors if 'data' in connection_info: connection_info['data']['device_path'] = conf.source_path vol['connection_info'] = connection_info vol.save(context) # 如果是加密卷,需要attach到encryptor if (not reboot and 'data' in connection_info and 'volume_id' in connection_info['data']): volume_id = connection_info['data']['volume_id'] encryption = encryptors.get_encryption_metadata( context, self._volume_api, volume_id, connection_info) if encryption: encryptor = self._get_volume_encryptor(connection_info, encryption) encryptor.attach_volume(context, **encryption) 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 = [] launch_flags = events and libvirt.VIR_DOMAIN_START_PAUSED or 0 domain = None try: with self.virtapi.wait_for_instance_event( instance, events, deadline=timeout, error_callback=self._neutron_failed_callback): # Plug VIFs into networks using vif_driver self.plug_vifs(instance, network_info) # Init iptables using firewall_driver self.firewall_driver.setup_basic_filtering(instance, network_info) self.firewall_driver.prepare_instance_filter(instance, network_info) # 調用libvirt庫提供的方法創建虛擬機 # if xml: # err = _LE('Error defining a domain with XML: %s') % xml # domain = self._conn.defineXML(xml) # if power_on: # err = _LE('Error launching a defined domain with XML: %s') \ # % domain.XMLDesc(0) # domain.createWithFlags(launch_flags) domain = self._create_domain( xml, instance=instance, launch_flags=launch_flags, power_on=power_on) self.firewall_driver.apply_instance_filter(instance, network_info) except exception.VirtualInterfaceCreateException: # Neutron reported failure and we didn't swallow it, so # bail here with excutils.save_and_reraise_exception(): if domain: domain.destroy() self.cleanup(context, instance, network_info=network_info, block_device_info=block_device_info) except eventlet.timeout.Timeout: # We never heard from Neutron LOG.warn(_LW('Timeout waiting for vif plugging callback for ' 'instance %(uuid)s'), {'uuid': instance['uuid']}) if CONF.vif_plugging_is_fatal: if domain: domain.destroy() self.cleanup(context, instance, network_info=network_info, block_device_info=block_device_info) raise exception.VirtualInterfaceCreateException() # Resume only if domain has been paused if launch_flags & libvirt.VIR_DOMAIN_START_PAUSED: domain.resume() return domain