目录
文章目录
- 目录
- Libvirt
- libvirt API 函数库
- libvirtd Daemon
- 软件架构
- 权限模式
- 运行模式
- XML 格式
- virsh CLI
- Libvirt + QEMU-KVM 环境部署
- HostOS 配置优化
- (可选的)开启 KVM Nested 嵌套虚拟化
- 安装 CentOS GNOME 图形界面
- 安装 Libvirt + QEMU-KVM
- Libvirt 的虚拟网络模式
- (默认)Linux Bridge 虚拟网络方案
- 1. vSwitch 使用 Bridge 模式
- 在 HostOS 上配置虚拟机的桥接网络
- 管理网桥
- 局域网桥
- 2. vSwitch 使用 NAT 模式
- 3. vSwitch 使用 Routed 模式
- 4. vSwitch 使用 Isolated 模式
- (可选)使用 OvS 代替 Linux Bridge
- Libvirt 的 Live Migration
- 网络数据传输层
- 控制层
- KVM 的 Pre-Copy Live Migration 过程
- 虚拟机迁移功能编程示例
Libvirt
Libvirt 是目前使用最为广泛的异构虚拟化管理工具,由 libvirt API 函数库、libvirtd Daemon 这 2 个关键部分组成,还具有一个默认命令行管理工具 virsh。
libvirt API 函数库
Libvirt 采用了面向驱动的架构设计,北向提供了统一的虚拟化资源管理 API,南向通过不同的驱动程序来对接异构的底层虚拟化技术。同时,libvirt API 为多种高级编程语言都提供了编程接口,包括 C、Python、Java、Perl、Ruby 等,具有非常强的可扩展性。
除了官方提供的多种管理工具之外(包括:virsh CLI、virt-manager、virt-viewer、vist-install ),OpenStack 也通过 libvirt API 提供的跨虚拟化平台能力,可以同时支持 QEMU-KVM、VMware、Xen 等多种虚拟化实现。
libvirtd Daemon
libvirtd Daemon 是一个 Multi-Drivers 软件架构,围绕虚拟机这一核心对象提供了计算、存储、网络、安全、监控等虚拟化资源的管理功能。
- 虚拟机管理:用于创建、删除、修改、暂停、恢复、迁移和监视 VM。
- 虚拟网络管理:用于创建、删除和修改虚拟网络,包括 Bridge/OvS、NAT、VLAN 网络。
- 存储管理:用于管理 VM 的 QCOW2 镜像和虚拟磁盘,包括创建、删除、修改和查看存储卷。
- 集群管理:用于管理多台运行了 libvirtd Daemon 的 Host Cluster 上的 VM,可以通过远程连接的方式进行管理。
- 安全策略:用于控制对 Host 和 VM 的访问权限,防止未经授权的访问。
- 监控和统计:用于获取 Host 和 VM 的状态信息和统计数据,包括 CPU 使用率、内存使用率、网络流量、磁盘 I/O 等。
软件架构
libvirtd 的软件架构包括了以下核心组件:
- 监听器(Listener):监听客户端连接请求。默认情况下,libvirtd 使用 TCP/IP 协议,并在本地主机上监听 16509 端口。
- 驱动程序(Driver):libvirtd 使用驱动程序来与不同的虚拟化技术交互。
- 数据库(DB):存储虚拟机相关的信息。默认情况下,libvirtd 使用 SQLite,也支持其他数据库,如:MySQL、PostgreSQL。
权限模式
为了保障 HostOS 的安全性,libvirtd 可以在 2 种权限模式下运行:
- 系统模式:libvirtd 以 Root 权限运行,可以使用 libvirtd 的完整功能,虚拟出物理主机的各种设备。
- 会话模式:libvirtd 以 Non-root 权限运行,以普通系统用户的身份运行。
运行模式
为了支持集群管理和虚拟机迁移功能,libvirtd 具有 2 种运行模式:
- 本地控制管理:Application 和 libvirtd 在同一个 Host 上。
- 远程控制管理:Application 和 libvirtd 不再同一个 Host 上。Local libvirtd 和 Remote libvirtd 之间可以使用 TCP over SSH 协议进行安全通信。
XML 格式
libvirtd 使用 XML 格式来保存一个 VM 所有的配置信息,下面是一个简单的示例:
<domain type='kvm'>
<name>myvm</name>
<memory unit='KiB'>1048576</memory>
<vcpu placement='static'>2</vcpu>
<os>
<type arch='x86_64' machine='pc-i440fx-2.9'>hvm</type>
<boot dev='hd'/>
</os>
<devices>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='/var/lib/libvirt/images/myvm.qcow2'/>
<target dev='vda' bus='virtio'/>
</disk>
<interface type='network'>
<mac address='52:54:00:9b:08:fa'/>
<source network='default'/>
<model type='virtio'/>
</interface>
</devices>
</domain>
- domain 根元素:其中,type=‘kvm’ 表示使用 KVM Hypervisor。
- name 元素:指定 VM 的名称。
- memory 元素:指定 VM 的 vMemory 大小,单位为 KiB。
- vcpu 元素:指定 VM 的 vCPU 个数,其中,placement=‘static’ 表示静态分配方式。
- os 元素:指定 VM 的操作系统类型和 boot 引导顺序。
- devices 元素:指定 VM 的设备信息。
- disk 元素:指定 VM 的 vDisk 信息,包括:类型、文件路径、磁盘编号、驱动程序等信息。
- interface 元素:指定 VM 的 vNIC 信息,包括:虚拟网络、MAC 地址、驱动程序等信息。
virsh CLI
virsh list # 查看在运行的虚拟机。
virsh list --all # 查看所有虚拟机。
virsh console centos72 # 连接虚拟机的 Console。
virsh start centos72 # 启动虚拟机。
virsh reboot centos72 # 重新启动虚拟机。
virsh shutdown centos72 # 优雅关闭虚拟机。
virsh destroy centos72 # 强制关闭虚拟机。
virsh suspend centos72 # 暂停(挂起)虚拟机。
virsh resume centos72 # 恢复被挂起的虚拟机。
virsh undefine centos72 # 删除虚拟机的 XML 配置文件,但不删除虚拟机的磁盘文件。
virsh autostart centos72 # 设置虚拟机随物理机一同启动。
virsh autostart --disable centos72 # 取消虚拟机的开机自启动。
virsh dumpxml centos72 # 查看虚拟机的配置文件。
virsh edit centos72 # 编辑虚拟机的配置文件。
virsh setvcpus # 动态配置虚拟机的 CPU。
virsh setmaxmem # 动态配置虚拟机的 Memory。
Libvirt + QEMU-KVM 环境部署
HostOS 配置优化
- 使用国内 yum 和 epel 镜像源加速。
yum -y install wget
mkdir /etc/yum.repos.d/repo.bk
mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/repo.bk
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
wget -O /etc/yum.repos.d/epel-7.repo http://mirrors.aliyun.com/repo/epel-7.repo
yum clean all
yum makecache
yum install -y epel-release
- 升级 HostOS。
yum update -y && yum upgrade -y
(可选的)开启 KVM Nested 嵌套虚拟化
如果 HostOS 本身就是一个 VM,那么就需要开启 KVM Nested 嵌套虚拟化,使得 HostOS 能够具有与 Host 相同的 CPU 硬件辅助虚拟化特性,才能够在 VM 里面嵌套运行 KVM 虚拟机。
KVM Nested 是一个可通过修改内核参数来启用的功能,它能够使一台 VM 与 Host 可以具相同的 CPU 特性,支持 vmx|svm(AMD) 硬件虚拟化,该特性需要 Linux 内核版本大于 Linux 3.x。
查看是否启动了 Nested:
$ cat /sys/module/kvm_intel/parameters/nested
Y
启用 Nested:
echo 'options kvm_intel nested=1' >/etc/modprobe.d/kvm-nested.conf
# 卸载内核模块
modprobe -r kvm_intel
# 重新加载内核模块
modprobe kvm_intel
NOTE:如果无法重载内核模块,可以考虑直接重启。
如果希望已经存在的 KVM 虚拟机支持嵌套虚拟化,则需要重新编辑虚拟机的 XML 文件,修改其 CPU mode。
# 关机
$ virsh shutdown domain-xxx
# 编辑
$ virsh edit domain-xxx
<cpu mode='host-passthrough'>
# 开机
$ virsh start domain-xxx
登录入虚拟机检查是否有 CPU 穿透:
$ egrep '(vmx|svm)' /proc/cpuinfo
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon rep_good nopl eagerfpu pni pclmulqdq vmx ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust smep erms xsaveopt
安装 CentOS GNOME 图形界面
$ yum groupinstall -y "X Window System"
$ yum groupinstall -y "GNOME Desktop" "Graphical Administration Tools"
$ init 5
安装 Libvirt + QEMU-KVM
$ cat /proc/cpuinfo | egrep 'vmx|svm'
$ setenforce 0
$ sed -i 's/=enforcing/=disabled/g' /etc/selinux/config
$ systemctl disable firewalld.service && systemctl stop firewalld.service && systemctl status firewalld.service
$ yum install -y qemu-kvm libvirt virt-manager virt-install bridge-utils
$ lsmod | grep kvm
# 修改 QEMU 配置,使 Root 用户有文件访问权限
$ vi /etc/libvirt/qemu.conf
...
user = "root"
group = "root"
$ systemctl start libvirtd && systemctl enable libvirtd && systemctl status libvirtd
相关安装包及其作用:
- qemu-kvm:QEMU 的 KVM 分支发行版软件程序,包含了 KVM Kernel Module 和基于 KVM 重构后的 QEMU 模拟器。
- qemu-img:QCOW2 镜像文件命令行工具。
- libvirt:Libvirt 软件程序,提供了一套虚拟化管理工具及 API。
- libvirt-client:Libvirt 的客户端软件程序,最重要的功能之一就是在宿主机关机时可以通知虚拟机也进行关机。
- virt-manager:基于 Libvirt 的 GUI 虚拟机管理软件程序。
- virt-install:基于 Libvirt 的用于创建虚拟机的命令行工具。
- libvirt-python:Libvirt 的 Pyhton API。
- python-virtinst:一套 Python 实现的用于创建虚拟机的命令行工具和程序库。
- virt-viewer:连接虚拟机 Console 的命令行工具。
- virt-top:虚拟机资源使用情况查看命令行工具。
- virt-clone:虚拟机克隆命令行工具。
- libguestfs-tools:GuestOS File System 命令行工具。
- bridge-utils:Linux Bridge 命令行工具。
Libvirt 的虚拟网络模式
(默认)Linux Bridge 虚拟网络方案
libvirtd Daemon 首次启动时,默认会为 VMs 创建一个 vSwitch,表现为一个名为 virbr0 的 Linux Bridge 设备,因为 Linux Bridge 设备是连接到 Linux Kernel TCP/IP stack 中的,所以可以通过查看 Network Interface 的方式来查看到。
并且,这个 vSwitch 默认运行在 NAT 模式下,使 VMs 可以访问外部网络。
1. vSwitch 使用 Bridge 模式
Bridge 模式,将 vSwitch 当作一个 L2 Switch 设备。使 VM 与 Host 处于同一个 LAN 网络,VM 和外部网络之间可以互相访问,外部网络都可以看见这些 VM。
Bridge 模式多用于高级设置,特别是 Host 多个网络接口的情况,将一个 Bridge 与物理网卡进行绑定。
其中,网桥模式是目前比较简单,也是用的比较多的模式,下图是网桥模式下的 VM 的收发包的流程。红色箭头表示数据报文的入方向,过程为:
- 网络数据从 Host pNIC 接收,到达网桥;
- 由于 eth0 与 tap1 均加入网桥中,根据二层转发原则,br0 将数据从 tap1 口转发出去,即数据由 Tap设备接收;
- Tap 设备通知对应的 fd 数据可读;
- fd 的读动作通过 tap 设备的字符设备驱动将数据拷贝到用户空间,完成数据报文的前端接收。
在 HostOS 上配置虚拟机的桥接网络
管理网桥
# 虚拟机管理网网桥 br-mgmt
$ vi /etc/sysconfig/network-scripts/ifcfg-br-mgmt
TYPE=Bridge
BOOTPROTO=static
ONBOOT=yes
DEFROUTE=yes
NAME=br-mgmt
DEVICE=br-mgmt
IPADDR=192.168.1.2
NETMASK=255.255.255.0
GATEWAY=192.168.1.1
DNS1=114.114.114.114
DNS1=8.8.8.8
# 虚拟机管理网物理网卡,绑定到 br-mgmt
$ vi /etc/sysconfig/network-scripts/ifcfg-enp2s0
TYPE=Ethernet
BOOTPROTO=none
ONBOOT=yes
NAME=enp2s0
DEVICE=enp2s0
BRIDGE=br-mgmt
$ systemctl restart network
$ ip a
...
2: enp2s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master br-mgmt state UP group default qlen 1000
link/ether 40:8d:5c:b8:25:4b brd ff:ff:ff:ff:ff:ff
...
7: br-mgmt: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 40:8d:5c:b8:25:4b brd ff:ff:ff:ff:ff:ff
inet 192.168.1.2/24 brd 192.168.1.255 scope global noprefixroute br-mgmt
valid_lft forever preferred_lft forever
inet6 fe80::428d:5cff:feb8:254b/64 scope link
valid_lft forever preferred_lft forever
局域网桥
创建网桥设备:
$ brctl addbr br-lan01
$ brctl addbr br-lan02
配置网桥设备:
$ vi /etc/sysconfig/network-scripts/ifcfg-br-lan01
TYPE=Bridge
DEVICE=br-lan01
ONBOOT=yes
BOOTPROTO=static
$ vi /etc/sysconfig/network-scripts/ifcfg-br-lan02
TYPE=Bridge
DEVICE=br-lan02
ONBOOT=yes
BOOTPROTO=static
$ systemctl restart network
$ brctl show
bridge name bridge id STP enabled interfaces
br-lan01 8000.000000000000 no
br-lan02 8000.000000000000 no
br-mgmt 8000.408d5cb8254b no enp2s0
virbr0 8000.525400088710 yes virbr0-nic
使能网桥设备:
$ ifup br-lan01
$ ifup br-lan02
2. vSwitch 使用 NAT 模式
NAT 模式(默认)将 vSwitch 当作一个 Firewall NAT 设备。底层支撑使用了 iptables nat 的 MASQUERADE(地址伪装)规则类型,而非 SNAT 或 DNAT。IP 地址伪装规则,使得 VMs 可以使用 Host 的 IP 地址访问外部网络,但外部网络无法主动访问到 VMs,因为 VMs 对于外部网络而言是不可见的。
从下图可以看出,vSwitch virbr0 和物理接口之间是没有绑定关系的,而是使用了 Linux Kernel TCP/IP Stack 中的 netfilter 框架完成 NAT 转发。
┌───────────────────────┐
│ HOST │
│ ┌──────┐ │ ┌─────────────────┐
│ │ br0 │─┬──────┐ │ │Virtual Machine 1│
│ └──────┘ │ │ │ │ ┌──────┐ │
│ │ │ ┌───────┐ │ ─ │ │ br0 │ │
│ │ │ │ vnet0 │─│┘ │ └──────┘ │
│ ┌──────┐ │ └───────┘ │ └─────────────────┘
│ │virbr0│ │ ┌───────┐ │ ┌─────────────────┐
│ │ -nic │ └──│ vnet1 │─│┐ │Virtual Machine 2│
│ └──────┘ └───────┘ │ │ │
│ ┌──────┐ │└ ─│ ┌──────┐ │
│ │ eno0 │ │ │ │ br0 │ │
│ └──────┘ │ │ └──────┘ │
│ ┌──────┐ │ └─────────────────┘
│ │ eno1 │ │
│ └──────┘ │
└───────────────────────┘
另外,在 NAT 模式下,Host 内部的虚拟网络环境自成一套体系,所以可以考虑将 DNS 和 DHCP server 部署在 vSwitch 上,Libvirt 可以通过 dnsmasq 程序来实现。
3. vSwitch 使用 Routed 模式
路由模式,将 vSwitch 当作一个 L3 Router 设备,作为 VMs 的 default GW。路由模式中,所有 VMs 都处于一个 LAN 中,通过 vSwitch 来完成 L3 Routing,继而与外部网络进行通信。
值得注意的是,路由模式并不总是理想的,因为外部网络上的其他 Hosts 并不默认使用该 vSwitch,也就无法访问 VMs 了。
4. vSwitch 使用 Isolated 模式
隔离模式,vSwitch 不与外部网络进行互通。所以同一个 vSwitch 中的 VMs 可以互相访问,VMs 也可以访问 Host,但不能访问外部网络。
(可选)使用 OvS 代替 Linux Bridge
- 编辑 ovs-net XML 文件。
$ vi ovs-net.xml
<network>
<name>ovs-net</name>
<forward mode="bridge"/>
<bridge name="ovs-br0"/>
<virtualport type="openvswitch"/>
</network>
-
创建 ovs-net 网桥,并添加一个 Physical NIC 作为 internal Interface。
-
创建 OvS Network,并删除 Default Linux Bridge Network。
virsh net-destory default
virsh net-define ovs-net.xml
virsh net-start default
virsh net-autostart default
virsh net-list --all
- 使用 OvS Network 创建虚拟机。
$ virt-install \
--virt-type kvm \
--name vm01 \
--ram 128 \
--boot hd \
--disk path=cirros.qcow2 \
--network network=ovs-net,mac=52:54:00:aa:69:cc \
--graphics vnc,listen=0.0.0.0 \
--noautoconsole
- Libvirt 会自动的在 OvS Bridge 中添加 vnetX Ports。
Libvirt 的 Live Migration
Libvirt 的 Live Migration 主要分为两个层面:
- 网络数据传输层面。
- 控制层面。
网络数据传输层
-
基于 Hypervisor 的传输,两个 Hypervisor 之间直接建立数据传输连接。优点:数据传输量少。缺点:需要额外配置 Hypervisor Network。需要在防火墙上面打开更多的端口来支持并发迁移,数据不一定支持加密(取决于 Hypervisor 实现)。
-
基于 libvirtd Tunnel 的传输,源主机和目标主机上运行的 libvirtd 之间建立 RPC 隧道来传输数据。数据要先拷贝到 libvirtd,再由 libvirtd 中继到目标主机的 libvirtd。优点:不需要重新配置网络,防火墙上面只需要一个端口就可以支持并发迁移,数据强加密。缺点:相关的数据拷贝多,所有流量都通过一个端口,容易造成网络拥堵。
在 Tunnel 传输模式下,同一份数据需要被拷贝多次,并且所有的流量都通过一个端口,在大内存、高业务(快速增加 RAM 脏数据)的场景下,同样的网络带宽,Tunnel 传输模式迁移速率较慢。因此,对于大内存、业务繁忙的场景下,首选基于 Hypervisor 的传输。
控制层
-
Client 控制:由 Libvirt Client 直接控制迁移。Client 跟源主机 libvirtd 连接,也跟目的主机 libvirtd 连接,当目的主机迁移过程出现异常会反馈给 Client,再由 Client 通知源 libvirtd。整个过程,由 Client 主导控制。
-
源主机 libvirtd 控制:由源主机 libvirtd 主导整个迁移过程。Client 仅作为迁移指令的发起者,将迁移指令异步发送给源主机 libvirtd。如果目的主机迁移过程出现异常会反馈给源主机 libvirtd。这种方式的好处是,Client 故障也不会影响到整个迁移过程。
-
Hypervisor 控制:有源主机 Hypervisor 控制整个迁移过程,Client 向源主机 Hypervisor 发送迁移指令,源主机和目的主机的 libvirtd 不参与控制。这种方式的前提是 Hypervisor 自身支持热迁移,好处在于 Client 和 libvirtd 的故障不会影响到整个迁移过程。
KVM 的 Pre-Copy Live Migration 过程
Step 1. 系统验证目标服务器的存储器和网络设置是否正确,并预保留目标服务器虚拟机的资源。
Step 2. 当虚拟机还在源服务器上运转时,第一个循环内将全部内存镜像复制到目标服务器上。在这个过程中,KVM 依然会监视内存的任何变化。
Step 3. 以后的循环中,检查上一个循环中内存是否发生了变化。 假如发生了变化,那么 VMM 会将发生变化的内存页即 dirty pages 重新复制到目标服务器中,并覆盖掉先前的内存页。在这个阶段,VMM 依然会继续监视内存的变化情况。
Step 4. VMM 会持续这样的内存复制循环。随着循环次数的增加,所需要复制的 dirty pages 就会明显减少,而复制所耗费的时间就会逐渐变短,那么内存就有可能没有足够的时间发生变化。最后,当源服务器与目标服务器之间的差异达到一定标准时,内存复制操作才会结束,同时暂停源系统。
Step 5. 在源系统和目标系统都停机的情况下,将最后一个循环的 dirty-pages 和源系统设备的工作状态复制到目标服务器。
Step 6. 然后,将存储从源系统上解锁,并锁定在目标系统上。启动目标服务器,并与存储资源和网络资源相连接。
虚拟机迁移功能编程示例
import libvirt
import pprint
conn_src = libvirt.open('qemu+tcp://username@src_server/system')
conn_dest = libvirt.open('qemu+tcp://username@dest_server/system')
vm_domain = conn_src.lookupByName('instance_name')
vm_domain.migrate(conn_dest, True, 'instance_name', None, 0)
pprint.pprint(help(vm_domain.migrate))