- 1. libvirt简介
- 1.1. 多种虚拟化方案支持
- 1.2. LGPL许可证
- 1.3. 开发者社区
- 1.4. 编程语言支持
- 1.5. AMQP 协议
- 1.6. 加密认证
- 1.7. 基本架构
- 1.8. 重要概念
- 1.9. 管理功能
- 1.9.1. 域的管理
- 1.9.2. 远程节点的管理
- 1.9.3. 存储的管理
- 1.9.4. 网络的管理
- 1.9.5. 应用程序接口
- 1.10. libvirt组成
- 2. 基于 libvirt 所开发的开源应用
- 3. libvirt的安装与配置
- 3.1. libvirt安装
- 3.2. libvirt的配置文件
- 3.2.1. /etc/libvirt/libvirt.conf
- 3.2.2. /etc/libvirt/libvirtd.conf
- 3.2.3. /etc/libvirt/qemu.conf
- 3.2.4. /etc/libvirt/qemu/目录
- 4. libvirtd的使用
- 5. libvirt域的XML配置文件
- 5.1. 客户机的XML配置文件格式的示例
- 5.2. CPU、内存、启动顺序等基本配置
- 5.2.1. CPU的配置
- 5.2.2. 内存的配置
- 5.2.3. 客户机系统类型和启动顺序: os标签
- 5.3. 网络的配置
- 5.3.1. 桥接方式的网络配置
- 5.3.2. NAT方式的虚拟网络配置
- 5.3.2.1. 默认配置
- 5.3.3. 用户模式网络的配置
- 5.3.4. 网卡设备直接分配(VT-d)
- 5.3.4.1. interface方式: 仅支持SRIOV的VF透传
- 5.3.4.2. hostdev方式: VF透传和直接透传
- 5.4. 存储的配置
- 5.5. 其他配置简介
- 5.5.1. 域的配置
- 5.5.2. 域的元数据配置
- 5.5.3. QEMU模拟器的配置
- 5.5.4. 图形显示方式
- 5.5.5. 客户机声卡和显卡的配置
- 5.5.6. 串口和控制台
- 5.5.7. 输入设备
- 5.5.8. PCI控制器
- 6. libvirt API简介
- 6.1. 连接Hypervisor相关的API
- 6.2. 域管理的API
- 6.3. 节点管理的API
- 6.4. 网络管理的API
- 6.5. 存储卷管理的API
- 6.6. 存储池管理的API
- 6.7. 事件管理的API
- 6.8. 数据流管理的API
- 7. 建立到Hypervisor的连接
- 7.1. 本地URI
- 7.2. 远程URI
- 7.3. 使用URI建立到Hypervisor的连接
- 8. libvirt API使用示例
- 8.1. libvirt的C API的使用
- 8.1.1. 启动libvirtd守护进程
- 8.1.2. libvirt-devel软件包
- 8.1.3. C程序查询
- 8.1.4. virsh命令查询
- 8.2. libvirt的Python API的使用
- 8.1. libvirt的C API的使用
1. libvirt简介
libvirt的主要目标是为包括Xen在内的各种虚拟化工具提供一套方便、可靠的编程接口,用一种单一的方式管理多种不同的虚拟化提供方式和Hypervisor,避免学习、使用不同Hypervisor的特定工具。
libvirt本身不是一种工具,它为虚拟化管理工具提供API(Application Programming Interface,应用程序编程接口);当前主流 Linux 平台上默认的虚拟化管理工具 virsh、virt-manager (图形化)、virt-install(命令行模式)和云计算框架平台(如OpenStack、ZStack、OpenNebula、Eucalyptus等)等都是基于libvirt开发而成的。
libvirt是为了更方便地管理平台虚拟化技术而设计的开放源代码的应用程序接口、守护进程和管理工具,它不仅提供了对虚拟化客户机的管理,也提供了对虚拟化网络和存储的管理!!!。
关于libvirt的官方描述如下。
- libvirt 是一种为最新的 Linux(及其他操作系统)提供虚拟化功能的工具包。
- libvirt是遵循GUN公共许可的免费软件。
- libvirt是一种长期稳定的C语言API。
- libvirt提供一组通用语言的绑定。
- libvirt是一个DMTF(Distributed Management Task Force,分布式管理任务组)虚拟化模式的CIM(Computer Integrated Manufacturing,计算机集成制造)提供者。
- libvirt是一个实现AMQP(Advanced Message Queuing Protocol,消息队列协议)/Qpid(AMQP 的一个实现)消息系统的 QMF(Query Management Facility,查询管理应用程序)代理。
1.1. 多种虚拟化方案支持
libvirt支持多种虚拟化方案,既支持包括KVM、QEMU、Xen、VMware、VirtualBox、Hyper-V等在内的平台虚拟化方案,也支持OpenVZ、LXC等Linux容器虚拟化系统!!!,还支持用户态Linux(UML!!!)的虚拟化。
1.2. LGPL许可证
libvirt是一个免费的开源的软件,使用的许可证是LGPL(GNU宽松的通用公共许可证),使用libvirt库进行链接的软件程序不一定要选择开源和遵守GPL许可证。
1.3. 开发者社区
和KVM、Xen等开源项目类似,libvirt也有自己的开发者社区,而且随着虚拟化、云计算等成为近年来的技术热点,libvirt项目的社区也比较活跃。
目前,libvirt的开发主要由Redhat公司作为强大的支持,由于Redhat公司在虚拟化方面逐渐偏向于支持KVM(而不是Xen),故libvirt对QEMU/KVM的支持是非常成熟和稳定的。
当然,IBM、Novell等公司以及众多的个人开发者对libvirt项目的代码贡献量也是非常大的。
1.4. 编程语言支持
libvirt本身提供了一套较为稳定的C语言应用程序接口,目前,在其他一些流行的编程语言中也提供了对libvirt的绑定,在Python、Perl、Java、Ruby、PHP、OCaml等高级编程语言中已经有libvirt的程序库可以直接使用。
1.5. AMQP 协议
libvirt还提供了为基于AMQP(高级消息队列协议)的消息系统(如Apache Qpid)提供QMF代理,这可以让云计算管理系统中宿主机与客户机、客户机与客户机之间的消息通信变得更易于实现。
1.6. 加密认证
libvirt还为安全地远程管理虚拟客户机提供了加密和认证等安全措施。
正是由于libvirt拥有这些强大的功能和较为稳定的应用程序接口,而且它的许可证(license)也比较宽松,所以libvirt的应用程序接口已被广泛地用在基于虚拟化和云计算的解决方案中,主要作为连接底层Hypervisor和上层应用程序的一个中间适配层。
1.7. 基本架构
libvirt对多种不同的Hypervisor的支持是通过一种基于驱动程序的架构来实现的。libvirt对不同的Hypervisor提供了不同的驱动:对Xen有Xen的驱动,对QEMU/KVM有QEMU驱动,对VMware有VMware驱动。在libvirt源代码中,可以很容易找到qemu_driver.c、xen_driver.c、xenapi_driver.c、VMware_driver.c、vbox_driver.c这样的驱动程序源代码文件。
libvirt作为中间适配层,可以让底层Hypervisor对上层用户空间的管理工具是完全透明的,因为libvirt屏蔽了底层各种Hypervisor的细节,为上层管理工具提供了一个统一的、较稳定的接口(API)。通过libvirt,一些用户空间管理工具可以管理各种不同的Hypervisor和上面运行的客户机,它们之间基本的交互框架如图4-1所示。
注: virsh、virsh-manager、virt-viewer、virt-install等都是libvirt工具
1.8. 重要概念
在libvirt中涉及几个重要的概念,解释如下:
- 节点(Node)是一个物理机器,上面可能运行着多个虚拟客户机。Hypervisor和Domain都运行在节点上。
- Hypervisor也称虚拟机监控器(VMM),如KVM、Xen、VMware、Hyper-V等,是虚拟化中的一个底层软件层,它可以虚拟化一个节点让其运行多个虚拟客户机(不同客户机可能有不同的配置和操作系统)。
- 域(Domain)是在Hypervisor上运行的一个客户机操作系统实例。域也被称为实例(instance,如在亚马逊的AWS云计算服务中客户机就被称为实例)、客户机操作系统(guest OS)、虚拟机(virtual machine),它们都是指同一个概念。
节点、Hypervisor和域的关系可以简单地用图4-2来表示。 
1.9. 管理功能
在了解了节点、Hypervisor和域的概念之后,用一句话概括libvirt的目标,那就是:为了安全高效地管理节点上的各个域,而提供一个公共的稳定的软件层。当然,这里的管理,既包括本地的管理,也包含远程的管理。
具体地讲,libvirt的管理功能主要包含如下5个部分。
1.9.1. 域的管理
1)域的管理。
包括对节点上的域的各个生命周期的管理,如启动、停止、暂停、保存、恢复和动态迁移。还包括对多种设备类型的热插拔操作,包括磁盘、网卡、内存和CPU。当然不同的Hypervisor上对这些热插拔的支持程度有所不同。
1.9.2. 远程节点的管理
2)远程节点的管理。
只要物理节点上运行了libvirtd这个守护进程,远程的管理程序就可以连接到该节点进程管理操作,经过认证和授权之后,所有的libvirt功能都可以被访问和使用。libvirt支持多种网络远程传输类型,如SSH、TCP套接字、Unix domain socket、TLS的加密传输等。假设使用了最简单的SSH,不需要额外的配置工作,比如,在example.com节点上运行了libvirtd,而且允许SSH访问,在远程的某台管理机器上就可以用如下的命令行来连接到example.com上,从而管理其上的域。
virsh -c qemu+ssh://[email protected]/system
1.9.3. 存储的管理
3)存储的管理。
任何运行了libvirtd守护进程的主机!!!,都可以通过libvirt来管理不同类型的存储!!!,如创建不同格式的客户机镜像(qcow2、raw、qde、vmdk等)、挂载NFS共享存储系统、查看现有的LVM卷组、创建新的LVM卷组和逻辑卷、对磁盘设备分区、挂载iSCSI共享存储、使用Ceph系统支持的RBD远程存储,等等。
当然在libvirt中,对存储的管理也是支持远程的。
1.9.4. 网络的管理
4)网络的管理。
任何运行了libvirtd守护进程的主机,都可以通过libvirt来管理物理的和逻辑的网络接口。包括列出现有的网络接口卡,配置网络接口,创建虚拟网络接口,网络接口的桥接,VLAN管理,NAT网络设置,为客户机分配虚拟网络接口,等等。
1.9.5. 应用程序接口
5)提供一个稳定、可靠、高效的应用程序接口,以便可以完成前面的4个管理功能。
1.10. libvirt组成
libvirt主要由3个部分组成,分别是:应用程序编程接口库、一个守护进程(libvirtd)和一个默认命令行管理工具(virsh)。
- 应用程序接口是为其他虚拟机管理工具(如virsh、virt-manager、virt-viewer、virt-install等)提供虚拟机管理的程序库支持。
- libvirtd守护进程负责执行对节点上的域的管理工作,在用各种工具对虚拟机进行管理时,这个守护进程一定要处于运行状态中。而且这个守护进程可以分为两种:
- 一种是root权限的libvirtd,其权限较大,可以完成所有支持的管理工作;
- 一种是普通用户权限的libvirtd,只能完成比较受限的管理工作。
- virsh是libvirt项目中默认的对虚拟机管理的一个命令行工具,将在4.2节中详细介绍。
2. 基于 libvirt 所开发的开源应用
libvirt 的功能强大,目前有大量的应用程序正成功构建于 libvirt 之上。
其中一个有趣的应用程序就是 virsh,它是一种虚拟 shell。还有一种名为virt-install 的应用程序,它可用于从多个操作系统发行版供应新域。
virt-clone可用于从另一个 VM 复制 VM(既包括操作系统复制,也包括磁盘复制)。
一些高级应用程序包括多用途桌面管理工具 virt-manager 和安全连接到 VM图形控制台的轻量级工具 virt-viewer。
构建于libvirt之上的一种最重要的工具名为oVirt。oVirt VM管理应用程序旨在管理单个结点上的单个 VM 或多个主机上的大量 VM。除了可以简化大量的主机和VM的管理外,它还可用于跨平台和架构自动化集群,负载平衡和工作。
3. libvirt的安装与配置
3.1. libvirt安装
RHEL 系列中可以使用yum或rpm工具来安装对应的RPM包。
查看某系统中已经安装的libvirt相关的RPM包,命令行如下:
# rpm -qa | grep libvirt
# yum install libvirt
当然,RHEL 7.3默认采用QEMU/KVM的虚拟化方案,所以应该安装QEMU相关的软件包。查看这些软件包的命令行操作如下:
[root@localhost qemu]# rpm -qa | grep '^qemu'
qemu-kvm-1.5.3-160.el7_6.1.x86_64
qemu-kvm-common-1.5.3-160.el7_6.1.x86_64
qemu-guest-agent-2.12.0-2.el7.x86_64
qemu-img-1.5.3-160.el7_6.1.x86_64
# 安装时, 运行yum install qemu-kvm即可
由于libvirt是跨平台的,而且还支持微软公司的Hyper-V虚拟化,所以在Windows上也可以安装libvirt,甚至可以编译libvirt。
可以到libvirt官方的网页( https://libvirt.org/windows.html )中查看更多关于libvirt对Windows的支持。
3.2. libvirt的配置文件
以RHEL 7.3为例,libvirt相关的配置文件都在/etc/libvirt/目录之中,如下:
[root@localhost qemu]# ll /etc/libvirt/
总用量 80
-rw-r--r-- 1 root root 450 4月 24 22:07 libvirt-admin.conf
-rw-r--r-- 1 root root 547 4月 24 22:07 libvirt.conf
-rw-r--r-- 1 root root 16529 4月 24 22:07 libvirtd.conf
-rw-r--r-- 1 root root 1175 4月 24 22:07 lxc.conf
drwx------. 2 root root 4096 5月 15 19:51 nwfilter
drwx------. 3 root root 22 4月 24 22:07 qemu
-rw-r--r-- 1 root root 30306 4月 24 22:07 qemu.conf
-rw-r--r-- 1 root root 2169 4月 24 22:07 qemu-lockd.conf
drwx------. 2 root root 6 3月 1 09:39 secrets
-rw-r--r-- 1 root root 3202 4月 24 22:07 virtlockd.conf
-rw-r--r-- 1 root root 3247 4月 24 22:07 virtlogd.conf
[root@localhost qemu]# ll /etc/libvirt/qemu
总用量 0
drwx------. 3 root root 42 4月 24 22:07 networks
下面简单介绍其中几个重要的配置文件和目录。
3.2.1. /etc/libvirt/libvirt.conf
libvirt.conf文件用于配置一些常用libvirt连接(通常是远程连接)的别名。和Linux中的普通配置文件一样,在该配置文件中以井号(#)开头的行是注释,如下:
[root@localhost libvirt]# cat /etc/libvirt/libvirt.conf
#
# This can be used to setup URI aliases for frequently
# used connection URIs. Aliases may contain only the
# characters a-Z, 0-9, _, -.
#
# Following the '=' may be any valid libvirt connection
# URI, including arbitrary parameters
#uri_aliases = [
# "hail=qemu+ssh://[email protected]/system",
# "sleet=qemu+ssh://[email protected]/system",
#]
#
# These can be used in cases when no URI is supplied by the application
# (@uri_default also prevents probing of the hypervisor driver).
#
#uri_default = "qemu:///system"
#为了演示目录,配置了如下这个别名
uri_aliases = [
"remote1=qemu+ssh://[email protected]/system",
]
其中,配置了remote1这个别名,用于指代
qemu+ssh//[email protected]/system
这个远程的libvirt连接。有这个别名后,就可以在用virsh等工具或自己写代码调用libvirt API时使用这个别名,而不需要写完整的、冗长的URI连接标识了。
用virsh使用这个别名,连接到远程的libvirt上查询当前已经启动的客户机状态,然后退出连接。命令行操作如下:
[root@kvm-host kvm_demo]# systemctl reload libvirtd
[root@kvm-host kvm_demo]# virsh -c remote1
[email protected]'s password:
Welcome to virsh, the virtualization interactive terminal.
Type: 'help' for help with commands
'quit' to quit
virsh # list
Id Name State
----------------------------------------------------
1 rhel7u2-remote running
virsh # quit
[root@kvm-host kvm_demo]#
在代码中调用libvirt API时也可以使用这个别名来建立连接,如下的python代码行就实现了使用这个别名来建立连接。
conn = libvirt.openReadOnly('remote1')
3.2.2. /etc/libvirt/libvirtd.conf
libvirtd.conf是libvirt的守护进程libvirtd的配置文件,被修改后需要让libvirtd重新加载配置文件(或重启libvirtd)才会生效。
在libvirtd.conf文件中,用井号(#)开头的行是注释内容,真正有用的配置在文件的每一行中使用“配置项=值”(如tcp_port="16509")这样配对的格式来设置。
在libvirtd.conf中配置了libvirtd启动时的许多设置,包括是否建立TCP、UNIX domain socket等连接方式及其最大连接数,以及这些连接的认证机制,设置libvirtd的日志级别等。
例如,下面的几个配置项表示
- 关闭TLS安全认证的连接(默认值是打开的),
- 打开TCP连接(默认是关闭TCP连接的),
- 设置TCP监听的端口,
- 设置UNIX domain socket的保存目录
- TCP连接不使用认证授权方式
# 关闭TLS安全认证的连接
listen_tls = 0
# 打开TCP连接
listen_tcp = 1
# TCP监听的端口
tcp_port = "16666"
# UNIX domain socket的保存目录
unix_sock_dir = "/var/run/libvirt"
# TCP连接不使用认证授权方式
auth_tcp = "none"
注意:
- 要让TCP、TLS等连接生效,需要在启动libvirtd时加上--listen参数(简写为-l)。
- 而默认的systemctl start libvirtd命令在启动libvirtd服务时并没带--listen参数。
- 所以如果要使用TCP等连接方式,可以使用libvirtd --listen -d命令来启动libvirtd。
以上配置选项实现将UNIX socket放到/var/run/libvirt目录下,启动libvirtd并检验配置是否生效。命令行操作如下:
[root@kvm-host ~]# libvirtd --listen -d
[root@kvm-host ~]# virsh -c qemu+tcp://localhost:16666/system
Welcome to virsh, the virtualization interactive terminal.
Type: 'help' for help with commands
'quit' to quit
virsh # quit
[root@kvm-host ~]# ls /var/run/libvirt/libvirt-sock*
/var/run/libvirt/libvirt-sock /var/run/libvirt/libvirt-sock-ro
3.2.3. /etc/libvirt/qemu.conf
qemu.conf是libvirt对QEMU的驱动的配置文件,包括VNC、SPICE等,以及连接它们时采用的权限认证方式的配置,也包括内存大页、SELinux、Cgroups等相关配置。
3.2.4. /etc/libvirt/qemu/目录
在qemu目录下存放的是使用QEMU驱动的域的配置文件。查看qemu目录如下:
[root@kvm-host ~]# ls /etc/libvirt/qemu/
networks centos7u2-1.xml centos7u2-2.xml
其中包括了两个域的XML配置文件(centos7u2-1.xml和centos7u2-2.xml),这就是笔者用virt-manager工具创建的两个域,默认会将其配置文件保存到/etc/libvirt/qemu/目录下。而其中的networks目录保存了创建一个域时默认使用的网络配置。
4. libvirtd的使用
libvirtd是一个作为libvirt虚拟化管理系统中的服务器端的守护程序,要让某个节点能够利用libvirt进行管理(无论是本地还是远程管理!!!),都需要在这个节点上运行libvirtd这个守护进程,以便让其他上层管理工具可以连接到该节点,libvirtd负责执行其他管理工具发送给它的虚拟化管理操作指令。
而libvirt的客户端工具(包括virsh、virt-manager等)可以连接到本地或远程的libvirtd进程,以便管理节点上的客户机(启动、关闭、重启、迁移等)、收集节点上的宿主机!!!和客户机的配置和资源使用状态。
在RHEL 7.3中,libvirtd是作为一个服务(service)配置在系统中的,所以可以通过systemctl命令来对其进行操作(RHEL 6.x等系统中使用service命令)。常用的操作方式有:
- “systemctl start libvirtd”命令表示启动libvirtd,
- “systemctl restart libvirtd”表示重启libvirtd,
- “systemctl reload libvirtd”表示不重启服务但重新加载配置文件(即/etc/libvirt/libvirtd.conf配置文件),
- “systemctl status libvirtd”表示查询libvirtd服务的运行状态。
在默认情况下,libvirtd在监听一个本地!!!的Unix domain socket!!!,而没有监听基于网络的TCP/IP socket,需要使用“-l或--listen”的命令行参数来开启对libvirtd.conf配置文件中TCP/IP socket的配置!!!。
另外,libvirtd守护进程的启动或停止,并不会直接影响正在运行中的客户机!!!。
libvirtd在启动或重启完成时,只要客户机的XML配置文件!!! 是存在的,libvirtd会自动加载这些客户的配置!!!,获取它们的信息。
当然,如果客户机没有基于libvirt格式的XML文件来运行(例如直接使用qemu命令行来启动的客户机),libvirtd则不能自动发现它。
libvirtd是一个可执行程序,不仅可以使用“systemctl”命令调用它作为服务来运行,而且可以单独地运行libvirtd命令来使用它。下面介绍几种libvirtd命令行的参数。
(1)-d或--daemon
表示让libvirtd作为守护进程(daemon)在后台运行。
(2)-f或--config FILE
指定libvirtd的配置文件为FILE,而不是使用默认值(通常是/etc/libvirt/libvirtd.conf)。
(3)-l或--listen
开启配置文件中配置的TCP/IP连接。
(4)-p或--pid-file FILE
将libvirtd进程的PID写入FILE文件中,而不是使用默认值(通常是/var/run/libvirtd.pid)。
(5)-t或--timeout SECONDS
设置对libvirtd连接的超时时间为SECONDS秒。
(6)-v或--verbose
执行命令输出详细的输出信息。特别是在运行出错时,详细的输出信息便于用户查找原因。
(7)--version
显示libvirtd程序的版本信息。
关于libvirtd命令的使用,几个简单的命令行操作演示如下:
#使用libvirtd 命令前,先停止已运行的服务
[root@kvm-host ~]# systemctl stop libvirtd
[root@kvm-host ~]# libvirtd --version
libvirtd (libvirt) 2.0.0
[root@kvm-host ~]# libvirtd
^C #没有以daemon的形式启动,标准输出被libvirtd占用;这里用Ctrl+C组合键结束libvirtd进程,以便继续进行后续操作
[root@kvm-host ~]# libvirtd -l -d -p /root/libvirtd.pid
[root@kvm-host ~]# cat /root/libvirtd.pid
8136
5. libvirt域的XML配置文件
在使用libvirt!!!对虚拟化系统进行管理时,很多地方都是以XML文件作为配置文件!!! 的,包括客户机(域)的配置、宿主机网络接口配置、网络过滤、各个客户机的磁盘存储配置、磁盘加密、宿主机和客户机的CPU特性,等等。
本节只针对客户机的XML进行较详细介绍,因为客户机的配置是最基本的和最重要的,了解了它之后就可以使用libvirt管理客户机了。
5.1. 客户机的XML配置文件格式的示例
在libvirt中,客户机(即域)的配置是采用XML格式来描述的。下面展示了笔者使用virt-manager创建的一个客户机的配置文件(即在4.1.2节中看到的centos7u2-1.xml文件),后面几节将会分析其中的主要配置项目。



centos7u2-1 
2f6260bf-1283-4933-aaef-fa82148537ba 
2097152 
2097152 
2 

hvm 








Haswell-noTSX 






destroy 
restart 
restart 





/usr/libexec/qemu-kvm 






























































由上面的配置文件示例可以看到,在该域的XML文件中,所有有效配置都在<domain>和domain>标签之间,这表明该配置文件是一个域的配置。
通过libvirt启动客户机,经过文件解析和命令参数的转换,最终也会调用qemu命令行工具来实际完成客户机的创建。
用这个XML配置文件启动的客户机,它的qemu命令行参数是非常详细、非常冗长的一行。查询qemu命令行参数的操作如下:
[root@kvm-host ~]# ps -ef | grep qemu | grep centos7u2-1
qemu 5865 1 60 21:21 ? 00:00:13 /usr/libexec/qemu-kvm -name centos7u2-1 -S -machine pc-i440fx-rhel7.0.0,accel=kvm,usb=off -cpu Haswell,-rtm,-hle -m 2048 -realtime mlock=off -smp 2,sockets=2,cores=1,threads=1 -uuid 68ec2ee0-2f50-4189-bbfc-ac5d990fc93a -no-user-config -nodefaults -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/domain-centos7u2-1/monitor.sock,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=utc,driftfix=slew -global kvm-pit.lost_tick_policy=discard -no-hpet -no-shutdown #... 省略了更多的命令行参数
这里RHEL 7.3系统中默认的QEMU工具!!!为/usr/libexec/qemu-kvm!!!,与第3章中从源代码编译和安装的qemu-system-x86_64工具是类似的,它们的参数也基本一致(当然如果二者版本差异较大,参数和功能可能有一些不同)。
5.2. CPU、内存、启动顺序等基本配置
5.2.1. CPU的配置
在前面介绍的centos7u2-1.xml配置文件中,关于CPU的配置如下:
2 





Haswell-noTSX 
vcpu标签,表示客户机中vCPU的个数,这里为2。
features标签,表示Hypervisor为客户机打开或关闭CPU或其他硬件的特性!!!,这里打开了ACPI、APIC等特性。
当然,CPU的基础特性!!! 是在cpu标签!!!中定义的,这里是之前创建客户机时,libvirt自动检测了CPU硬件平台,默认使用了Haswell的CPU给客户机。对于这里看到的CPU模型:Haswell-noTSX,可以在文件/usr/share/libvirt/cpu_map.xml中查看详细描述。
该CPU模型中的特性(如SSE2、LM、NX、TSC、AVX2、SMEP等)也是该客户机可以看到和使用的特性。
对于CPU模型的配置,有以下3种模式。
1)custom模式:就是这里示例中表示的,基于某个基础的CPU模型,再做个性化的设置。
2)host-model模式:根据物理CPU的特性,选择一个与之最接近的标准CPU型号,如果没有指定CPU模式,默认也是使用这种模式!!!。
xml配置文件为:
3)host-passthrough模式:直接将物理CPU特性暴露给虚拟机使用,在虚拟机上看到的完全就是物理CPU的型号。
xml配置文件为:
对vCPU的分配,可以有更细粒度的配置,如下:
...
2
...
cpuset表示允许到哪些物理CPU上执行,这里表示客户机的两个vCPU被允许调度到1、2、4、6号物理CPU上执行(^3表示排除3号);而current表示启动客户机时只给1个vCPU,最多可以增加到使用2个vCPU。
当然,libvirt还提供cputune标签来对CPU的分配进行更多调节,如下:
...
2048
1000000
-1
1000000
-1
...
这里只简单解释其中几个配置:
vcpupin标签表示将虚拟CPU绑定到某一个或多个物理CPU上,如
表示客户机2号虚拟CPU被绑定到4号物理CPU上;
表示将QEMU emulator绑定到1~3号物理CPU上。
在不设置任何vcpupin和cpuset的情况下,客户机的虚拟CPU可能会被调度到任何一个物理CPU上去运行。
2048
表示客户机占用CPU时间的加权配置,一个配置为2048的域获得的CPU执行时间是配置为1024的域的两倍。如果不设置shares值,就会使用宿主机系统提供的默认值。
另外,还可以配置客户机的NUMA拓扑,以及让客户机针对宿主机NUMA的策略设置等,读者可参考
5.2.2. 内存的配置
在该域的XML配置文件中,内存大小的配置如下:
2097152 
2097152
可知,内存大小为2097152KB(即2GB),
- memory标签中的内存表示客户机最大可使用的内存,
- currentMemory标签中的内存表示启动时即分配给客户机使用的内存。
在使用QEMU/KVM时,一般将二者设置为相同的值。
另外,内存的ballooning相关的配置包含在devices这个标签!!!的memballoon子标签中,该标签配置了该客户机的内存气球设备,如下:


