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