1. 前言
云计算离不开虚拟化,虚拟化绕不开libvirt。
因为开发虚拟机监控程序,花了一些时间学习libvirt, 这是一篇用来熟悉libvirt的博客。
2. libvirt
按照官方主页的介绍,libvirt项目是:
- 一套用于管理虚拟化平台的工具
- 支持C、Python、Perl、Java及其他语言
- 使用开源license
- 支持KVM、QEMU、Xen、Virtuozzo、VMWare ESX、LXC、BHyve及其他驱动
- 支持Linux、FreeBSD、Windows及macOS等操作系统
- 被许多应用使用
在Linux平台上以C/S的形式存在,libvirtd程序作为服务端管理虚拟机,接收和解析客户端请求,客户端可以是virsh命令行工具,也可以是基于libvirt库自行开发的程序。
架构如下,图片来自维基百科:
可以看到libvirt对用户屏蔽了底层hypervisor,向上提供了统一的接口。
2.1 为什么不直接使用KVM
笔者在接触libvirtd前,一直都是直接使用qemu-kvm来玩虚拟机的,一般配置好参数和脚本后手动操作,控制虚拟机起停。
qemu本身也是一个模拟器,在Linux下可以利用kvm模块加速虚拟化,但qemu只是一个命令行工具,且参数多如牛毛,在用kvm调试安装黑苹果成功后,还是需要配置libvirtd实现开机自启动。
如果需要实现可编程的的虚拟机操作,我们需要:
- 定义结构化的数据来虚拟机配置
- 维护虚拟机的生命周期,包括创建、启动、停止、删除,并处理异常情况
- 本地的Daemon维护虚拟机,向服务端提供API
开发和维护的工作量都不小,而libvirt不止做到这些,还提供一套虚拟化管理方案。
2.2 从virsh命令看libvirt
由于libvirt主要使用者都是云厂商的开发者,技术的应用者也不太可能公开分享实践经验。因此笔者在入门时从virsh命令开始,熟悉通过virsh命令操作虚拟机后,再从API库中寻找对应的接口完成功能。
virsh帮助命令输出
|
|
在virsh的帮助命令中,所有的命令被划分为以下几类:
- domain:虚拟机管理命令,domain对应虚拟机,libvirt使用xml描述虚拟机配置。需要首先通过define将虚拟机导入libvirtd,然后才能执行start、shutdown、destroy等开机、关机、强制关机操作,domain相关的命令接收ID或Name作为参数(在每台宿主机上需唯一)。domain相关命令对应API中Domain对象的方法集合,我们首选需要获取到一个domain引用,然后才能执行操作。
- monitor:监控命令,可获取domain的配置、运行状态、统计数据等。
- host:宿主机管理命令,用于获取和操作宿主机。
- interface:网卡管理命令,比较少用,一般自行管理网络,使用VXLAN、Bridge、TUN/TAP将虚拟机接入用户网络。
- filter:防火墙管理命令,可读取xml格式定义等防火墙规则,按虚拟机网卡配置防火墙。
- network:网络相关命令,这里指的是libvirt的网络,默认使用Bridge和TUN/TAP将虚拟机加入宿主机网络,使用DHCP和iptables为虚拟机提供网络。
- nodedev:宿主机设备管理命令,可将宿主机设备,例如GPU、USB、PCIe设备等分配给虚拟机。
- secret:密钥管理命令。
- snapshot:快照管理命令。
- pool:存储池管理命令,一个存储池内可存放多个卷。
- volume:数据卷管理命令。
virsh命令基于libvirt库开发的,因为libvirt有着良好的API架构,熟悉virsh命令对使用API有很大的帮助。
2.3 API
官方文档链接:Reference Manual for libvirt
在目录中我们可以看到与virsh命令几乎一一对应的API。
libvirt使用C语言编写,提供稳定的C API,对其他语言的支持也是通过C API的绑定实现的,因此本地需要安装相关的C开发库,并运行libvirtd守护进程。
以Go语言和CentOS 7为例,官方Go语言库为:libvirt-go,与C库版本同步向后兼容到v1.2.0,可用于生产环境。
开发环境需要安装gcc、libvirt、libvirt-devel、centos-release-qemu-ev等,该库通过CGO调用libvrt C库,访问本地的UNIX套接字来与libvirtd通信。因此不可避免需要开发本地Daemon程序,相当于在libvirtd基础上再封装一层,但这也方便操作NFS、VXLAN、系统镜像等,实现更多自定义功能。
以获取虚拟机运行状态为例,流程通常为:
获取libvirt连接 -> 按ID获取domain -> 执行操作 -> 释放domain
如下所示:
|
|
我们可以把常用的操作封装为函数,确保所有成功打开的连接和domain都被正确释放。
有CGO的地方,就有纯GO实现。digitalocean开源了他们内部使用的libvirt库:go-libvirt,这个库通过libvirt的RPC接口与守护进程通信,不需要引用C库,可通过网络远程调用libvirtd,唯一的缺点是不稳定。
2.4 虚拟机状态
libvirt-go提供的虚拟机状态分为当前状态state与导致当前状态的原因reason,由于含义都在源码注释中,下面整理了英文注释:
|
|
笔者按照枚举数值整理如下:
- 0:nostate,无状态
- 1:running,运行中
- unknown:未知
- booted:正常开机启动
- migrated:从其他宿主机迁移后启动
- restored:从状态文件恢复运行
- fromSnapshot:从快照恢复运行
- unpaused:从挂起状态恢复运行
- migrationCanceled:取消迁移状态后恢复运行
- saveCancel:取消保存后恢复运行
- wakeup:唤醒后恢复运行
- crashed:从crash状态恢复启动
- postCopy:复制结束后恢复启动
- 2:blocked,因为资源阻塞
- unknown:未知
- 3:pause,挂起
- unkown:未知
- user:用户主动执行挂起
- migration:执行迁移导致的挂起
- save:保存虚拟机导致的挂起
- dump:dump虚拟机导致的挂起
- IOError:磁盘IO错误导致的挂起
- watchdog:看门狗事件导致的挂起
- fromSnapshot:从快照恢复后挂起
- shuttingDown:关机时挂起
- snapshot:创建快照时挂起
- crash:虚拟机crash导致的挂起
- startingUp:虚拟机正在启动
- postCopy:post-copy迁移导致的挂起
- postCopyFaiied:post-copy失败后的挂起
- 4:shutdown,正在关机
- unknown:未知
- user:用户主动关机
- 5:shutoff,已关闭
- unknown:未知
- shutdown:正常关机
- destroyed:强制关机
- crashed:domain crash
- migrated:已迁移到其他host
- saved:已保存到文件
- failed:domain无法启动
- fromSnapshot:从domain关闭时生成的快照恢复
- daemon:被守护进程终结
- 6:crash,crash
- unknown:未知
- panicked:domain panic
- 7:pmsuspend,由虚拟机的电源管理挂起
- unknown:未知
3. 写在最后
笔者的负责的虚拟机监控程序涉及libvirt内容比较简单,基本仅限于上面的示例代码:获取虚拟机状态上报服务端。
状态通过调接口就可以获取,比较麻烦的是做相关测试来确定libvirtd状态与业务状态的转换关系。
如果未来有更多虚拟机相关的开发工作时,应该会再深入研究一些API内部实现。