该配置将为客户机分配一个使用virtio-balloon驱动的设备,以便实现客户机内存的ballooning调节。该设备在客户机中的PCI设备编号为0000:00:08.0。
5.2.3. 客户机系统类型和启动顺序: os标签
客户机系统类型及其启动顺序在os标签中配置,如下:

hvm 


这样的配置表示客户机类型是hvm类型,HVM(hardware virtual machine,硬件虚拟机)原本是Xen虚拟化中的概念,它表示在硬件辅助虚拟化技术(Intel VT或AMD-V等)的支持下不需要修改客户机操作系统就可以启动客户机。
因为KVM一定要依赖于硬件虚拟化技术的支持,所以在KVM中,客户机类型应该总是hvm,操作系统的架构是x86_64,机器类型是pc-i440fx-rhel7.0.0(这是libvirt中针对RHEL 7系统的默认类型!!!,也可以根据需要修改为其他类型)。
boot选项用于设置客户机启动时的设备,这里有hd(即硬盘)和cdrom(光驱)两种,而且是按照硬盘、光驱的顺序启动的,它们在XML配置文件中的先后顺序即启动时的先后顺序。
5.3. 网络的配置
5.3.1. 桥接方式的网络配置
在域的XML配置中,使用桥接方式的网络的相关配置如下:

