0%

【虚拟化】libvirt 原理与实践

Libvirt是由Redhat开发的一套开源的软件工具,目标是提供一个通用和稳定的软件库来高效、安全地管理一个节点上的虚拟机,并支持远程操作。Libvirt屏蔽了不同虚拟化的实现,提供统一管理接口。用户只关心高层的功能,而VMM的实现细节,对于最终用户应该是透明的。Libvirt就作为VMM和高层功能之间的桥梁,接收用户请求,然后调用VMM提供的接口,来完成最终的工作。

Libvirt 是什么

为什么需要Libvirt?

  1. Hypervisor 比如 qemu-kvm 的命令行虚拟机管理工具参数众多,难于使用。
  2. Hypervisor 种类众多,没有统一的编程接口来管理它们,这对云环境来说非常重要。
  3. 没有统一的方式来方便地定义虚拟机相关的各种可管理对象。

Libvirt提供了什么?

  1. 它提供统一、稳定、开放的源代码的应用程序接口(API)、守护进程 (libvirtd)和和一个默认命令行管理工具(virsh)。
  2. 它提供了对虚拟化客户机和它的虚拟化设备、网络和存储的管理。
  3. 它提供了一套较为稳定的C语言应用程序接口。目前,在其他一些流行的编程语言中也提供了对libvirt的绑定,在Python、Perl、Java、Ruby、PHP、OCaml等高级编程语言中已经有libvirt的程序库可以直接使用。
  4. 它对多种不同的 Hypervisor 的支持是通过一种基于驱动程序的架构来实现的。libvirt 对不同的 Hypervisor 提供了不同的驱动,包括 Xen 的驱动,对QEMU/KVM 有 QEMU 驱动,VMware 驱动等。在 libvirt 源代码中,可以很容易找到 qemu_driver.c、xen_driver.c、xenapi_driver.c、vmware_driver.c、vbox_driver.c 这样的驱动程序源代码文件。
  5. 它作为中间适配层,让底层 Hypervisor 对上层用户空间的管理工具是可以做到完全透明的,因为 libvirt 屏蔽了底层各种 Hypervisor 的细节,为上层管理工具提供了一个统一的、较稳定的接口(API)。
  6. 它使用 XML 来定义各种虚拟机相关的受管理对象。

目前,libvirt 已经成为使用最为广泛的对各种虚拟机进行管理的工具和应用程序接口(API),而且一些常用的虚拟机管理工具(如virsh、virt-install、virt-manager等)和云计算框架平台(如OpenStack、OpenNebula、Eucalyptus等)都在底层使用 libvirt 的应用程序接口。

img img

Libvirt C API

Libvirti API 所管理的主要对象

对象 解释
Domain (域) 指运行在由Hypervisor提供的虚拟机器上的一个操作系统实例(常常是指一个虚拟机)或者用来启动虚机的配置。
Hypervisor 一个虚拟化主机的软件层
Node (主机) 一台物理服务器。
Storage pool (存储池) 一组存储媒介的集合,比如物理硬盘驱动器。一个存储池被划分为小的容器称作卷。卷会被分给一个或者多个虚机。
Volume (卷) 一个从存储池分配的存储空间。一个卷会被分给一个或者多个域,常常成为域里的虚拟硬盘。

对象的管理模型

对象名称 对象 Python 类 描述
Connect 与 Hypervisor的连接 virConnectPtr 在调用任何 API 去管理一个本地或者远端的Hypervisor前,必须建立和这个Hypervisor的连接。
Domain Guest domain virDomainPtr 用于列举和管理已有的虚机,或者创建新的虚机。唯一标识:ID,Name,UUID。一个域可能是暂时性的或者持久性的。暂时性的域只能在它运行期间被管理。持久性的域在主机上保存了它的配置。
Virtual Network 虚拟网络 virNetworkPtr 用于管理虚机的网络设备。唯一标识:Name,UUID。一个虚拟网络可能是暂时性的或者持久性的。每个主机上安装libvirt后,它都有一个默认的网络设备“default”。它向该主机上运行的虚机提供DHCP服务,以及通过NAT连接到主机上。
Storage Pool 存储池 virStoragePoolPtr 用于管理虚拟机内的所有存储,包括 local disk, logical volume group, iSCSI target, FibreChannel HBA and local/network file system。唯一标识:Name,UUID。一个存储池可能是暂时性的或者持久性的。Pool 的 type 可以是 dir, fs, netfs, disk, iscsi, logical, scsi,mpath, rbd, sheepdog, gluster 或者 zfs。
Storage Volume 存储卷 virStorageVolPtr 用于管理一个存储池内的存储块,包括一个池内分配的块、磁盘分区、逻辑卷、SCSI/iSCSI Lun,或者一个本地或者网络文件系统内的文件等。唯一标识:Name,Key,Path。
Host device 主机设备 virNodeDevPtr 用于管理主机上的物理硬件设备,包括 the physical USB or PCI devices and logical devices these provide, such as a NIC, disk, diskcontroller, sound card, etc。唯一标识:Name。

