热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

虚拟机创建流程libvirt篇(上)

虚拟机创建流程-libvirt篇(上)-社区博客-网易数帆libvirt的架构libvirt是CS架构应用,用户通过client与se

虚拟机创建流程-libvirt篇(上)-社区博客-网易数帆


libvirt的架构

libvirt是CS架构应用,用户通过client与server交互,server与client通过socket连接通信。基本架构图如下所示:

 


  • libvirt分为client和deamon两个部分
  • libvirt deamon中还包含了rpc,acl,事件机制,线程池等公共组件。基于rpc可以实现libvirt remote client对本地虚拟机的操作。acl实现了访问控制标签。事件机制是libvirt所有动作的基础,所有的请求,消息转发,事件触发都是通过事件机制传递的。
  • libvirt deamon中通过事件机制监听某个端口的消息。client发出的请求会通过socket连接发送到libvirt api。
  • libvirt deamon在启动时会加载部署的hypervisor驱动,libvirt api接收到的请求会路由到conn对象指定的驱动程序中。
  • 驱动程序接收到转发的请求之后会与hypervisor交互实现对虚拟机的具体操作。
  • libvirt中目前实现了多种hypervisor的驱动,其中qemu_driver对应kvm,lxc对应容器。
  • 对于kvm而言,一个虚拟机对应一个qemu进程。qemu进程通过软件模拟计算机的主板,CPU,南北桥及内存设备。虚拟机操作系统就运行在qemu进程内。
  • libvirt独立实现了lxc driver来管理容器。lxc driver启动一个独立的进程并使用这个进程拉起一个init子进程,这个子进程有其独立的namespace并与cgroup结合实现了容器资源的隔离和限制。

在libvirt中接口的调用方式分为两种:

 


  • 远程调用


  • 本地调用

 


从nova到libvirt
openstack是基于Python实现的,而libvirt是基于C实现的。那么C和Python之间是如何转换的呢。下面以启动虚拟机实例来看一下在openstack中如何调用libvirt接口: 
  1. import python-libvirt库

if libvirt is None:libvirt = __import__('libvirt')

  1. 通过openAuth获取与libvirtd进程的连接conn

return tpool.proxy_call((libvirt.virDomain, libvirt.virConnect),libvirt.openAuth, uri, auth, flags)

  1. 调用define接口创建一个虚拟机实例,获取domain对象

domain = self._conn.defineXML(xml)

  1. 通过domain对象启动虚拟机实例

domain.createWithFlags(launch_flags)

由这个流程我们可以看到,openstack中主要通过python-libvirt库与libvirtd进程交互,完成对虚拟机实例的操作。python-libvirt是由libvirt提供的一个面向python client的连接组件,包含以下内容:

/usr/share/pyshared/libvirt.py #libvirt python接口文件,包含大部分的libvirt接口
/usr/share/pyshared/libvirt_lxc.py #lxc接口文件,因为这部分接口参数不能自动转换,所以通过手动重写完成转换
/usr/share/pyshared/libvirt_qemu.py #与上面的类似,qemu相关的。
/usr/lib/python2.7/dist-packages/libvirtmod_qemu.so
/usr/lib/python2.7/dist-packages/libvirtmod_lxc.so
/usr/lib/python2.7/dist-packages/libvirtmod.so

在libvirt代码中有一个专门的目录用于存放接口python化相关的代码。所有的libvirt接口被分为了两个部分:

  1. 可以直接自动转换的接口,使用generator.py直接封装python接口
  2. 无法直接自动转换的接口,通过libvirt-override.c等文件对C接口做一层封装再封装python接口。 libvirt-python工程会将未重写和重写过的接口编译到一个动态库中,并且和生成的py文件一起打包到python-libvirt包中。然后我们就可以通过引入这个python库的方式调用libvirt的C接口了。


libvirt的接口调用流程

下面继续以创建虚拟机为例说明libvirt中接口调用的流程


  1. libvirt中接收xml格式定义的虚拟机实例配置,nova通过defineXML接口定义虚拟机。该接口返回一个虚拟机的domain对象,用户接下来可以通过这个对象操作虚拟机。

domain = self._conn.defineXML(xml)

  1. 第一步只是执行了定义操作,相当于libvirt开始管理这台虚拟机。但是此时实际的虚拟机还没有运行,用户还无法使用。nova中调用domain.createWithFlags(launch_flags)接口,用第一步中定义的虚拟机规格在hypervisor层把虚拟机真正创建起来。

  2. createWithFlags调用python-libvirt封装的virDomainCreateWithFlags