...






...
type='bridge'表示使用桥接方式使客户机获得网络,
address用于配置客户机中网卡的MAC地址,
表示使用宿主机中的br0网络接口来建立网桥,
5.3.2. NAT方式的虚拟网络配置
在域的XML配置中,NAT方式的虚拟网络的配置示例如下:

...





...
这里type='network'和表示使用NAT的方式,并使用默认的网络配置,客户机将会分配到192.168.122.0/24网段中的一个IP地址。
当然,使用NAT必须保证宿主机中运行着DHCP!!! 和DNS服务器!!!,一般默认使用dnsmasq软件查询。
查询DHCP和DNS服务的运行的命令行如下:
[root@kvm-host ~]# ps -ef | grep dnsmasq
nobody 1863 1 0 Dec08 ? 00:00:03 /usr/sbin/dnsmasq --strict-order --bind-interfaces --pid-file=/var/run/libvirt/network/default.pid --conf-file= --except-interface lo --listen-address 192.168.122.1 --dhcp-range 192.168.122.2,192.168.122.254 --dhcp-leasefile=/var/lib/libvirt/dnsmasq/default.leases --dhcp-lease-max=253 --dhcp-no-override
5.3.2.1. 默认配置
由于配置使用了默认的NAT网络配置,可以在libvirt相关的网络配置中看到一个default.xml文件(/etc/libvirt/qemu/networks/default.xml),它具体配置了默认的连接方式,如下:

