热门标签 | 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;这块没有接触过。

推荐阅读
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • HDFS2.x新特性
    一、集群间数据拷贝scp实现两个远程主机之间的文件复制scp-rhello.txtroothadoop103:useratguiguhello.txt推pushscp-rr ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
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社区 版权所有