def createWithFlags(self, flags=0):ret = libvirtmod.virDomainCreateWithFlags(self._o, flags)if ret == -1: raise libvirtError ('virDomainCreateWithFlags() failed', dom=self)return ret

  1. 在python-libvirt中,createWithFlags接口是直接封装的,参数不需要转换。下一步会在转换中调用到libvirt.c中的virDomainCreateWithFlags接口,由此进入libvirt api层。 传入的flag值为0,flag取值范围及对应含义如下:

VIR_DOMAIN_NONE = 0, /* Default behavior */
VIR_DOMAIN_START_PAUSED &#61; 1 <<0, /* Launch guest in paused state */
VIR_DOMAIN_START_AUTODESTROY &#61; 1 <<1, /* Automatically kill guest when virConnectPtr is closed */
VIR_DOMAIN_START_BYPASS_CACHE &#61; 1 <<2, /* Avoid file system cache pollution */
VIR_DOMAIN_START_FORCE_BOOT &#61; 1 <<3, /* Boot, discarding any managed save */

int
virDomainCreateWithFlags(virDomainPtr domain, unsigned int flags) {virConnectPtr conn;VIR_DOMAIN_DEBUG(domain, "flags&#61;%x", flags);virResetLastError();#重置错误码。#libvirt中采用了线程池机制&#xff0c;每次从线程池中取出一个线程执行当前的请求。#线程中会保存当前线程最后产生的错误码&#xff0c;因此在请求最开始的位置就要把原有的错误重置&#xff0c;防止误报。#合法性检查&#xff0c;传入的domain指针及其中的conn指针是否为正确的类型。if (!VIR_IS_CONNECTED_DOMAIN(domain)) {virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__);virDispatchError(NULL);return -1;}#获取domain中的conn指针&#xff0c;如果conn是只读的&#xff0c;则设置错误码并直接退出。因为创建虚拟机属于修改操作。conn &#61; domain->conn;if (conn->flags & VIR_CONNECT_RO) {virLibDomainError(VIR_ERR_OPERATION_DENIED, __FUNCTION__);goto error;}#从这里跳转到具体的driver中执行。驱动在libvirtd启动的时候加载&#xff0c;映射关系由conn指针初始化的时候指定。在配置文件中可以配置默认的conn driver&#xff0c;也可以在创建conn的时候通过接口参数指定。if (conn->driver->domainCreateWithFlags) {int ret;ret &#61; conn->driver->domainCreateWithFlags(domain, flags);if (ret <0)goto error;return ret;}#如果驱动中没有实现对应的方法&#xff0c;直接报no support错误。virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__);error:virDispatchError(domain->conn);return -1;
}

  1. libvirt中每一个driver都有一张映射关系表&#xff0c;用于对应driver中的函数指针和具体的driver函数。第4步中从api映射到了具体的driver。在qemu_driver.c中查找该函数。

static int
qemuDomainCreateWithFlags(virDomainPtr dom, unsigned int flags)
{virQEMUDriverPtr driver &#61; dom->conn->privateData;virDomainObjPtr vm;int ret &#61; -1;#首先检查传入flag参数的合法性&#xff0c;必须是上面提到的几个可选值之一。这里是一个宏来实现的&#xff0c;如果出错直接返回-1。virCheckFlags(VIR_DOMAIN_START_PAUSED |VIR_DOMAIN_START_AUTODESTROY |VIR_DOMAIN_START_BYPASS_CACHE |VIR_DOMAIN_START_FORCE_BOOT, -1);#获取虚拟机的vm指针if (!(vm &#61; qemuDomObjFromDomain(dom)))return -1;#访问控制&#xff0c;判断当前conn是否有权限执行该操作。目前配置的访问控制标签默认为None&#xff0c;即所有用户都有最高权限。if (virDomainCreateWithFlagsEnsureACL(dom->conn, vm->def) <0)goto cleanup;#获取虚拟机job锁&#xff0c;类型为MODIFY&#xff0c;可选类型如后所示。只有获得该锁才能继续执行。if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) <0)goto cleanup;#检查虚拟机是否已经处于运行状态if (virDomainObjIsActive(vm)) {virReportError(VIR_ERR_OPERATION_INVALID,"%s", _("domain is already running"));goto endjob;}#启动虚拟机if (qemuDomainObjStart(dom->conn, driver, vm, flags) <0)goto endjob;ret &#61; 0;endjob:#该job是同步操作&#xff0c;任务结束之后要释放job锁。if (!qemuDomainObjEndJob(driver, vm))vm &#61; NULL;cleanup:if (vm)virObjectUnlock(vm);return ret;
}

  • qemu job的类型