default 







在使用NAT时,查看宿主机中网桥的使用情况如下:
[root@kvm-host ~]# brctl show
bridge name bridge id STP enabled interfaces
virbr0 8000.525400b45ba5 yes virbr0-nic
vnet0
其中vnet0这个网络接口就是客户机和宿主机网络连接的纽带。
5.3.3. 用户模式网络的配置
在域的XML文件中,如下的配置即实现了使用用户模式的网络。

...



...
其中,type='user'表示该客户机的网络接口是用户模式网络,是完全由QEMU软件!!!模拟的一个网络协议栈。
在宿主机中,没有一个虚拟的网络接口连接到virbr0这样的网桥。
5.3.4. 网卡设备直接分配(VT-d)
在客户机的网络配置中,还可以采用PCI/PCI-e网卡将设备直接分配给客户机使用。关于设备直接分配的细节,可以参考6.2节中的介绍,本节只介绍其在libvirt中的配置方式。
对于设备直接分配的配置在域的XML配置文件中有两种方式:
- 一种是较新的方式,使用
标签; - 另一种是较旧但支持设备很广泛的方式,直接使用
标签。
5.3.4.1. interface方式: 仅支持SRIOV的VF透传
在
用<driver name='vfio'/>指定使用哪一种分配方式(默认是VFIO,如果使用较旧的传统的device assignment方式,这个值可配为'kvm'),
用<source>标签来指示将宿主机中的哪个VF分配给宿主机使用,
还可使用
一个示例配置如下所示,它表示将宿主机的0000:08:10.0这个VF网卡直接分配给客户机使用,并规定该网卡在客户机中的MAC地址为“52:54:00:6d:90:02”。

...





...
5.3.4.2. hostdev方式: VF透传和直接透传
在
这种方式并不支持对直接分配的网卡在客户机中的MAC地址的设置,在客户机中网卡的MAC地址!!!与宿主机中看到的完全相同。
在域的XML配置文件中,使用

...



...
5.4. 存储的配置
在示例的域的XML配置文件中,关于客户机磁盘的配置如下:

...