Libvirt XML 定义

Libvirt 使用 XML 来定义各种对象,详细的 XML 定义说明在 https://libvirt.org/format.html。 这里列出几种主要的对象:

disk

任何磁盘设备,包括软盘(floppy)、硬盘(hard disk)、光驱(cdrom)或者半虚拟化驱动都使用 元素来定义。 方式:

1
<disk type='**' device='**'>

其中:

  • type 用来指定device source 的类型:file, block, dir, network 或者 `volume”。具体的 source 由 标签定义。
  • device 用来指定 device target 的类型:floppy, disk, cdrom, and “lun”, 默认为 disk 。具体的 target 由 标签定义。
volume 类型的 disk
1
2
3
4
5
<disk type='volume' device='disk'>
<driver name='qemu' type='raw'/>
<source pool='blk-pool0' volume='blk-pool0-vol0'/>
<target dev='hdk' bus='ide'/>
</disk>
file 类型的 disk
1
2
3
4
5
<disk type='file' snapshot='external'>
<driver name="tap" type="aio" cache="default"/>
<source file='/var/lib/xen/images/fv0' startupPolicy='optional' />
<target dev='hda' bus='ide'/>
</disk>
block 类型的 disk
1
2
3
4
5
<disk type='block' device='cdrom'>
<driver name='qemu' type='raw'/>
<target dev='hdd' bus='ide' tray='open'/>
<readonly/>
</disk>
network 类型的 disk
1
2
3
4
5
6
7
8
<disk type='network' device='cdrom'>
<driver name='qemu' type='raw'/>
<source protocol="http" name="url_path">
<host name="hostname" port="80"/>
</source>
<target dev='hde' bus='ide' tray='open'/>
<readonly/>
</disk>

host device assignment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<hostdev mode='subsystem' type='usb'> #USB 设备直接分配
<source startupPolicy='optional'>
<vendor id='0x1234'/>
<product id='0xbeef'/>
</source>
<boot order='2'/>
</hostdev>
<hostdev mode='subsystem' type='pci' managed='yes'> #PCI 设备直接分配
<source>
<address domain='0x0000' bus='0x06' slot='0x02' function='0x0'/>
</source>
<boot order='1'/>
<rom bar='on' file='/etc/fake/boot.bin'/>
</hostdev>

network interface

有几种 interface 类型:
(1)type = ‘network’ 定义一个连接 Virtual network 的 interface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<devices>
<interface type='network'>
<source network='default'/> #虚拟网络的名称为 ‘default’
</interface>
...
<interface type='network'>
<source network='default' portgroup='engineering'/>
<target dev='vnet7'/>
<mac address="00:11:22:33:44:55"/>
<virtualport>
<parameters instanceid='09b11c53-8b5c-4eeb-8f00-d84eaa0aaa4f'/>
</virtualport>

</interface>
</devices>
1
# virsh:attach-interface --domain d-2 --type network --source isolatednet1 --mac 52:53:00:4b:75:6f --config

(2)type=‘birdge’ 定义一个 Bridge to LAN(桥接到物理网络)的interface:前提是主机上存在一个 bridge,该 bridge 已经连到物理LAN。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<interface type='bridge'> #连接到 br0
<source bridge='br0'/>
</interface>
<interface type='bridge'> #连接到br1
<source bridge='br1'/>
<target dev='vnet7'/>
<mac address="00:11:22:33:44:55"/>
</interface>
<interface type='bridge'> #连接到 Open vSwithc bridge ovsbr
<source bridge='ovsbr'/>
<virtualport type='openvswitch'>
<parameters profileid='menial' interfaceid='09b11c53-8b5c-4eeb-8f00-d84eaa0aaa4f'/>
</virtualport>
</interface>
1
#virsh:attach-interface --domain d-2 --type bridge --source virbr0 --mac 52:22:33:44:55:66 --config

(3)type=‘ethernet’ 定义一个使用指定脚本连接到 LAN 的 interface

1
2
3
4
5
6
<devices>
<interface type='ethernet'>
<target dev='vnet7'/>
<script path='/etc/qemu-ifup-mynet'/>
</interface>
</devices>

(4)type=‘direct’ 定义一个直接连到物理网卡(Direct attachment to physical interface)的 interface:需要 Linux macvtap 驱动支持

1
2
3
<interface type='direct' trustGuestRxFilters='no'>
<source dev='eth0' mode='vepa'/>
</interface>

(5)type=‘hostdev’ 定义一个由主机PCI 网卡直接分配(PCI Passthrough)的 interface: 分配主机上的网卡给虚机

1
2
3
4
5
6
7
8
9
10
11
12
<devices>
<interface type='hostdev' managed='yes'>
<driver name='vfio'/>
<source>
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
</source>
<mac address='52:54:00:6d:90:02'/>
<virtualport type='802.1Qbh'>
<parameters profileid='finance'/>
</virtualport>
</interface>
</devices>

network

1
2
3
<bridge name="virbr0" stp="on" delay="5" macTableManager="libvirt"/>
<domain name="example.com" localOnly="no"/>
<forward mode="nat" dev="eth0"/>
  1. bridge:定义一个用于构造该虚拟网络的网桥。
  2. domain:定义 DHCP server 的 DNS domain。
  3. forward: 定义虚拟网络直接连到物理 LAN 的方式. ”mode“指转发模式。

    (1) mode=‘nat’:所有连接到该虚拟网络的虚拟的网络都会经过物理机器的网卡,并转换成物理网卡的地址。

1
2
3
4
5
6
7
8
9
10
11
<network>
<name>default</name>
<bridge name="virbr0" />
<forward mode="nat"/>
<ip address="192.168.122.1" netmask="255.255.255.0">
<dhcp>
<range start="192.168.122.2" end="192.168.122.254" />
</dhcp>
</ip>
<ip family="ipv6" address="2001:db8:ca2:2::1" prefix="64" />
</network>

也可以指定公共的IP地址和端口号。

1
2
<forward mode='nat'><nat><address start='1.2.3.4' end='1.2.3.10'/> </nat> </forward>
<forward mode='nat'><nat><port start='500' end='1000'/></nat></forward>

(2) mode=‘route’:类似于 NAT,但是不使用NAT,而是使用routing table。

1
2
3
4
5
6
7
8
9
10
11
<network>
<name>local</name>
<bridge name="virbr1" />
<forward mode="route" dev="eth1"/>
<ip address="192.168.122.1" netmask="255.255.255.0">
<dhcp>
<range start="192.168.122.2" end="192.168.122.254" />
</dhcp>
</ip>
<ip family="ipv6" address="2001:db8:ca2:2::1" prefix="64" />
</network>

(3) mode=‘bridge’:使用不受 libvirt 管理的bridge,比如主机上已有的bridge;open vswitch bridge;使用 macvtap’s “bridge” 模式

1
2
3
4
5
<network>
<name>host-bridge</name>
<forward mode="bridge"/>
<bridge name="br0"/>
</network>

(4) mode=‘passthrough’:使用 a macvtap “direct” connection in “passthrough” mode 指定主机上的特定网卡用于虚拟网络

1
2
3
4
5
6
7
<forward mode='passthrough'>
<interface dev='eth10'/>
<interface dev='eth11'/>
<interface dev='eth12'/>
<interface dev='eth13'/>
<interface dev='eth14'/>
</forward>

(5) mode=‘hostdev’:直接分配主机上的网络设备。

1
2
3
4
5
6
<forward mode='hostdev' managed='yes'>
<driver name='vfio'/>
<address type='pci' domain='0' bus='4' slot='0' function='1'/>
<address type='pci' domain='0' bus='4' slot='0' function='2'/>
<address type='pci' domain='0' bus='4' slot='0' function='3'/>
</forward>

Libvirt API 的实现

libvirt API 的实现是在各个 Hypervisor driver 和 Storage dirver 内。Hypervisor 驱动包括:

原理实现

整体架构

kvm

kvm 是 linux 内核的模块,需要硬件辅助虚拟化技术,如 cpu 的 Intel-VT,AMD-V;内存相关如 Intel 的 EPT 和 AMD 的 RVI 技术。Guest OS 的 CPU 指令不用再经过 Qemu 转译,直接运行,大大提高了速度,KVM 通过 /dev/kvm 暴露接口,用户程序可以通过 ioctl 函数来访问这个接口。见如下伪代码:

1
2
3
4
5
6
7
8
9
10
open("/dev/kvm")
ioctl(KVM_CREATE_VM)
ioctl(KVM_CREATE_VCPU)
for (;;) {
ioctl(KVM_RUN)
switch (exit_reason) {
case KVM_EXIT_IO:
case KVM_EXIT_HLT:
}
}

kvm 内核模块本身只能提供 cpu 和 内存 的虚拟化,所以他必须搭配 QEMU 才能构成一个完整的虚拟化技术,这就是下面要说的 qemu-kvm。

qemu

Qemu 是一个模拟器,向 Guest OS 模拟硬件,Guest OS 认为自己和硬件直接打交道,其实是同 Qemu 模拟出来的硬件打交道,Qemu 将这些指令转译给真正的硬件。由于所有的硬件都要从 Qemu 里面过一手,因而性能较差。

qemu-kvm

Qemu 将 kvm 整合进来,通过 ioctl 调用 /dev/kvm 接口,将有关 cpu 指令的部分交由内核模块来做。kvm 负责 cpu 虚拟化+内存虚拟化,但kvm不能模拟其他设备。Qemu 模拟IO设备(网卡,磁盘等),kvm 加上 Qemu 之后就能实现真正意义上服务器虚拟化。因为用到了上面两个东西,所以称之为 qemu-kvm。

virtio

Qemu 模拟 I/O 设备,同样会影响这些设备的性能,于是又产生了半虚拟化设备 virtio_blk,virtio_net 来提高设备性能。

Libvirt

libvirt 是目前使用最为广泛的对 kvm 虚拟机进行管理的工具和 API

  • Libvirtd 是一个 daemon 进程,可以被本地的virsh调用,也可以被远程的virsh调用
  • Libvirtd 调用 qemu-kvm 操作KVM 虚拟机

代码实现

Libvirt代码里所定义的主要对象如下图所示。

img

1) VirConnectPtr:代表了一个特定VMM建立的连接。每一个基于Libvirt的应用程序都应该先提供一个URI来指定本地或远程的某个VMM,从而获得一个VirConnectPtr连接。比如xen+ssh://host-virt/代表了通过ssh连接一个在host-virt机器上运行的Xen VMM。拿到virConnectPtr连接后,应用程序就可以管理这个VMM的虚拟机和对应的虚拟化资源,比如存储和网络。

2) VirDomainPtr:代表一个虚拟机,可能是激活状态或者仅仅已定义。

3) VirNetworkPtr:代表一个网络

4) VirStorageVolPtr:代表一个存储卷,通常被虚拟机当做块设备使用。

5) VirStoragePoolPtr:代表一个存储池,用来分配和管理存储卷的逻辑区域。

本机之间的通信

在初始化的过程中,所有的驱动被枚举和注册。每一个驱动都会加载特定的函数为Libvirt API所调用。如下图所示,Application通过URI调用Public API,然后PublicAPI通过使用Driver提供的API接口调用正真的Driver实现。

远程主机之间的通信

Libvirt的目标是支持远程管理,所以到Libvirt的驱动的访问,都由Libvirt守护进程libvirtd处理,libvirtd被部署在运行虚拟机的节点上,通过RPC由对端的remote Driver管理,如下图所示。

img

在远程管理模式下,virConnectionPtr实际上连接了本地的remote Driver和远端的特定Driver。所有的调用都通过remote Driver先到达云端的libvirtd,libvirtd访问对应的Driver。

对照关系

这里 有一个 virsh 命令、Libvirt C API、 QEMU driver 方法 和 QEMU Monitor 命令的对照表(部分):

virsh command Public API QEMU driver function Monitor command
virsh create XMLFILE virDomainCreateXML() qemudDomainCreate() info cpus, cont, change vnc password, balloon (all indirectly)
virsh suspend GUEST virDomainSuspend() qemudDomainSuspend() stop
virsh resume GUEST virDomainResume() qemudDomainResume() cont
virsh shutdown GUEST virDomainShutdown() qemudDomainShutdown() system_powerdown
virsh setmem GUEST MEM-KB virDomainSetMemory() qemudDomainSetMemory() balloon (indirectly)
virsh dominfo GUEST virDomainGetInfo() qemudDomainGetInfo() info balloon (indirectly)
virsh save GUEST FILENAME virDomainSave() qemudDomainSave() stop, migrate exec
virsh restore FILENAME virDomainRestore() qemudDomainRestore() cont
virsh dumpxml GUEST virDomainDumpXML() qemudDomainDumpXML() info balloon (indirectly)
virsh attach-device GUEST XMLFILE virDomainAttachDevice() qemudDomainAttachDevice() change, eject, usb_add, pci_add (all indirectly)
virsh detach-device GUEST XMLFILE virDomainDetachDevice() qemudDomainDetachDevice() pci_del (indirectly)
virsh migrate GUEST DEST-URI virDomainMigrate() qemudDomainMigratePerform() stop, migrate_set_speed, migrate, cont
virsh domblkstat GUEST virDomainBlockStats() qemudDomainBlockStats() info blockstats
- virDomainBlockPeek() qemudDomainMemoryPeek() memsave

操作实践

安装kvm环境

KVM 是基于虚拟化扩展(Intel VT 或者 AMD-V)的 X86 硬件的开源的 Linux 原生的全虚拟化解决方案。KVM 中,虚拟机被实现为常规的 Linux 进程,由标准 Linux 调度程序进行调度; 虚机的每个虚拟 CPU 被实现为一个常规的 Linux 进程。这使得 KMV 能够使用 Linux 内核的已有功能。 但是,KVM 本身不执行任何硬件模拟,需要客户空间程序通过 /dev/kvm 接口设置一个客户机虚拟服务器的地址空间,向它提供模拟的 I/O,并将它的视频显示映射回宿主的显示屏。 目前这个应用程序是 QEMU。

Kvm相关安装包及其作用:

  • qemu-kvm 主要的kvm程序包
  • Python-virtinst 创建虚拟机所需要的命令行工具和程序库
  • virt-manager 用于管理虚拟机的 GUI 界面(可以管理远程 kvm 主机)
  • virt-viewer: 通过 GUI 界面直接与虚拟机交互(可以管理远程 kvm 主机)
  • virsh:基于 libvirt 的 命令行工具(CLI)
  • virt-top 虚拟机统计命令
  • virt-viewer GUI连接程序,连接到已配置好的虚拟机
  • libvirt:提供简单且统一的工具和 API,用于管理虚拟机,屏蔽了底层的复杂结构。(支持 qemu-kvm/virtualbox/vmware)
  • libvirt-client 为虚拟客户机提供的c语言工具包
  • virt-install 基于libvirt服务的虚拟机创建命令
  • bridge-utils 创建和管理桥接设备的工具
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 安装qemu libvirt
yum -y install qemu-kvm qemu-img libvirt virt-install bridge-utils virt-manager
# yum -y install qemu-kvm-tools 没找到

# 查看kvm模块是否被正确加载
[root@VM-64-223-centos ~]# lsmod | grep kvm
kvm_intel 315392 0
kvm 847872 1 kvm_intel
irqbypass 16384 1 kvm

# 开启kvm服务,并且设置其开自动启动
[root@VM-64-223-centos ~]# systemctl enable libvirtd
[root@VM-64-223-centos ~]# systemctl start libvirtd
[root@VM-64-223-centos ~]# systemctl status libvirtd
● libvirtd.service - Virtualization daemon
Loaded: loaded (/usr/lib/systemd/system/libvirtd.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2022-02-24 16:58:36 CST; 6s ago
Docs: man:libvirtd(8)
https://libvirt.org
Main PID: 16881 (libvirtd)
Tasks: 19 (limit: 32768)
Memory: 14.8M
CGroup: /system.slice/libvirtd.service
├─16881 /usr/sbin/libvirtd --timeout 120
├─17009 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf --leasefile-ro --dhcp-script=/>
└─17011 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf --leasefile-ro --dhcp-script=/>


# 设置一下语言环境
[root@VM-64-223-centos ~]# LANG="en_US.UTF-8"

#在根下创建一个目录/data ,(不把虚拟机的文件放在/root或根主要是虚拟机启动之后qemu这些用户没有权限去/root或根下读取虚拟机的配置文件)

1
2
3
# mkdir /data
# cd /data
# wget http://mirrors.ustc.edu.cn/centos/7/isos/x86_64/CentOS-7-x86_64-Minimal-2009.iso

安装虚拟机

安装前要设置环境语言为英文 LANG=”en_US.UTF-8”,如果是中文的话某些版本可能会报错。CentOS 7 在这里修改 /etc/locale.conf。

kvm创建虚拟机,特别注意.iso镜像文件一定放到/data 或者根目录重新创建目录,不然会因为权限报错,无法创建虚拟机。

1
2
3
# 创建一个虚拟磁盘, -f指定格式, 路径是/data/kvm.qcow2 ,大小为30G
[root@VM-64-223-centos data]# qemu-img create -f qcow2 -o preallocation=metadata /data/kvm.qcow2 30G
Formatting '/data/kvm.qcow2', fmt=qcow2 size=32212254720 cluster_size=65536 preallocation=metadata lazy_refcounts=off refcount_bits=16

首先学virt-install命令,在这里使用-help查看,并且只学习重要的,其他的可以后面学习.

1
2
3
4
5
6
7
8
# virt-install -help
-n (name) : 指定虚拟机的名称
-memory (-raw) : 指定内存大小
-cpu : 指定CPU的核数(默认为1)
-cdrom : 指定镜像
-disk: 指定磁盘路径(即预先创建的虚拟磁盘)
-virt-type : 指定虚拟机类型(kvm, qemu , xen)
-network : 指定网络类型

执行创建虚拟机命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# virt-install --virt-type=kvm --name=centos --vcpus=2 --memory=4096 --location=/data/CentOS-7-x86_64-Minimal-2009.iso --disk path=/data/kvm.qcow2,size=20,format=qcow2 --network network=default  --graphics none --extra-args='console=ttyS0' --force

Starting install...
Retrieving file vmlinuz... | 6.5 MB 00:00:00
Retrieving file initrd.img... | 53 MB 00:00:00
Connected to domain centos
Escape character is ^]
[ 4.020055] Freeing initrd memory: 53840k freed
[ 4.032322] PCI-DMA: Using software bounce buffering for IO (SWIOTLB)
[ 4.033010] software IO TLB [mem 0x78b3c000-0x7cb3c000] (64MB) mapped at [ffff9a54f8b3c000-ffff9a54fcb3bfff]
[ 4.034101] RAPL PMU: API unit is 2^-32 Joules, 3 fixed counters, 10737418240 ms ovfl timer
[ 4.034969] RAPL PMU: hw unit of domain pp0-core 2^-0 Joules
[ 4.035550] RAPL PMU: hw unit of domain package 2^-0 Joules
[ 4.036131] RAPL PMU: hw unit of domain dram 2^-16 Joules
[ 4.037025] sha1_ssse3: Using AVX2 optimized SHA-1 implementation
...

上面创建虚拟机命令最终需要配置一下系统基础设置,带 [!] 基本都是要配置的,可以参考 这篇文章 进行配置。配置完成后,即可进入虚拟机安装流程。

查看虚拟机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 查看在运行的虚拟机
[root@VM-64-223-centos ~]# virsh list
Id Name State
------------------------
2 centos running

# 连接到虚拟机的命令
# 如果报错,可能是安装完成之后已经在界面进入了,需要退出那个界面,再登录就好了
# virsh console centos
Connected to domain centos
Escape character is ^]

CentOS Linux 7 (Core)
Kernel 3.10.0-1160.el7.x86_64 on an x86_64

localhost login: root
Password:
Last login: Fri Feb 25 15:39:48 on ttyS0
[root@localhost ~]#

虚拟机基本操作学习

1
2
3
4
5
6
7
8
9
10
11
virt-install          # 生成kvm虚拟机
virsh list # 查看在运行的虚拟机
virsh list –all # 查看所有虚拟机
virsh dumpxml name # 查看kvm虚拟机配置文件
virsh start name # 启动kvm虚拟机
virsh shutdown name # 正常关机:
virsh destroy name # 非正常关机(相当于物理机直接拔掉电源)
virsh undefine name # 删除虚拟机,(彻底删除,找不回来了,如果想找回来,需要备份/etc/libvirt/qemu的xml文件)
virsh define file.xml # 根据配置文件定义虚拟机
virsh suspend name # 挂起,终止
virsh resumed name # 恢复挂起状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
[root@VM-64-223-centos ~]# virsh dumpxml centos
<domain type='kvm' id='2'>
<name>centos</name>
<uuid>556c2f19-2c66-42c3-bedb-5e1cbc469bee</uuid>
<metadata>
<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
<libosinfo:os id="http://centos.org/centos/7.0"/>
</libosinfo:libosinfo>
</metadata>
<memory unit='KiB'>4194304</memory>
<currentMemory unit='KiB'>4194304</currentMemory>
<vcpu placement='static'>2</vcpu>
<resource>
<partition>/machine</partition>
</resource>
<os>
<type arch='x86_64' machine='pc-q35-rhel8.2.0'>hvm</type>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
</features>
<cpu mode='custom' match='exact' check='full'>
<model fallback='forbid'>Cascadelake-Server</model>
<vendor>Intel</vendor>
<feature policy='require' name='ss'/>
<feature policy='require' name='vmx'/>
<feature policy='require' name='pdcm'/>
<feature policy='require' name='hypervisor'/>
<feature policy='require' name='tsc_adjust'/>
<feature policy='require' name='umip'/>
<feature policy='require' name='pku'/>
<feature policy='require' name='md-clear'/>
<feature policy='require' name='stibp'/>
<feature policy='require' name='arch-capabilities'/>
<feature policy='require' name='xsaves'/>
<feature policy='require' name='ibpb'/>
<feature policy='require' name='ibrs'/>
<feature policy='require' name='amd-stibp'/>
<feature policy='require' name='amd-ssbd'/>
<feature policy='require' name='rdctl-no'/>
<feature policy='require' name='ibrs-all'/>
<feature policy='require' name='skip-l1dfl-vmentry'/>
<feature policy='require' name='mds-no'/>
<feature policy='require' name='pschange-mc-no'/>
<feature policy='require' name='tsx-ctrl'/>
<feature policy='disable' name='hle'/>
<feature policy='disable' name='rtm'/>
<feature policy='disable' name='mpx'/>
</cpu>
<clock offset='utc'>
<timer name='rtc' tickpolicy='catchup'/>
<timer name='pit' tickpolicy='delay'/>
<timer name='hpet' present='no'/>
</clock>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash>
<pm>
<suspend-to-mem enabled='no'/>
<suspend-to-disk enabled='no'/>
</pm>
<devices>
<emulator>/usr/libexec/qemu-kvm</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='/data/kvm.qcow2' index='2'/>
<backingStore/>
<target dev='vda' bus='virtio'/>
<alias name='virtio-disk0'/>
<address type='pci' domain='0x0000' bus='0x04' slot='0x00' function='0x0'/>
</disk>
<disk type='file' device='cdrom'>
<driver name='qemu'/>
<target dev='sda' bus='sata'/>
<readonly/>
<alias name='sata0-0-0'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
<controller type='usb' index='0' model='qemu-xhci' ports='15'>
<alias name='usb'/>
<address type='pci' domain='0x0000' bus='0x02' slot='0x00' function='0x0'/>
</controller>
<controller type='sata' index='0'>
<alias name='ide'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x1f' function='0x2'/>
</controller>
<controller type='pci' index='0' model='pcie-root'>
<alias name='pcie.0'/>
</controller>
<controller type='virtio-serial' index='0'>
<alias name='virtio-serial0'/>
<address type='pci' domain='0x0000' bus='0x03' slot='0x00' function='0x0'/>
</controller>
<controller type='pci' index='1' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='1' port='0x8'/>
<alias name='pci.1'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x0' multifunction='on'/>
</controller>
<controller type='pci' index='2' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='2' port='0x9'/>
<alias name='pci.2'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
</controller>
<controller type='pci' index='3' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='3' port='0xa'/>
<alias name='pci.3'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
</controller>
<controller type='pci' index='4' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='4' port='0xb'/>
<alias name='pci.4'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x3'/>
</controller>
<controller type='pci' index='5' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='5' port='0xc'/>
<alias name='pci.5'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x4'/>
</controller>
<controller type='pci' index='6' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='6' port='0xd'/>
<alias name='pci.6'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x5'/>
</controller>
<controller type='pci' index='7' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='7' port='0xe'/>
<alias name='pci.7'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x6'/>
</controller>
<interface type='network'>
<mac address='52:54:00:22:2c:d0'/>
<source network='default' portid='e3de6751-8de8-473c-b2a3-8c06679faea8' bridge='virbr0'/>
<target dev='vnet1'/>
<model type='virtio'/>
<alias name='net0'/>
<address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
</interface>
<serial type='pty'>
<source path='/dev/pts/1'/>
<target type='isa-serial' port='0'>
<model name='isa-serial'/>
</target>
<alias name='serial0'/>
</serial>
<console type='pty' tty='/dev/pts/1'>
<source path='/dev/pts/1'/>
<target type='serial' port='0'/>
<alias name='serial0'/>
</console>
<channel type='unix'>
<source mode='bind' path='/var/lib/libvirt/qemu/channel/target/domain-2-centos/org.qemu.guest_agent.0'/>
<target type='virtio' name='org.qemu.guest_agent.0' state='disconnected'/>
<alias name='channel0'/>
<address type='virtio-serial' controller='0' bus='0' port='1'/>
</channel>
<input type='mouse' bus='ps2'>
<alias name='input0'/>
</input>
<input type='keyboard' bus='ps2'>
<alias name='input1'/>
</input>
<memballoon model='virtio'>
<alias name='balloon0'/>
<address type='pci' domain='0x0000' bus='0x05' slot='0x00' function='0x0'/>
</memballoon>
<rng model='virtio'>
<backend model='random'>/dev/urandom</backend>
<alias name='rng0'/>
<address type='pci' domain='0x0000' bus='0x06' slot='0x00' function='0x0'/>
</rng>
</devices>
<seclabel type='dynamic' model='dac' relabel='yes'>
<label>+107:+107</label>
<imagelabel>+107:+107</imagelabel>
</seclabel>
</domain>

编辑kvm的xml文件, 更改虚拟机 CPU 配置

编辑 kvm 的xml文件,更改虚拟机CPU配置。配置虚拟机的CPU有两种方式

  • 启动的时候指定核数
  • 更改 xml

为了实现CPU的热添加,就需要更改CPU的最大值,热添加的个数不能超过最大值.

1
2
# virsh list --all  # 查看虚拟机
# virsh edit centos # 打开虚拟机的xml文件,找到如下项 vcpu 配置

当前为2,改为自动括容,最大为4

1
<vcpu placement='auto' current="2">4</vcpu>

重启虚拟机,查看CPU信息,确认CPU的个数,再进行CPU热添加

1
2
# virsh shutdown centos
# virsh start centos

此时查看虚拟机 CPU

1
2
3
[root@localhost ~]# cat /proc/cpuinfo | grep processor
processor : 0
processor : 1

动态修改虚拟机 CPU:

1
# virsh setvcpus centos --live --count

再去虚拟机查看 CPU 信息

1
2
3
4
5
[root@localhost ~]# cat /proc/cpuinfo | grep processor
processor : 0
processor : 1
processor : 2
processor : 3

编辑kvm的xml文件, 更改虚拟机内存配置

内存的设置拥有一个气球(balloon)机制,可以增大减少,但是也要设置一个最大值,默认并没有设置最大值,也可以在安装的时候指定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# virsh dominfo centos # 查看虚拟机信息
Id: 3
Name: centos
UUID: 556c2f19-2c66-42c3-bedb-5e1cbc469bee
OS Type: hvm
State: running
CPU(s): 4
CPU time: 14.8s
Max memory: 4194304 KiB
Used memory: 4194304 KiB
Persistent: yes
Autostart: disable
Managed save: no
Security model: none
Security DOI: 0

可以修改 XML 更改虚拟机内存配置

1
2
3
4
5
6
# virsh edit centos
<memory unit='KiB'>6000640</memory> (最大内存为6G)
<currentMemory unit='KiB'>4194304</currentMemory> (当前内存为4G)

# virsh setmem centos 5G (增大或减小)
# 能够在线调整的最大内存不能超过为虚拟机分配的最大内存,否则需要关闭虚拟机上调最大内存

通过镜像创建虚拟机

镜像制作原则

  • 分区的时候,只分一个/根分区,并不需要swap分区,由于虚拟机的磁盘性能就不好,如果设置了swap分区,当swap工作的时候,性能会更差。例如阿里云主机,就没有交换分区。
  • 镜像制作需要删除网卡(eth0)中的UUID
  • 关闭selinux,关闭iptables
  • 安装基础软件的包:net-tools lrzsz screen tree vim wget

创建虚拟机镜像文件

1
2
3
4
5
6
7
8
9
10
11
12
# 复制第一次安装的干净系统镜像,作为基础镜像文件,后面创建虚拟机使用这个基础镜像.
# cp /data/centos.qcow2 /data/centos7.base.qcow2

# 使用基础镜像文件,创建新的虚拟机镜像
# cp /data/centos7.base.qcow2 /data/centos7.113.qcow2

# 创建虚拟机配置文件
# 复制第一次安装的干净的系统镜像,作为基础配置文件.
# virsh dumpxml centos > /data/centos7.base.xml

# 使用基础虚拟机镜像配置文件,创建新的虚拟机配置文件
# cp /data/centos7.base.xml /data/centos.new.xml

编辑新虚拟机配置文件

主要是修改虚拟机文件名,UUID,镜像地址和网卡地址,其中 UUID 在 Linux 下可以使用 uuidgen 命令生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<domain type='kvm'>
<name>centos-new</name>
<uuid>1e86167a-33a9-4ce8-929e-58013fbf9122</uuid>
<devices>
<disk type='file' device='disk'>
<source file='/home/vms/centos.113.img'/>
</disk>
<interface type='bridge'>
<mac address='00:00:00:00:00:04'/>
</interface>
</devices>
</domain>

# virsh define /data/centos.new.xml

如何根据 XML 定义虚拟机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<domain type='kvm'>  //如果是Xen,则type=‘xen’
<name>vm0</name> //虚拟机名称,同一物理机唯一
<uuid>fd3535db-2558-43e9-b067-314f48211343</uuid> //同一物理机唯一,可用uuidgen生成
<memory>524288</memory>
<currentMemory>524288</currentMemory> //memory这两个值最好设成一样
<vcpu>2</vcpu> //虚拟机可使用的cpu个数,查看物理机可用CPU个数:cat /proc/cpuinfo |grep processor | wc -l
<os>
<type arch='x86_64' machine='pc-i440fx-vivid'>hvm</type> //arch指出系统架构类型,machine 则是机器类型,查看机器类型:qemu-system-x86_64 -M ?
<boot dev='hd'/> //启动介质,第一次需要装系统可以选择cdrom光盘启动
<bootmenu enable='yes'/> //表示启动按F12进入启动菜单
</os>
<features>
<acpi/> //Advanced Configuration and Power Interface,高级配置与电源接口
<apic/> //Advanced Programmable Interrupt Controller,高级可编程中断控制器
<pae/> //Physical Address Extension,物理地址扩展
</features>
<clock offset='localtime'/> //虚拟机时钟设置,这里表示本地本机时间
<on_poweroff>destroy</on_poweroff> //突发事件动作
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<devices> //设备配置
<emulator>/usr/bin/kvm</emulator> //如果是Xen则是/usr/lib/xen/binqemu-dm
<disk type='file' device='disk'> //硬盘
<driver name='qemu' type='raw'/>
<source file='/opt/vm/vmdev/fdisk.img'/>
<target dev='vda' bus='virtio'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> //域、总线、槽、功能号,slot值同一虚拟机上唯一
</disk>
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<source file='/opt/vm/vmdev/fdisk2.img'/>
<target dev='vdb' bus='virtio'/>
</disk>
<disk type='file' device='cdrom'>//光盘
<driver name='qemu' type='raw'/>
<source file='/opt/vm/vmiso/ubuntu-15.10-server-amd64.iso'/>
<target dev='hdc' bus='ide'/>
<readonly/>
</disk>

/* 利用Linux网桥连接网络 */
<interface type='bridge'>
<mac address='fa:92:01:33:d4:fa'/>
<source bridge='br100'/> //配置的网桥网卡名称
<target dev='vnet0'/> //同一网桥下相同
<alias name='net0'/> //别名,同一网桥下相同
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> //注意slot值唯一
</interface>

/* 利用ovs网桥连接网络 */
<interface type='bridge'>
<source bridge='br-ovs0'/>
<virtualport type='openvswitch'/>
<target dev='tap0'/>
<model type='virtio'/>
</interface>

/* 配置成pci直通虚拟机连接网络,SR-IOV网卡的VF场景 */
<hostdev mode='subsystem' type='pci' managed='yes'>
<source>
<address domain='0x0000' bus='0x03' slot='0x00' function='0x0'/>
</source>
</hostdev>

/* 利用vhostuser连接ovs端口 */
<interface type='vhostuser'>
<mac address='fa:92:01:33:d4:fa'/>
<source type='unix' path='/var/run/vhost-user/tap0' mode='client'/>
<model type='virtio'/>
<driver vringbuf='2048'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>

<interface type='network'> //基于虚拟局域网的网络
<mac address='52:54:4a:e1:1c:84'/> //可用命令生成,见下面的补充
<source network='default'/> //默认
<target dev='vnet1'/> //同一虚拟局域网的值相同
<alias name='net1'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> //注意slot值
</interface>
<graphics type='vnc' port='5900' autoport='yes' listen='0.0.0.0' keymap='en-us'/> //配置vnc,windows下可以使用vncviewer登录,获取vnc端口号:virsh vncdisplay vm0
<listen type='address' address='0.0.0.0'/>
</graphics>
</devices>
</domain>

参考资料