QEMU_JOB_NONE &#61; 0, /* Always set to 0 for easy if (jobActive) conditions */
QEMU_JOB_QUERY, /* Doesn&#39;t change any state */
QEMU_JOB_DESTROY, /* Destroys the domain (cannot be masked out) */
QEMU_JOB_SUSPEND, /* Suspends (stops vCPUs) the domain */
QEMU_JOB_MODIFY, /* May change state */
QEMU_JOB_ABORT, /* Abort current async job */
QEMU_JOB_MIGRATION_OP, /* Operation influencing outgoing migration *//* The following two items must always be the last items before JOB_LAST */
QEMU_JOB_ASYNC, /* Asynchronous job */
QEMU_JOB_ASYNC_NESTED, /* Normal job within an async job */QEMU_JOB_LAST
};

  1. 第5步中调用到了qemuDomainObjStart,这个函数处理了虚拟机wakeup的逻辑并且在虚拟机启动成功之后发送事件通知。

static int
qemuDomainObjStart(virConnectPtr conn, virQEMUDriverPtr driver, virDomainObjPtr vm, unsigned int flags)
{int ret &#61; -1;char *managed_save;#根据传入的参数确定虚拟机的启动模式bool start_paused &#61; (flags & VIR_DOMAIN_START_PAUSED) !&#61; 0;bool autodestroy &#61; (flags & VIR_DOMAIN_START_AUTODESTROY) !&#61; 0;bool bypass_cache &#61; (flags & VIR_DOMAIN_START_BYPASS_CACHE) !&#61; 0;bool force_boot &#61; (flags & VIR_DOMAIN_START_FORCE_BOOT) !&#61; 0;unsigned int start_flags &#61; VIR_QEMU_PROCESS_START_COLD;start_flags |&#61; start_paused ? VIR_QEMU_PROCESS_START_PAUSED : 0;start_flags |&#61; autodestroy ? VIR_QEMU_PROCESS_START_AUTODESTROY : 0;#组装hibernate文件的路径managed_save &#61; qemuDomainManagedSavePath(driver, vm);if (!managed_save)goto cleanup;#如果存在hibernate文件&#xff0c;则从该文件恢复虚拟机if (virFileExists(managed_save)) {#启动时可以指定强制启动&#xff0c;此时移除hibernate文件并按照正常流程启动虚拟机if (force_boot) {if (unlink(managed_save) <0) {virReportSystemError(errno,_("cannot remove managed save file %s"),managed_save);goto cleanup;}vm->hasManagedSave &#61; false;} else {#从hibernate文件恢复虚拟机&#xff0c;因为我们目前还不支持内存快照的功能&#xff0c;暂时不跟进了。ret &#61; qemuDomainObjRestore(conn, driver, vm, managed_save,start_paused, bypass_cache);#恢复成功&#xff0c;移除suspend文件if (ret &#61;&#61; 0) {if (unlink(managed_save) <0)VIR_WARN("Failed to remove the managed state %s", managed_save);elsevm->hasManagedSave &#61; false;}#如果恢复失败&#xff0c;则忽略suspend文件直接按正常流程启动虚拟机if (ret > 0)VIR_WARN("Ignoring incomplete managed state %s", managed_save);elsegoto cleanup;}}#启动qemu进程ret &#61; qemuProcessStart(conn, driver, vm, NULL, -1, NULL, NULL,VIR_NETDEV_VPORT_PROFILE_OP_CREATE, start_flags);#虚拟机启动完成之后&#xff0c;验证对应的启动参数&#xff0c;并且在/var/run/libvirt/qemu目录下保存一份运行状态的配置文件&#xff0c;这个文件的内容在虚拟机配置改变的时候会随之改变&#xff0c;virDomainAuditStart(vm, "booted", ret >&#61; 0);if (ret >&#61; 0) {#向事件队列发送虚拟机启动事件。如果此时有程序在监听此事件就会收到相应的通知。virDomainEventPtr event &#61;virDomainEventNewFromObj(vm,VIR_DOMAIN_EVENT_STARTED,VIR_DOMAIN_EVENT_STARTED_BOOTED);if (event) {qemuDomainEventQueue(driver, event);#如果指定了启动之后pause虚拟机&#xff0c;同时还要发送一个虚拟机pause事件。if (start_paused) {event &#61; virDomainEventNewFromObj(vm,VIR_DOMAIN_EVENT_SUSPENDED,VIR_DOMAIN_EVENT_SUSPENDED_PAUSED);if (event)qemuDomainEventQueue(driver, event);}}}cleanup:VIR_FREE(managed_save);return ret;
}

  1. 接下来我们来分析一下qemuProcessStart函数&#xff0c;这个函数处理qemu进程启动的主逻辑流程。由于这个函数中逻辑比较长&#xff0c;就不直接贴代码了&#xff0c;只选取其中关键部分了解一下。

  • 首先&#xff0c;老规矩检查输入参数。