...
上面的配置表示,使用qcow2格式的centos7u2.qcow镜像文件作为客户机的磁盘,其在客户机中使用virtio总线(使用virtio-blk驱动),设备名称为/dev/vda,其PCI地址为0000:00:07.0。
-
它的type属性表示磁盘使用哪种类型作为磁盘的来源,其取值为file、block、dir或network中的一个,分别表示使用文件、块设备、目录或网络作为客户机磁盘的来源。
-
它的device属性表示让客户机如何来使用该磁盘设备,其取值为floppy、disk、cdrom或lun中的一个,分别表示软盘、硬盘、光盘和LUN(逻辑单元号),默认值为disk(硬盘)。
在
-
子标签用于定义Hypervisor如何为该磁盘提供驱动, - 它的name属性用于指定宿主机中使用的后端驱动名称,QEMU/KVM仅支持name='qemu',但是它支持的类型type可以是多种,包括raw、qcow2、qed、bochs等。
- 而这里的cache属性表示在宿主机中打开该磁盘时使用的缓存方式,可以配置为default、none、writethrough、writeback、directsync和unsafe等多种模式。在5.4.1节中已经详细地介绍过磁盘缓存的各种配置方式的区别。
-
- 当
标签的type属性为file时,应该配置为这样的模式, - 而当type属性为block时,应该配置为这样的模式。
- 当
-
子标签表示将磁盘暴露给客户机时的总线类型和设备名称。 - 其dev属性表示在客户机中该磁盘设备的逻辑设备名称,
- 而bus属性表示该磁盘设备被模拟挂载的总线类型,bus属性的值可以为ide、scsi、virtio、xen、usb、sata等。如果省略了bus属性,libvirt则会根据dev属性中的名称来“推测”bus属性的值,例如,sda会被推测是scsi,而vda被推测是virtio。
- 子标签表示该磁盘设备在客户机中的PCI总线地址,这个标签在前面网络配置中也是多次出现的,如果该标签不存在,libvirt会自动分配一个地址。
5.5. 其他配置简介
5.5.1. 域的配置
在域的整个XML配置文件中,

...
在
-
一个是type,用于表示Hypervisor的类型,可选的值为xen、kvm、qemu、lxc、kqemu、VMware中的一个;
-
另一个是id,其值是一个数字,用于在该宿主机的libvirt中唯一标识一个运行着的客户机,如果不设置id属性,libvirt会按顺序分配一个最小的可用ID。
5.5.2. 域的元数据配置
在域的XML文件中,有一部分是用于配置域的元数据(meta data)。元数据用于表示域的属性(用于区别其他的域)。
在示例的域的XML文件中,元数据的配置如下:
centos7u2-1 
2f6260bf-1283-4933-aaef-fa82148537ba
其中,name用于表示该客户机的名称,uuid是唯一标识该客户机的UUID。在同一个宿主机上,各个客户机的名称!!! 和UUID!!! 都必须是唯一的。
当然,域的元数据还有其他很多配置,例如Xen上的一个域的元数据配置如下:

fv0 
4dea22b31d52d8f32516782e98ab3fa0 
A short description - title - of the domain 
Some human readable description 

.. 
.. 

...
5.5.3. QEMU模拟器的配置
在域的配置文件中,需要制定使用的设备模型的模拟器,在emulator标签中配置模拟器的绝对路径。在示例的域的XML文件中,模拟器的配置如下:

/usr/libexec/qemu-kvm 
...
假设自己编译了一个最新的QEMU,要使用自己编译的QEMU作为模拟器,只需要将这里修改为/usr/local/bin/qemu-system-x86_64即可。不过,创建客户机时可能会遇到如下的错误信息:
[root@kvm-host ~]# virsh create rhel7u2-1.xml
error: Failed to create domain from rhel7u2-1.xml
error: internal error Process exited while reading console log output: Supported machines are:
pc Standard PC (alias of pc-1.1)
pc-1.1 Standard PC (default)
pc-1.0 Standard PC
pc-0.15 Standard PC
pc-0.14 Standard PC
pc-0.13 Standard PC
这是因为自己编译的qemu-system-x86_64并不支持配置文件中的pc-i440fx-rhel7.0.0机器类型。做如下修改即可解决这个问题:
hvm
5.5.4. 图形显示方式
在示例的域的XML文件中,对连接到客户机的图形显示方式的配置如下:

...

...
这表示通过VNC的方式连接到客户机,其VNC端口为libvirt自动分配。
也可以支持其他多种类型的图形显示方式,以下就配置了SDL、VNC、RDP、SPICE等多种客户机显示方式。

...









...
5.5.5. 客户机声卡和显卡的配置
在示例的域的XML文件中,该客户机的声卡和显卡的配置如下:

...




...
<sound>标签表示的是声卡配置,其中model属性表示为客户机模拟出来的声卡的类型,其取值为es1370、sb16、ac97和ich6中的一个。
<video>标签表示的是显卡配置,其中<model>子标签表示为客户机模拟的显卡的类型,它的类型(type)属性可以为vga、cirrus、vmvga、xen、vbox、qxl中的一个,vram属性表示虚拟显卡的显存容量(单位为KB),heads属性表示显示屏幕的序号。
本示例中,KVM客户机的显卡的配置为qxl类型、显存为65536(即64 MB)、使用在第1号屏幕上。
5.5.6. 串口和控制台
串口和控制台是非常有用的设备,特别是在调试客户机的内核或遇到客户机宕机的情况下,一般都可以在串口或控制台中查看到一些利于系统管理员分析问题的日志信息。在示例的域的XML文件中,客户机串口和控制台的配置如下:

...






...
设置了客户机的编号为0的串口(即/dev/ttyS0),使用宿主机中的伪终端(pty),由于这里没有指定使用宿主机中的哪个虚拟终端,因此libvirt会自己选择一个空闲的虚拟终端(可能为/dev/pts/下的任意一个)。当然也可以加上配置来明确指定使用宿主机中的哪一个虚拟终端。
在通常情况下,控制台(console)配置在客户机中的类型为'serial',此时,如果没有配置串口(serial),则会将控制台的配置复制到串口配置中,如果已经配置了串口(本例即是如此),则libvirt会忽略控制台的配置项。
当然为了让控制台有输出信息并且能够与客户机交互,也需在客户机中配置将信息输出到串口,如在Linux客户机内核的启动行中添加“console=ttyS0”这样的配置。在9.5.2节对-serial参数的介绍中有更多和串口配置相关的内容。
5.5.7. 输入设备
在示例的XML文件中,在客户机图形界面下进行交互的输入设备的配置如下:

...



...
这里的配置会让QEMU模拟PS2接口的鼠标和键盘,还提供了tablet这种类型的设备,让光标可以在客户机获取绝对位置定位。
在5.6.3节中将介绍tablet设备的使用及其带来的好处。
5.5.8. PCI控制器
根据客户机架构的不同,libvirt默认会为客户机模拟一些必要的PCI控制器(而不需要在XML配置文件中指定!!!),而一些PCI控制器需要显式地在XML配置文件中配置。
在示例的域的XML文件中,一些PCI控制器的配置如下:


















