OpenStack基于Libvirt的虚拟化平台调度实现----Nova虚拟机
启动源码实现(1)
Nova通过独⽴的软件管理模块实现XenServer、Hyper-V和VMWare ESX的调⽤与管理,同时对于其他的Hypervisor,如KVM、LXC、QEMU、UML和Xen则是通过Libvirt标准接⼝统⼀实现,其中KVM是Nova-Compute中Libvirt默认调⽤的底层虚拟化平台。为了更好地理解在Nova环境下Libvirt是如何管理底层的Hypervisor,先要基本了解Libvirt的体系架构与实现⽅法。这部分详细内容请见另外⼀篇博⽂
Libvirt是⼀种实现虚拟化平台能⼒交互的⼯具集,它为所⽀持的Hypervisor提供了⼀种通⽤的API接⼝套件,上层管理平台(如Nova)通过Libvirt来实现对虚拟机的⽣命周期管理。Libvirt当前⽀持以下底层虚拟化平台:
KVM:Linux平台仿真器;
QEMU:⾯向各种架构的平台仿真器;
Xen:⾯向IA-32、IA-64和PowerPC970架构的虚拟机监控程序;
LXC:⽤于操作系统虚拟化的Linux(轻量级)容器;
OpenVZ:基于Linux内核的操作系统级虚拟化;
User Mode Linux:⾯向各种架构的Linux平台仿真器;
VirtualBox:x86虚拟化虚拟机监控程序;
ESX、GSX:VMW爱热企业级虚拟化平台;
VMWare Workstation、VMWare Player:VMWare⽤户级虚拟化平台;
Hyper-V:Microsoft虚拟化平台。
另外,Libvirt⽀持以Bridging、NAT、VEPA和VN-LINK⽅式构建虚拟⽹络,以及⽀持基于不同制式的IDE/SCSI/USB disks、FibreChannel、LVM、iSCSI、NFS和filesystems存储。因此,Libvirt在功能性、兼容性以及管理等⽅⾯的优势⼗分明显。
Libvirt对底层虚拟化平台的调⽤与管理有两种⽅式:本地管理与远程管理。其中,本地管理模式下、上层管理平台系统、Libvirt、虚拟化平台以及客户虚拟机均不属在同⼀物理主机节点之上;远程管理模式下,上层管理平台系统与底层虚拟化平台以及客户虚拟机分别不属在不同的物理主机节点上。当所有的组件部署在同⼀物理节点时,上层管理平台系统通过Libvirt ⼯作,以控制本地域中的所有底层虚拟
化软件,该种⽅式在安全性、可靠性以及可扩展性⽅⾯存在⼀定弊端。因此,相⽐⽽⾔基于Libvirt的远程控制模式可以较好地解决本地管理模式所遇到的问题。
在Libvirt环境下,⼀台虚拟机随着⽤户需求的改变可能会经历如下状态:
Undefined:⼀台虚拟机的初始化状态,Libvirt对处于该状态的Guest Domain不执⾏任何创建或定义的操作;
Defined:只有持久类型的Domain存在本状态,Guest Domain已经被创建;
Running:基于某种Hypervisor的Guest Domain虚拟机已经正常运⾏,可被客户控制并操作;
Paused:Hypervisor上对该Guest Domain虚拟机执⾏挂起操作,状态被临时存储直⾄恢复;
Saved:类似于Paused状态,不同的是Guest Domain虚拟机的相关状态与数据被永久存储,当接收到客户请求恢复的指令之后,处于Saved状态下的Guest Domain虚拟机被恢复。
以上简要描述了Libvirt相关的管理能⼒与技术特征,Nova基于Libvirt在功能⽅⾯实现与底层虚拟化平台的⽆缝兼容。Nova 对于底层Hypervisor(如KVM/QEMU等)的调⽤与管理主要通过LibvirtDriver类(见Nova源代码“nova/virt/libvirt/driver.py”)来实现,该类与类XenAPIDriver、VMwareESXDriver、HyperVDriver类似,均是ComputeDriver基类的⼀个特殊实
现。LibvirtDriver类通过“_conn = property(_get_connection)”语句定义了⼀个连接属性,在执⾏虚拟机管理相关的操作时,调⽤LibvirtDriver实例的这个属性的同时,就意味着执⾏_get_connection⽅法,打开底层Hypervisor的连接。
Libvirt对Hypervisor的连接有两种⽅式:⼀种是只读式;另⼀种是认证式。只读式的连接只能对Hypervisor进⾏读访问,所允许的API调⽤集合有限,⽐较适合监控性的应⽤程序;认证式的连接能够经过认证对Hypervisor进⾏读写访问,但是需要包含⼀个认证参数才能执⾏相关的写操作。LibvirtConnection类中虽然能根据“ReadOnly”条件来选择其中⼀种⽅式,但是实际执⾏Nova时,由于“ReadOnly”参数的默认值是False,并且在建⽴Libvirt链接时并⽆修改,因此系统连接⽅式是认证是连接。Nova通过Libvirt建⽴于底层Hypervisor认证式连接的关键语句是“libvirt.openAuth(uri, auth, 0)”。其中,Libvirt作为⼀个Python模块,通过它能够调⽤Libvirt库中与Hypervisor相关的管理API;“uri”参数主要是连接到Hypervisor的⼊⼝地址,如系统要求连接到本地的Xen Hypervisor,其本地URI是“xen:///”;“auth”参数是提供相应的认证信息,进⼀步实现Libvirt对Hypervisor的读写操作。
以下选取创建虚拟机的进程具体说明Nova如何基于Libvirt实现对底层虚拟化平台的调⽤与管理。在LibvirtConnection类中,通过“spawn”⽅法来实现虚拟机的创建,“spawn”调⽤⽅法_create_domain_and_network来启动⼀个新的Domain,在⽅法_create_domain_and_network中,实际与Hypervisor建⽴连接的语句是“domain = self._conn.defineXML(xml)”;在建⽴与Hypervisor连接的
基础上执⾏defineXML⽅法,创建并定义⼀个Domain,但是此时的Domain还未启动,必须执
⾏“ateWithFlags(launch_flags)”语句来启动之前定义好的虚拟机Domain。
下⾯来详细的解析⽅法spawn,来看看OpenStack Nova中是如何应⽤Libvirt库来启动虚拟机的。
我们来看⽅法spawn:
def spawn(self, context, instance, image_meta, injected_files,
admin_password, network_info=None, block_device_info=None):
"""
# 调⽤之⼀传进来的参数:
# context:上下⽂信息;
# instance:实例信息;
# image_meta:从glance获取的镜像⽂件image的元数据;
# injected_files:编码后的注⼊⽂件;
# admin_password:admin密码;
# network_info=self._legacy_nw_info(network_info):转换为传统格式的⽹络资源信息;
# block_device_info:实例错误记录的块设备;
"""
# get_disk_info:确定来宾系统的磁盘映射信息;
# 返回disk_bus、cdrom_bus和mapping的值这些磁盘映射信息给disk_info;;
# CONF.libvirt_type:这个参数定义了libvirt的域名类型,参数的默认值为'kvm';
# get_disk_info返回值:
# return {'disk_bus': disk_bus,    # 获取disk类型的磁盘总线;
#        'cdrom_bus': cdrom_bus,  # 获取cdrom类型的磁盘总线;
#        'mapping': mapping}      # 确定怎样映射从默认的磁盘到虚拟机,返回客户对于设备的磁盘映射;
disk_info = _disk_info(CONF.libvirt_type,
instance,
block_device_info,
image_meta)
# to_xml:为新建⽴的实例参数获取配置数据conf,并把获取的数据conf转换为xml格式;
# instance:实例信息;
# network_info:转换为传统格式的⽹络资源信息;
python虚拟机
# disk_info:来宾系统的磁盘映射信息;
# image_meta:从glance获取的镜像⽂件image的元数据;
# block_device_info:实例错误记录的块设备;
xml = _xml(instance, network_info,
disk_info, image_meta,
block_device_info=block_device_info)
# _create_image:建⽴虚拟机实例镜像;
# context:上下⽂信息;
# instance:实例信息;
# xml:为新建⽴的实例参数获取配置数据conf,并把获取的数据conf转换为xml格式;
# disk_info['mapping']:来宾系统磁盘的映射信息;
# network_info=network_info:转换为传统格式的⽹络资源信息;
# block_device_info=block_device_info:实例错误记录的块设备;
# files=injected_files:编码后的注⼊⽂件;
# admin_pass=admin_password:admin密码;
# admin_pass=admin_password:admin密码;
self._create_image(context, instance, xml,
disk_info['mapping'],
network_info=network_info,
block_device_info=block_device_info,
files=injected_files,
admin_pass=admin_password)
# 执⾏所需⽹络的安装以及建⽴域;
self._create_domain_and_network(xml, instance, network_info,block_device_info)
LOG.debug(_("Instance is running"), instance=instance)
def _wait_for_boot():
"""
在固定时间间隔调⽤,检测虚拟机启动状态,直到虚拟机成功运⾏;
"""
state = _info(instance)['state']
if state == power_state.RUNNING:
LOG.info(_("Instance spawned successfully."),instance=instance)
raise utils.LoopingCallDone()
# _wait_for_boot:在固定时间间隔调⽤,检测虚拟机启动状态,直到虚拟机成功运⾏;
# 以⼀定的时间间隔(0.5)循环调⽤_wait_for_boot⽅法;
timer = utils.FixedIntervalLoopingCall(_wait_for_boot)
timer.start(interval=0.5).wait()
⾸先来看⽅法get_disk_info,这个⽅法实现了确定来宾系统的磁盘映射信息,返回disk_bus、cdrom_bus和mapping的值这些磁盘映射信息给disk_info。具体的⽅法解析见⽅法中的注释信息:
def get_disk_info(virt_type, instance, block_device_info=None,
image_meta=None, rescue=False):
"""
确定来宾系统的磁盘映射信息;
返回disk_bus、cdrom_bus和mapping的值;
# 调⽤之⼀传进来的参数;
# virt_type=CONF.libvirt_type:这个参数定义了libvirt的域名类型,参数的默认值为'kvm';
# instance:实例信息;
# block_device_info:实例错误记录的块设备;
# image_meta:从glance获取的镜像⽂件image的元数据;
# rescue=False;
"""
# get_disk_bus_for_device_type:获取虚拟机设备最佳的磁盘总线接⼝类型信息;
# ⾸先尝试根据虚拟机元数据获取虚拟机磁盘总线接⼝类型,并判断它和虚拟机类型是否正确对应;
# 如果成功获取虚拟机的磁盘总线接⼝类型,并且验证正确,直接返回它;
# 如果在虚拟机元数据中没有获取到磁盘总线接⼝类型信息,则根据虚拟机类型,直接获取对应的最佳的磁盘总线接⼝类型值,并返回它;
# 考虑到当前配置的虚拟化类型,为给定的设备类型返回最佳的disk_bus(磁盘总线);
# ⽐如,对于KVM的disk,它会返回virtio,⽽对KVM的cdrom,它会返回ide;
# virt_type:定义了libvirt的域名类型,参数的默认值为'kvm';
# image_meta:从glance获取的镜像⽂件image的元数据;
# 获取disk类型的磁盘总线;
disk_bus = get_disk_bus_for_device_type(virt_type, image_meta, "disk")
# 获取cdrom类型的磁盘总线;
cdrom_bus = get_disk_bus_for_device_type(virt_type, image_meta, "cdrom")
# get_disk_mapping:确定怎样映射从默认的磁盘到虚拟机;
# 这⾥是计算出默认的'disk','disk.local', 'disk.swap'和'fig'镜像是否已经被块设备映射重写;
# 返回客户对于设备的磁盘映射;
# virt_type:定义了libvirt的域名类型,参数的默认值为'kvm';
# instance:实例信息;
# disk_bus:获取得到的disk类型的磁盘总线;
# cdrom_bus:获取得到的cdrom类型的磁盘总线;
# block_device_info:实例错误记录的块设备;
# image_meta:从glance获取的镜像⽂件image的元数据;
# rescue=False;
mapping = get_disk_mapping(virt_type, instance,
disk_bus, cdrom_bus,
block_device_info,
image_meta, rescue)
return {'disk_bus': disk_bus,    # 获取disk类型的磁盘总线;
'cdrom_bus': cdrom_bus,  # 获取cdrom类型的磁盘总线;
'mapping': mapping}      # 确定怎样映射从默认的磁盘到虚拟机,返回客户对于设备的磁盘映射;
接下来调⽤⽅法to_xml实现为新建⽴的虚拟机实例获取参数配置数据conf,并把获取的配置数据conf转换为xml格式⽂件,⽅法的具体实现如下:
def to_xml(self, instance, network_info, disk_info,
image_meta=None, rescue=None,
block_device_info=None, write_to_disk=False):
"""
为新建⽴的实例参数获取配置数据conf,并把获取的数据conf转换为xml格式;
# 调⽤之⼀传进来的参数:
# instance:实例信息;
# network_info:转换为传统格式的⽹络资源信息;
# disk_info:来宾系统的磁盘映射信息;
# image_meta:从glance获取的镜像⽂件image的元数据;
# rescue=None;
# block_device_info:实例错误记录的块设备;
# write_to_disk=False;
"""
LOG.debug(_("Start to_xml instance=%(instance)s "
"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") %
locals())
# get_guest_config:为新建的实例参数获取配置数据;
# instance:实例信息;
# network_info:转换为传统格式的⽹络资源信息;
# image_meta:从glance获取的镜像⽂件image的元数据;
# disk_info:来宾系统的磁盘映射信息;
# rescue=None;
# block_device_info:实例错误记录的块设备;
conf = _guest_config(instance, network_info, image_meta, disk_info, rescue, block_device_info)
# 调⽤to_xml把conf(实例的配置数据)⽣成xml格式⽂件;
xml = _xml()
#注:这⾥还不知道具体的是什么意思;
if write_to_disk:
# 确定实例存储的正确的路径;
instance_dir = _instance_path(instance)
# 确定xml⽂件路径,⽂件名称为l;
xml_path = os.path.join(instance_dir, 'l')
# 写前⾯获得的xml⽂件内容到l之中;
libvirt_utils.write_to_file(xml_path, xml)
LOG.debug(_('End to_xml instance=%(instance)s xml=%(xml)s') % locals())
return xml
接下来调⽤⼀个⽐较重要的⽅法_create_image来实现虚拟机镜像的建⽴,需要注意的是这个⽅法中还没有启动所建⽴的虚拟机实例。代码的详细解析请见。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。