virCheckFlags(VIR_QEMU_PROCESS_START_COLD |VIR_QEMU_PROCESS_START_PAUSED |VIR_QEMU_PROCESS_START_AUTODESTROY, -1);

  • 再次检查虚拟机是否处于运行状态。在api中检查的时候并未持有job锁&#xff0c;虚拟机可能正在执行启动操作。在拿到虚拟机job锁后做最后一次检查&#xff0c;如果没有启动则可以保证在本次启动过程中不会有其他的启动操作了。

if (virDomainObjIsActive(vm)) {virReportError(VIR_ERR_OPERATION_INVALID,"%s", _("VM is already active"));virObjectUnref(cfg);return -1;}

  • 复制xml文件下发的配置&#xff0c;作为虚拟机的在线配置。在libvirt中&#xff0c;虚拟机配置分为在线配置和离线配置两种。在线配置记录在内存中&#xff0c;与虚拟机实时状态保持一致&#xff08;比如执行网卡热插拔之后&#xff0c;在线配置也会同步更新&#xff09;。离线配置则作为一个持久化配置记录在宿主机磁盘上&#xff0c;虚拟机关机之后仍然存在直到虚拟机被undefine&#xff0c;下一次启动的时候使用该配置。离线插拔设备等操作会更新离线配置信息&#xff0c;虚拟机关机的时候也会把在线配置更新到离线配置中。

if (virDomainObjSetDefTransient(caps, driver->xmlopt, vm, true) <0)goto cleanup;

  • 获取虚拟机vm-id。这个ID与nova中的instance uuid不是一回事&#xff0c;仅有运行状态的虚拟机有这个ID。宿主机唯一&#xff0c;宿主机重启之后会重新计算。

vm->def->id &#61; qemuDriverAllocateID(driver);

  • 设置虚拟机的fakereboot标志位&#xff0c;正常reboot虚拟机的时候&#xff0c;qemu进程会被kill掉并重新启动。而如果fakereboot被设置为true时&#xff0c;只是重置当前qemu进程。

qemuDomainSetFakeReboot(driver, vm, false);

  • 设置虚拟机状态。libvirt中有一套虚拟机状态管理机制&#xff0c;分为stat和reason。并提供了相应的查询接口&#xff0c;可以查询虚拟机当前状态以及进入当前状态的原因。

virDomainObjSetState(vm, VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_UNKNOWN);

  • 执行hook脚本。libvirt提供了hook机制&#xff0c;允许用户在某些事件发生时执行预先自定义的脚本文件。目前我们的默认配置均为空。