这里显式指定了4个USB控制器、1个pci-root和1个virtio-serial控制器。
libvirt默认还会为客户机分配一些必要的PCI设备,如PCI主桥(Host bridge)、ISA桥等。
使用示例的域的XML配置文件启动客户机,在客户机中查看到的PCI信息如下:
[root@rhel7u2-1 ~]# lspci
00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
00:01.3 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 03)
00:02.0 VGA compatible controller: Redhat, Inc. QXL paravirtual graphic card (rev 04)
00:03.0 Ethernet controller: Redhat, Inc Virtio network device
00:04.0 Audio device: Intel Corporation 82801FB/FBM/FR/FW/FRW (ICH6 Family) High Definition Audio Controller (rev 01)
00:05.0 Communication controller: Redhat, Inc Virtio console
00:06.0 USB controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #1 (rev 03)
00:06.1 USB controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #2 (rev 03)
00:06.2 USB controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #3 (rev 03)
00:06.7 USB controller: Intel Corporation 82801I (ICH9 Family) USB2 EHCI Controller #1 (rev 03)
00:07.0 SCSI storage controller: Redhat, Inc Virtio block device
00:08.0 Unclassified device [00ff]: Redhat, Inc Virtio memory balloon
6. libvirt API简介
libvirt的核心价值和主要目标就是提供一套管理虚拟机的、稳定的、高效的应用程序接口(API)。libvirt API本身是用C语言实现的,本节以其提供的最核心的C语言接口的API为例进行简单的介绍。
libvirt API大致可划分为如下8个部分。
6.1. 连接Hypervisor相关的API
连接Hypervisor相关的API:以virConnect开头的一系列函数。
只有在与Hypervisor建立连接之后,才能进行虚拟机管理操作,所以连接Hypervisor的API是其他所有API使用的前提条件。与Hypervisor建立的连接为其他API的执行提供了路径,是其他虚拟化管理功能的基础。
通过调用virConnectOpen函数可以建立一个连接,其返回值是一个virConnectPtr对象,该对象就代表到Hypervisor的一个连接;如果连接出错,则返回空值(NULL)。
而virConnectOpenReadOnly函数会建立一个只读的连接,在该连接上可以使用一些查询的功能,而不使用创建、修改等功能。
virConnectOpenAuth函数提供了根据认证建立的连接。
virConnectGetCapabilities函数返回对Hypervisor和驱动的功能描述的XML格式的字符串。
virConnectListDomains函数返回一列域标识符,它们代表该Hypervisor上的活动域。
6.2. 域管理的API
域管理的API:以virDomain开头的一系列函数。
虚拟机最基本的管理职能就是对各个节点上的域的管理,故在libvirt API中实现了很多针对域管理的函数。要管理域,首先要获取virDomainPtr这个域对象,然后才能对域进行操作。有很多种方式来获取域对象,如virDomainPtr virDomainLookupByID(virConnectPtr conn,int id)函数是根据域的id值到conn这个连接上去查找相应的域。
类似的,virDomainLookupByName、virDomainLookupByUUID等函数分别是根据域的名称和UUID去查找相应的域。
在得到某个域的对象后,就可以进行很多操作,可以查询域的信息(如virDomainGetHostname、virDomainGetInfo、virDomainGetVcpus、virDomainGetVcpusFlags、virDomainGetCPUStats等),也可以控制域的生命周期(如virDomainCreate、virDomainSuspend、virDomainResume、virDomainDestroy、virDomainMigrate等)。
6.3. 节点管理的API
节点管理的API:以virNode开头的一系列函数。
域运行在物理节点之上,libvirt也提供了对节点进行信息查询和控制的功能。
节点管理的多数函数都需要使用一个连接Hypervisor的对象作为其中的一个传入参数,以便可以查询或修改该连接上的节点信息。
virNodeGetInfo函数是获取节点的物理硬件信息,virNodeGetCPUStats函数可以获取节点上各个CPU的使用统计信息,virNodeGetMemoryStats函数可以获取节点上的内存的使用统计信息,virNodeGetFreeMemory函数可以获取节点上可用的空闲内存大小。
还有一些设置或者控制节点的函数,如virNodeSetMemoryParameters函数可以设置节点上的内存调度的参数,virNodeSuspendForDuration函数可以让节点(宿主机)暂停运行一段时间。
6.4. 网络管理的API
网络管理的API:以virNetwork开头的一系列函数和部分以virInterface开头的函数。
libvirt也对虚拟化环境中的网络管理提供了丰富的API。
libvirt首先需要创建virNetworkPtr对象,然后才能查询或控制虚拟网络。
查询网络相关信息的函数有,virNetworkGetName函数可以获取网络的名称,virNetworkGetBridgeName函数可以获取该网络中网桥的名称,virNetworkGetUUID函数可以获取网络的UUID标识,virNetworkGetXMLDesc函数可以获取网络的以XML格式的描述信息,virNetworkIsActive函数可以查询网络是否正在使用中。
控制或更改网络设置的函数有,virNetworkCreateXML函数可以根据提供的XML格式的字符串创建一个网络(返回virNetworkPtr对象),virNetworkDestroy函数可以销毁一个网络(同时也会关闭使用该网络的域),virNetworkFree函数可以回收一个网络(但不会关闭正在运行的域),virNetworkUpdate函数可根据提供XML格式的网络配置来更新一个已存在的网络。另外,virInterfaceCreate、virInterfaceFree、virInterfaceDestroy、virInterfaceGetName、virInterfaceIsActive等函数可以用于创建、释放和销毁网络接口,以及查询网络接口的名称和激活状态。
6.5. 存储卷管理的API
存储卷管理的API:以virStorageVol开头的一系列函数。
libvirt对存储卷(volume)的管理主要是对域的镜像文件的管理,这些镜像文件的格式可能是raw、qcow2、vmdk、qed等。
libvirt对存储卷的管理,首先需要创建virStorageVolPtr这个存储卷对象,然后才能对其进行查询或控制操作。
libvirt提供了3个函数来分别通过不同的方式来获取存储卷对象,如virStorageVolLookupByKey函数可以根据全局唯一的键值来获得一个存储卷对象,virStorageVolLookupByName函数可以根据名称在一个存储资源池(storage pool)中获取一个存储卷对象,virStorageVolLookupByPath函数可以根据它在节点上的路径来获取一个存储卷对象。
有一些函数用于查询存储卷的信息,如virStorageVolGetInfo函数可以查询某个存储卷的使用情况,virStorageVolGetName函数可以获取存储卷的名称,virStorageVolGetPath函数可以获取存储卷的路径,virStorageVolGetConnect函数可以查询存储卷的连接。一些函数用于创建和修改存储卷,如virStorageVolCreateXML函数可以根据提供的XML描述来创建一个存储卷,virStorageVolFree函数可以释放存储卷的句柄(但是存储卷依然存在),virStorageVolDelete函数可以删除一个存储卷,virStorageVolResize函数可以调整存储卷的大小。
6.6. 存储池管理的API
存储池管理的API:以virStoragePool开头的一系列函数。
libvirt对存储池(pool)的管理包括对本地的基本文件系统、普通网络共享文件系统、iSCSI共享文件系统、LVM分区等的管理。
libvirt需要基于virStoragePoolPtr这个存储池对象才能进行查询和控制操作。
一些函数可以通过查询获取一个存储池对象,如virStoragePoolLookupByName函数可以根据存储池的名称来获取一个存储池对象,virStoragePoolLookupByVolume可以根据一个存储卷返回其对应的存储池对象。
virStoragePoolCreateXML函数可以根据XML描述来创建一个存储池(默认已激活),virStoragePoolDefineXML函数可以根据XML描述信息静态地定义一个存储池(尚未激活),virStorage PoolCreate函数可以激活一个存储池。
virStoragePoolGetInfo、virStoragePoolGetName、virStoragePoolGetUUID函数可以分别获取存储池的信息、名称和UUID标识。
virStoragePool IsActive函数可以查询存储池状态是否处于使用中,virStoragePoolFree函数可以释放存储池相关的内存(但是不改变其在宿主机中的状态),virStoragePoolDestroy函数可以用于销毁一个存储池(但并没有释放virStoragePoolPtr对象,之后还可以用virStoragePoolCreate函数重新激活它),virStoragePoolDelete函数可以物理删除一个存储池资源(该操作不可恢复)。
6.7. 事件管理的API
事件管理的API:以virEvent开头的一系列函数。
libvirt支持事件机制,在使用该机制注册之后,可以在发生特定的事件(如域的启动、暂停、恢复、停止等)时得到自己定义的一些通知。
6.8. 数据流管理的API
数据流管理的API:以virStream开头的一系列函数。
libvirt还提供了一系列函数用于数据流的传输。 对于libvirt API一些细节的使用方法和实现原理,可以参考其源代码。
7. 建立到Hypervisor的连接
要使用libvirt API进行虚拟化管理,就必须先建立到Hypervisor的连接,因为有了连接才能管理节点、Hypervisor、域、网络等虚拟化中的要素。本节就介绍一下建立到Hypervisor连接的一些方式。
对于一个libvirt连接,可以使用简单的客户端-服务器端(C/S)的架构模式来解释,一个服务器端运行着Hypervisor,一个客户端去连接服务器端的Hypervisor,然后进行相应的虚拟化管理。
当然,如果通过libvirt API实现本地的管理,则客户端和服务器端都在同一个节点上,并不依赖于网络连接。
一般来说(如基于QEMU/KVM的虚拟化方案),不管是基于libvirt API的本地管理还是远程管理,在服务器端的节点!!! 上,除了需要运行相应的Hypervisor以外,还需要让libvirtd!!!这个守护进程处于运行中的状态!!!,以便让客户端连接到libvirtd,从而进行管理操作。
不过,也并非所有的Hypervisor!!!都需要运行libvirtd守护进程!!!,比如VMware ESX/ESXi就不需要在服务器端运行libvirtd,依然可以通过libvirt客户端以另外的方式连接到VMware。
由于支持多种Hypervisor!!!,libvirt需要通过唯一的标识来指定如何才能准确地连接到本地或远程的Hypervisor。
为了达到这个目的,libvirt使用了在互联网应用中广泛使用的URI(Uniform Resource Identifier,统一资源标识符!!!)来标识到某个Hypervisor的连接!!!。
libvirt中连接的标识符URI,其本地URI和远程URI有一些区别,下面分别介绍一下它们的使用方式。
7.1. 本地URI
在libvirt的客户端使用本地的URI连接本系统范围内的Hypervisor,本地URI的一般格式如下:
driver[+transport]:///[path][?extral-param]
其中,
-
driver是连接Hypervisor的驱动名称(如qemu、xen、xbox、lxc等),
-
transport是选择该连接所使用的传输方式(可以为空,也可以是“unix”这样的值),
-
path是连接到服务器端上的某个路径,
-
?extral-param是可以额外添加的一些参数(如Unix domain sockect的路径)。
在libvirt中KVM使用QEMU驱动!!!。
QEMU驱动是一个多实例的驱动,它提供了
- 一个系统范围内的特权驱动(即“system”实例)
- 一个用户相关的非特权驱动(即“session”实例)。
通过“qemu:///session”这样的URI可以连接到一个libvirtd非特权实例,但是这个实例必须是与本地客户端的当前用户和用户组相同的实例,也就说,根据客户端的当前用户和用户组去服务器端寻找对应用户下的实例。在建立session连接后,可以查询和控制的域或其他资源都仅仅是在当前用户权限范围内的,而不是整个节点上的全部域或其他资源。
而使用“qemu:///system”这样的URI连接到libvirtd实例,是需要系统特权账号“root”权限的。在建立system连接后,由于它是具有最大权限的,因此可以查询和控制整个节点范围内的域,还可以管理该节点上特权用户才能管理的块设备、PCI设备、USB设备、网络设备等系统资源。一般来说,为了方便管理,在公司内网范围内建立到system实例的连接进行管理的情况比较常见,当然为了安全考虑,赋予不同用户不同的权限就可以使用建立到session实例的连接。
在libvirt中,本地连接QEMU/KVM的几个URI示例如下:
- qemu:///session
连接到本地的session实例,该连接仅能管理当前用户的虚拟化资源。
- qemu+unix:///session
以Unix domain sockect的方式连接到本地的session实例,该连接仅能管理当前用户的虚拟化资源。
- qemu:///system
连接到本地的system实例,该连接可以管理当前节点的所有特权用户可以管理的虚拟化资源。
- qemu+unix:///system
以Unix domain sockect的方式连接到本地的system实例,该连接可以管理当前节点的所有特权用户可以管理的虚拟化资源。
7.2. 远程URI
除了本地管理,libvirt还提供了非常方便的远程的虚拟化管理功能。libvirt可以使用远程URI来建立到网络上的Hypervisor的连接。远程URI和本地URI是类似的,只是会增加用户名、主机名(或IP地址)和连接端口来连接到远程的节点。远程URI的一般格式如下:
driver[+transport]://[user@][host][:port]/[path][?extral-param]
其中,
- transport表示传输方式,其取值可以是ssh、tcp、libssh2等;
- user表示连接远程主机使用的用户名,
- host表示远程主机的主机名或IP地址,
- port表示连接远程主机的端口。
其余参数的意义与本地URI中介绍的完全一样。
在远程URI连接中,也存在使用system实例和session实例两种方式,这二者的区别和用途,与本地URI中介绍的内容是完全一样的。
在libvirt中,远程连接QEMU/KVM的URI示例如下:
- qemu+ssh://[email protected]/system
通过ssh通道连接到远程节点的system实例,具有最大的权限来管理远程节点上的虚拟化资源。建立该远程连接时,需要经过ssh的用户名和密码验证或者基于密钥的验证。
- qemu+ssh://[email protected]/session
通过ssh通道连接到远程节点的使用user用户的session实例,该连接仅能对user用户的虚拟化资源进行管理,建立连接时同样需要经过ssh的验证。
- qemu://example.com/system
通过建立加密的TLS连接与远程节点的system实例相连接,具有对该节点的特权管理权限。在建立该远程连接时,一般需要经过TLS x509安全协议的证书验证。
- qemu+tcp://example.com/system
通过建立非加密的普通TCP连接与远程节点的system实例相连接,具有对该节点的特权管理权限。在建立该远程连接时,一般需要经过SASL/Kerberos认证授权。
7.3. 使用URI建立到Hypervisor的连接
在某个节点启动libvirtd后,一般在客户端都可以通过ssh方式连接到该节点。而TLS和TCP等连接方式却不一定都处于开启可用状态,如RHEL 7.3系统中的libvirtd服务在启动时就默认没有打开TLS和TCP这两种连接方式。关于libvirtd的配置可以参考4.1.2节中的介绍。而在服务器端的libvirtd打开了TLS和TCP连接方式,也需要一些认证方面的配置,当然也可直接关闭认证功能(这样不安全),可以参考libvirtd.conf配置文件。
我们看到,URI这个标识还是比较复杂的,特别是在管理很多远程节点时,需要使用很多的URI连接。为了简化系统管理的复杂程度,可以在客户端的libvirt配置文件中为URI命名别名,以方便记忆,这在4.1.2节中已经介绍过了。
在4.1.4节中已经介绍过,libvirt使用virConnectOpen函数来建立到Hypervisor的连接,所以virConnectOpen函数就需要一个URI作为参数。
而当传递给virConnectOpen的URI为空值(NULL)时,libvirt会依次根据如下3条规则去决定使用哪一个URI。
1)试图使用LIBVIRT_DEFAULT_URI这个环境变量。
2)试用使用客户端的libvirt配置文件中的uri_default参数的值。
3)依次尝试用每个Hypervisor的驱动去建立连接,直到能正常建立连接后即停止尝试。
当然,如果这3条规则都不能够让客户端libvirt建立到Hypervisor的连接,就会报出建立连接失败的错误信息(“failed to connect to the hypervisor”)。
在使用virsh这个libvirt客户端工具时,可以用“-c”或“--connect”选项来指定建立到某个URI的连接。只有连接建立之后,才能够操作。使用virsh连接到本地和远程的Hypervisor的示例如下:
[root@kvm-host ~]# virsh -c qemu:///system
Welcome to virsh, the virtualization interactive terminal.
virsh # list
Id Name State
----------------------------------------------------
1 rhel7u1-1 running
2 rhel7u2-2 running

