##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