if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) {char *xml &#61; qemuDomainDefFormatXML(driver, vm->def, 0);int hookret;hookret &#61; virHookCall(VIR_HOOK_DRIVER_QEMU, vm->def->name,VIR_HOOK_QEMU_OP_PREPARE, VIR_HOOK_SUBOP_BEGIN,NULL, xml, NULL);VIR_FREE(xml);/** If the script raised an error abort the launch*/if (hookret <0)goto cleanup;}

  • 获取宿主机上安装的qemu支持的特性列表&#xff0c;用于后续对虚拟机执行某些操作时判断兼容性。

if (!(priv->qemuCaps &#61; virQEMUCapsCacheLookupCopy(driver->qemuCapsCache,vm->def->emulator)))goto cleanup;

  • 预处理配置文件中的虚拟设备。

#处理配置文件中的直通网卡。虽然在配置文件中指定设备类型为interface&#xff0c;但是实际上直通网卡还是一个PCI设备&#xff0c;因此将其加入hostdev设备中。if (qemuNetworkPrepareDevices(vm->def) <0)goto cleanup;#处理直通设备。直通设备分为三类&#xff1a;PCI设备&#xff0c;USB设备及scsi设备。#PCI设备的处理逻辑比较复杂&#xff0c;大致流程为# - 检查配置的PCI设备是否已经直通到其他虚拟机# - 移除这些设备的原有驱动# - 重置这些设备# - 对于SRIOV的网卡直通设备&#xff0c;需要额外设置一些网络相关的参数# - 在qemu驱动中将这些设备设置为active状态# - 在qemu驱动的未启用设备列表中移除这些设备# - 在qemu驱动中记录当前使用这些设备的虚拟机# - 记录这些设备的原始状态# - 从host上隐藏这些设备#经过以上处理之后&#xff0c;配置的PCI设备就可以作为一个普通的虚拟机设备供虚拟机使用了。#对于USB直通设备不需要这么复杂&#xff0c;只要确保设备存在并且在qemu驱动中记录使用这些设备的虚拟机。#if (qemuPrepareHostDevices(driver, vm->def, priv->qemuCaps,!migrateFrom) <0)goto cleanup;#处理字符设备&#xff0c;包括serial&#xff0c;parallels&#xff0c;channel&#xff0c;console等设备类型&#xff0c;主要是检查这些设备是否存在if (virDomainChrDefForeach(vm->def,true,qemuProcessPrepareChardevDevice,NULL) <0)goto cleanup;

  • 安全相关的&#xff0c;这块没有接触过。

推荐阅读
  • Android 中的布局方式之线性布局
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 入门指南:使用FastRPC技术连接Qualcomm Hexagon DSP
    本文旨在为初学者提供关于如何使用FastRPC技术连接Qualcomm Hexagon DSP的基础知识。FastRPC技术允许开发者在本地客户端实现远程调用,从而简化Hexagon DSP的开发和调试过程。 ... [详细]
  • 本文探讨了如何将Python对象转换为字节流,以实现文件保存、数据库存储或网络传输的需求。主要介绍了利用pickle模块进行序列化的具体方法。 ... [详细]
  • 本文将详细探讨 Python 编程语言中 sys.argv 的使用方法及其重要性。通过实际案例,我们将了解如何在命令行环境中传递参数给 Python 脚本,并分析这些参数是如何被处理和使用的。 ... [详细]
  • Zabbix自定义监控与邮件告警配置实践
    本文详细介绍了如何在Zabbix中添加自定义监控项目,配置邮件告警功能,并解决测试告警时遇到的邮件不发送问题。 ... [详细]
  • 函子(Functor)是函数式编程中的一个重要概念,它不仅是一个特殊的容器,还提供了一种优雅的方式来处理值和函数。本文将详细介绍函子的基本概念及其在函数式编程中的应用,包括如何通过函子控制副作用、处理异常以及进行异步操作。 ... [详细]
  • 如何在Django框架中实现对象关系映射(ORM)
    本文介绍了Django框架中对象关系映射(ORM)的实现方式,通过ORM,开发者可以通过定义模型类来间接操作数据库表,从而简化数据库操作流程,提高开发效率。 ... [详细]
  • 本文探讨了如何在Python中将具有相同值的元素分组到矩阵中,这是一个在数据分析和处理中常见的需求。 ... [详细]
  • JUnit下的测试和suite
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • Requests库的基本使用方法
    本文介绍了Python中Requests库的基础用法,包括如何安装、GET和POST请求的实现、如何处理Cookies和Headers,以及如何解析JSON响应。相比urllib库,Requests库提供了更为简洁高效的接口来处理HTTP请求。 ... [详细]
  • 本文介绍了如何通过C#语言调用动态链接库(DLL)中的函数来实现IC卡的基本操作,包括初始化设备、设置密码模式、获取设备状态等,并详细展示了将TextBox中的数据写入IC卡的具体实现方法。 ... [详细]
  • OBS Studio自动化实践:利用脚本批量生成录制场景
    本文探讨了如何利用OBS Studio进行高效录屏,并通过脚本实现场景的自动生成。适合对自动化办公感兴趣的读者。 ... [详细]
  • Web动态服务器Python基本实现
    Web动态服务器Python基本实现 ... [详细]
  • 在OpenCV 3.1.0中实现SIFT与SURF特征检测
    本文介绍如何在OpenCV 3.1.0版本中通过Python 2.7环境使用SIFT和SURF算法进行图像特征点检测。由于这些高级功能在OpenCV 3.0.0及更高版本中被移至额外的contrib模块,因此需要特别处理才能正常使用。 ... [详细]
  • Jenkins API当前未直接提供获取任务构建队列长度的功能,因此需要通过解析HTML页面来间接实现这一需求。 ... [详细]
author-avatar
阡蓝fliona
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有