virsh # quit
[root@kvm-host ~]# virsh -c qemu+tcp://localhost:16666/system
Welcome to virsh, the virtualization interactive terminal.
virsh # list
Id Name State
----------------------------------------------------
1 rhel7u1-1 running
2 rhel7u2-2 running

virsh # quit
[root@localhost ~]# virsh -c qemu+tcp://localhost/system
错误:连接到管理程序失败
错误:无法在 'localhost :16509' 连接到服务器: 拒绝连接

[root@kvm-host ~]# virsh -c qemu+ssh://[email protected]/system
[email protected]'s password:
Welcome to virsh, the virtualization interactive terminal.

Type: 'help' for help with commands
'quit' to quit

virsh # list
Id Name State
----------------------------------------------------
1 rhel7u2-remote running

virsh # quit
其实,除了针对QEMU、Xen、LXC等真实Hypervisor的驱动之外,libvirt自身还提供了一个名叫“test”的傀儡Hypervisor及其驱动程序。
test Hypervisor是在libvirt中仅仅用于测试和命令学习的目的,因为在本地的和远程的Hypervisor都连接不上(或无权限连接)时,test这个Hypervisor却一直都会处于可用状态。使用virsh连接到test Hypervisor的示例操作如下:
[root@kvm-host ~]# virsh -c test:///default list
Id Name State
----------------------------------------------------
1 test running

[root@kvm-host ~]# virsh -c test:///default
Welcome to virsh, the virtualization interactive terminal.

Type: 'help' for help with commands
'quit' to quit

virsh # list
Id Name State
----------------------------------------------------
1 test running

virsh # quit
8. libvirt API使用示例
经过前面几节对libvirt的配置、编译、API、建立连接等内容的介绍,相信大家对libvirt已经有了大致的了解。学习API的最好方法就是通过代码来调用API实现几个小功能,所以本节主要通过两个示例来分别演示如何调用libvirt的由C语言和Python语言绑定的API。
8.1. libvirt的C API的使用
8.1.1. 启动libvirtd守护进程
在使用libvirt API之前,必须要在远程或本地的节点上启动libvirtd守护进程!!!。
8.1.2. libvirt-devel软件包
在使用libvirt的客户端前,先安装libvirt-devel软件包。本次示例中安装的是RHEL 7.3自带的libvirt-devel软件包,如下:
[root@kvm-host ~]# rpm -q libvirt-devel
libvirt-devel-2.0.0-4.el7.x86_64
8.1.3. C程序查询
如下一个简单的C程序(文件名为dominfo.c)就是通过调用libvirt的API来查询一些关于某个域的信息。该示例程序比较简单易懂,它仅仅是使用libvirt API的一个演示程序,这里不做过多的介绍。不过,这里有三点需要注意:
1)需要在示例代码的开头引入
2)由于只是实现查询信息的功能,所以可以使用virConnectOpenReadOnly来建立只读连接;
3)这里使用了空值(NULL)作为URI,是让libvirt自动根据4.1.5节中介绍的默认规则去建立到Hypervisor的连接。这里由于本地已经运行了libvirtd守护进程,并启动了两个QEMU/KVM客户机,所以它默认会建立到QEMU/KVM的连接。
/**
* Get domain information via libvirt C API.
* Tested with libvirt-devel-2.0.0 on a RHEL 7.3 host system.
*/

#include 
#include 

int getDomainInfo(int id) {
virConnectPtr conn = NULL; /* the hypervisor connection */
virDomainPtr dom = NULL; /* the domain being checked */
virDomainInfo info; /* the information being fetched */

/* NULL means connect to local QEMU/KVM hypervisor */
conn = virConnectOpenReadOnly(NULL);
if (conn == NULL) {
fprintf(stderr, "Failed to connect to hypervisor\n");
return 1;
}

/* Find the domain by its ID */
dom = virDomainLookupByID(conn, id);
if (dom == NULL) {
fprintf(stderr, "Failed to find Domain %d\n", id);
virConnectClose(conn);
return 1;
}

/* Get virDomainInfo structure of the domain */
if (virDomainGetInfo(dom, &info) < 0) {
fprintf(stderr, "Failed to get information for Domain %d\n", id);
virDomainFree(dom);
virConnectClose(conn);
return 1;
}

/* Print some info of the domain */
printf("Domain ID: %d\n", id);
printf(" vCPUs: %d\n", info.nrVirtCpu);
printf(" maxMem: %d KB\n", info.maxMem);
printf(" memory: %d KB\n", info.memory);

if (dom != NULL)
virDomainFree(dom);
if (conn != NULL)
virConnectClose(conn);

return 0;
}

int main(int argc, char **argv)
{
int dom_id = 3;
printf("-----Get domain info by ID via libvirt C API -----\n");
getDomainInfo(dom_id);
return 0;
}
在获得dominfo.c这个示例程序之后,用virsh命令查看当前节点中的情况,再编译和运行这个示例程序去查询一些域的信息。将二者得到的一些信息进行对比,可以发现得到的信息是匹配的。
8.1.4. virsh命令查询
命令行操作如下:
[root@kvm-host kvm_demo]# virsh list
Id Name State
----------------------------------------------------
3 kvm-guest running

[root@kvm-host kvm_demo]# virsh dommemstat 3
actual 1048576
rss 680228

[root@kvm-host kvm_demo]# virsh vcpucount 3
maximum config 2
maximum live 2
current config 2
current live 2

[root@kvm-host kvm_demo]# gcc dominfo.c -o dominfo -lvirt

[root@kvm-host kvm_demo]# ./dominfo
-----Get domain info by ID via libvirt C API -----
Domain ID: 3
vCPUs: 2
maxMem: 1048576 KB
memory: 1048576 KB
这里需要注意的是,在使用GCC编译dominfo.c这个示例程序时,加上了“-lvirt”这个参数来指定程序链接时依赖的库文件,如果不指定libvirt相关的共享库,则会发生链接时错误。在本次示例的RHEL 7.3系统中,需要依赖的libvirt共享库文件是/usr/lib64/libvirt.so,如下:
[root@kvm-host ~]# ls /usr/lib64/libvirt.so
/usr/lib64/libvirt.so
8.2. libvirt的Python API的使用
在4.1.1节中已经介绍过,许多种编程语言都提供了libvirt的绑定。Python作为一种在Linux上比较流行的编程语言,也提供了libvirt API的绑定。
在使用Python调用libvirt之前,需要安装libvirt-python软件包,或者自行编译和安装libvirt及其Python API。
本次示例是基于RHEL 7.3系统自带的libvirt和libvirt-python软件包来进行的,对libvirt-python及Python中的libvirt API文件的查询,命令行如下:
[root@kvm-host ~]# rpm -q libvirt-python
libvirt-python-2.0.0-2.el7.x86_64 
[root@kvm-host ~]# ls /usr/lib64/python2.7/site-packages/libvirt*
/usr/lib64/python2.7/site-packages/libvirt_lxc.py /usr/lib64/python2.7/site-packages/libvirt.py
/usr/lib64/python2.7/site-packages/libvirt_lxc.pyc /usr/lib64/python2.7/site-packages/libvirt.pyc
/usr/lib64/python2.7/site-packages/libvirt_lxc.pyo /usr/lib64/python2.7/site-packages/libvirt.pyo
/usr/lib64/python2.7/site-packages/libvirtmod_lxc.so /usr/lib64/python2.7/site-packages/libvirt_qemu.py
/usr/lib64/python2.7/site-packages/libvirtmod_qemu.so /usr/lib64/python2.7/site-packages/libvirt_qemu.pyc
/usr/lib64/python2.7/site-packages/libvirtmod.so /usr/lib64/python2.7/site-packages/libvirt_qemu.pyo
如下是本次示例使用的一个Python小程序(libvirt-test.py),用于通过调用libvirt的Python API来查询域的一些信息。该Python程序示例的源代码如下:
#!/usr/bin/python
# Get domain info via libvirt python API.
# Tested with python2.7 and libvirt-python-2.0.0 on a KVM host.

import libvirt
import sys

def createConnection():
conn = libvirt.openReadOnly(None)
if conn == None:
print 'Failed to open connection to QEMU/KVM'
sys.exit(1)
else:
print '-----Connection is created successfully-----'
return conn

def closeConnnection(conn):
print ''
try:
conn.close()
except:
print 'Failed to close the connection'
return 1

print 'Connection is closed'

def getDomInfoByName(conn, name):
print ''
print '----------- get domain info by name ----------"'
try:
myDom = conn.lookupByName(name)
except:
print 'Failed to find the domain with name "%s"' % name
return 1

print "Dom id: %d name: %s" % (myDom.ID(), myDom.name())
print "Dom state: %s" % myDom.state(0)
print "Dom info: %s" % myDom.info()
print "memory: %d MB" % (myDom.maxMemory()/1024)
print "memory status: %s" % myDom.memoryStats()
print "vCPUs: %d" % myDom.maxVcpus()

def getDomInfoByID(conn, id):
print ''
print '----------- get domain info by ID ----------"'
try:
myDom = conn.lookupByID(id)
except:
print 'Failed to find the domain with ID "%d"' % id
return 1

print "Domain id is %d ; Name is %s" % (myDom.ID(), myDom.name())

if __name__ == '__main__':
name1 = "kvm-guest"
name2 = "notExist"
id1 = 3
id2 = 9999
print "---Get domain info via libvirt python API---"
conn = createConnection()
getDomInfoByName(conn, name1)
getDomInfoByName(conn, name2)
getDomInfoByID(conn, id1)
getDomInfoByID(conn, id2)
closeConnnection(conn)
该示例程序比较简单,只是简单地调用libvirt Python API获取一些信息。这里唯一需要注意的是“import libvirt”语句引入了libvirt.py这个API文件,然后才能够使用libvirt.openReadOnly、conn.lookupByName等libvirt中的方法。
在本次示例中,必须被引入的libvirt.py这个API文件的绝对路径是/usr/lib64/python2.7/site-packages/libvirt.py,它实际调用的是/usr/lib64/python2.7/site-packages/libvirtmod.so这个共享库文件。
在获得该示例Python程序后,运行该程序(libvirt-test.py),查看其运行结果,命令行操作如下:
[root@kvm-host kvm_demo]# python libvirt-test.py 2>/dev/null
---Get domain info via libvirt python API---
-----Connection is created successfully-----

----------- get domain info by name ----------"
Dom id: 3 name: kvm-guest
Dom state: [1, 1]
Dom info: [1, 1048576L, 1048576L, 2, 257070000000L]
memory: 1024 MB
memory status: {'actual': 1048576L, 'rss': 680228L}
vCPUs: 2

----------- get domain info by name ----------"
Failed to find the domain with name "notExist"

----------- get domain info by ID ----------"
Domain id is 3 ; Name is kvm-guest

----------- get domain info by ID ----------"
Failed to find the domain with ID "9999"

Connection is closed