1. libvirt是什么,以及它到底解决了什么问题
如果你接触虚拟化有一阵子,大概率听过“libvirt”这个名字,但很多人对它的认知停留在“一个管理虚拟化的库”这种模糊层面。我最早接触它时也有同样的困惑:明明有QEMU命令行、有KVM内核模块,为什么还要多出一层libvirt?真正把它用起来之后才明白,libvirt并不是一个可有可无的中间层,而是让虚拟化从“能用”走向“好用”的那块关键拼图。
简单说,libvirt是一套开源的虚拟化平台管理工具集,它提供统一的API、守护进程管理服务(libvirtd)、命令行工具(virsh)和图形管理界面(virt-manager),用来管理多种虚拟化技术,包括KVM、QEMU、Xen、LXC等。它的核心价值在于“统一抽象”——只要你学会用一种方式管理虚拟机,就能用同一套方式去管理底层不同的虚拟化实现,不用关心底层到底是KVM还是Xen。
这篇文章主要面向三类读者:一是刚接触服务器虚拟化、想搞清楚libvirt和KVM/QEMU关系的人;二是已经在用virsh但只停留在启动、关机等基础命令、想深入了解存储、网络和迁移的运维工程师;三是想在自己的工具链或自动化脚本里集成虚拟化管理能力的开发人员。我会从架构、工具链、存储、网络、生命周期管理到问题排查,把整个libvirt的使用逻辑串起来,尽量讲清楚“为什么这么做”而不是只给命令。
2. 整体架构拆解:libvirt、libvirtd与hypervisor之间的关系
2.1 三层架构:API层、守护进程层、驱动层
很多人以为libvirt只是一个库,实际上它由三个协同工作的层次组成。最上层是提供给应用程序和命令行工具的API接口,中间是运行在宿主机上的libvirtd守护进程,最下层是各种hypervisor驱动。
当你执行一条virsh命令时,实际发生的过程是这样的:virsh作为客户端,通过本地socket(默认是/var/run/libvirt/libvirt-sock)或远程socket连接libvirtd,libvirtd接收到请求后,根据当前连接所对应的驱动类型,把请求转发给底层的hypervisor管理接口。比如管理KVM虚拟机时,libvirtd会调用QEMU Monitor Protocol去和QEMU进程通信;管理LXC容器时会调用LXC的API。
这个架构带来的一个直接好处是:所有管理操作都有了一个统一的入口,你可以用同一套命令管理本地和远程宿主机上的虚拟机,也可以用同一套API对接不同的虚拟化后端。我在实际使用中经常通过virsh -c qemu+ssh://root@host/system这种方式管理远程宿主机,底层走的是SSH隧道加密,不需要额外开放管理端口,这在生产环境里非常实用。
2.2 为什么需要libvirt而不是直接调QEMU命令
有人会问:QEMU本身就提供了丰富的命令行参数,直接用qemu-system-x86_64启动虚拟机不就行了?确实可以,但那会陷入几个很现实的困境。
首先,直接使用QEMU命令启动虚拟机,你需要自己维护进程生命周期、监控进程状态、处理异常退出。而libvirt把虚拟机抽象成“域”(domain)这个概念,域的配置、启动、暂停、恢复、迁移都变成了一套有状态的管理流程,libvirtd会负责跟踪每个域的运行状态,并在宿主机重启后根据配置自动恢复。
其次,直接使用QEMU命令时,网络、存储、设备接入等都需要手动指定复杂的参数组合。举个例子,创建一个桥接网络并让虚拟机接入,你可能需要自己创建bridge、绑定物理网卡、配置iptables规则,再在QEMU命令行里指定网卡设备。而libvirt把网络也抽象成了可管理的对象,你只需要定义一个虚拟网络,指定NAT还是桥接模式,虚拟机XML配置里引用这个网络即可,底层网络设备的创建和销毁全部由libvirt自动处理。
再有一个关键点是安全性。libvirt对身份认证和权限控制做了标准化处理,你可以通过polkit配置哪些用户或组能管理虚拟机,也可以通过TLS或SSH加密远程管理通道。如果直接暴露QEMU的monitor接口,安全风险很难把控。
2.3 hypervisor驱动的选择与注意事项
libvirt支持多种驱动,常见的有qemu(管理QEMU/KVM虚拟机)、xen(管理Xen虚拟机)、lxc(管理Linux容器)、esx(管理VMware ESXi)等。你可以通过virsh driver命令查看当前libvirtd编译支持的驱动列表。
我个人的建议是:如果底层用的是KVM,连接URI使用qemu:///system而不是qemu:///session。这两个连接的区别很容易被忽略但非常重要。qemu:///system连接的是系统级的libvirtd实例,拥有宿主机root权限,创建的虚拟机默认以系统服务方式运行;qemu:///session连接的是用户级的libvirtd实例,权限仅限于当前用户,虚拟机生命周期跟随用户会话,不适合生产环境。我见过一些新手在session连接下创建虚拟机,重启宿主机后虚拟机消失,还以为数据丢了,其实就是因为session连接下libvirtd没有在系统启动时自动拉起虚拟机。
3. 核心工具链解析:virsh、virt-manager与配置文件体系
3.1 virsh:日常管理的主战场
virsh是使用频率最高的libvirt命令行工具,它的设计风格类似于shell,进去之后就是一个交互式终端,也可以直接在命令行后面跟子命令执行。
把virsh常用的子命令分成几组,按使用频率排序:
域管理组:list(列出域)、start(启动)、shutdown(优雅关机)、destroy(强制断电)、reboot(重启)、suspend(暂停)、resume(恢复)、undefine(删除配置)、define(从XML定义域)。
设备管理组:attach-disk(挂载磁盘)、detach-disk(卸载磁盘)、attach-interface(挂载网卡)、detach-interface(卸载网卡)、update-device(更新设备配置)。
资源管理组:vcpupin(绑定vCPU到物理核)、setvcpus(调整虚拟CPU数量)、setmaxmem(调整最大内存)、setmem(调整当前内存)、blkdeviotune(磁盘IO限流)、bandwidth(网卡带宽限流)。
虚拟网络与存储组:net-list、net-create、net-destroy、net-define、pool-list、pool-create、pool-destroy、pool-define等。
信息查询组:dominfo(域基本信息)、domblklist(磁盘设备列表)、domiflist(网卡设备列表)、domstats(域详细统计)、vcpinfo(虚拟CPU信息)、dommemstat(内存统计)。
至少要把前四组命令练熟,日常运维基本就够用了。
3.2 域配置XML:libvirt的核心载体
在libvirt的世界里,虚拟机的一切配置都编码在XML文件里。你通过define命令读入一个XML,libvirtd就按照这个描述去创建域;通过edit命令修改XML,libvirtd会尝试把变更应用到运行中的域(能热变更的属性会立即生效)。
一个最小的域XML长这样:
<domain type='kvm'> <name>test-vm</name> <memory>524288</memory> <vcpu>2</vcpu> <os> <type arch='x86_64'>hvm</type> <boot dev='hd'/> </os> <devices> <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/var/lib/libvirt/images/test-vm.qcow2'/> <target dev='vda' bus='virtio'/> </disk> <interface type='network'> <source network='default'/> <model type='virtio'/> </interface> </devices> </domain>这里面有几个容易踩坑的地方。memory单位是KiB,不是字节也不是MB。上例中524288表示512MiB。如果忘了这一点,创建出来的虚拟机内存会比预期小很多。vcpu配置的是虚拟CPU的数量,但并没有规定vCPU如何映射到物理核。要查看当前vCPU绑定情况,用virsh vcpupin test-vm。
disk节点的source file路径指向虚拟机磁盘镜像的实际位置。type='file'表示磁盘后端是普通文件,type='block'表示后端是块设备(如/dev/sdb)。这两种类型在性能表现和操作方式上有差异,block设备通常能获得更低的延迟,但管理和迁移的灵活性不如文件镜像。
interface节点的network属性指向一个虚拟网络的名称。model type='virtio'表示使用半虚拟化网卡,性能远优于模拟的e1000网卡。我在实际项目中几乎全部使用virtio网卡和virtio磁盘,因为无论在吞吐量还是CPU占用率上,virtio的表现都明显更好。
3.3 virt-manager:图形化的补充工具
虽然日常自动化运维以virsh为主,但在快速创建测试虚拟机、直观查看虚拟机画面和资源消耗趋势时,virt-manager依然很好用。它本质上是libvirt API的图形化客户端,所有你在virt-manager里能做的事,后台都是通过libvirt API完成的。
我个人习惯的分工是:快速原型验证用virt-manager,生产环境所有操作走virsh或脚本。理由很简单,virt-manager方便直观,但不适合批量操作和审计跟踪;virsh命令可以被记录下来、脚本化、版本化,这对生产环境至关重要。
4. 存储池与存储卷:如何管理系统镜像和磁盘
4.1 存储池的基本概念与类型
libvirt把存储管理抽象为两层:存储池(storage pool)和存储卷(storage volume)。存储池是宿主机上一块用来存放虚拟磁盘的存储空间,可以是一个目录、一个LVM卷组、一个iSCSI目标、一个磁盘分区或一个ceph池;存储卷则是这个池里划分出来的具体存储资源,通常对应一个qcow2文件或一个逻辑卷。
这种抽象的价值在于:你不需要关心存储具体落在哪种介质上,只需在虚拟机XML里引用存储卷的名称,libvirt会负责解析卷到物理位置的映射。当底层存储需要迁移时,只要卷的导出方式不变,虚拟机的配置就不用改。
查看当前宿主机上已有的存储池:
virsh pool-list --all创建一个基于目录的存储池:
mkdir -p /data/vm-images virsh pool-define-as vm_images dir - - - - /data/vm-images virsh pool-build vm_images virsh pool-start vm_images virsh pool-autostart vm_imagespool-define-as后面跟的参数依次是:池名称、类型(dir表示目录)、格式(目录类型不需要)、源主机、源路径、目标路径。pool-build会在宿主机上创建实际目录,如果目录已存在这一步可以省略。pool-start启动池,pool-autostart设置开机自动启动。
4.2 创建与管理存储卷
存储卷的创建和删除天然属于存储池的管理范畴。在目录池里,存储卷就是一个文件,你可以指定容量、格式和分配策略。
virsh vol-create-as vm_images test-node1.qcow2 50G --format qcow2这条命令在vm_images池里创建一个名为test-node1.qcow2、大小为50G、格式为qcow2的存储卷。qcow2格式支持写时复制、快照和压缩,是KVM环境下的默认选择。另一个常见选项是raw格式,raw格式没有元数据开销,性能略好,但无法做快照,也缺少qcow2的精简分配能力。
如果采用qcow2格式,创建时默认并没有预分配全部50G空间,而是按需增长。这在节省宿主机存储空间方面非常有用,但也带来一个隐患:虚拟机磁盘镜像的实际占用空间会逐渐膨胀,而且膨胀之后不会自动缩回去。我遇到过宿主机磁盘被快撑爆的情况,排查下来就是几个大容量的qcow2镜像不断增长。要监控各镜像的实际占用,可以用以下命令:
du -h /data/vm-images/*.qcow2也可以用libvirt的API查询卷信息:
virsh vol-info --pool vm_images test-node1.qcow2配合定期巡检脚本,可以及时发现镜像膨胀或磁盘不足的问题。
4.3 磁盘挂载与热插拔
把存储卷挂载到虚拟机上有两种方式:冷挂载(虚拟机停机时修改配置)和热挂载(虚拟机运行时动态添加)。
冷挂载的做法是编辑域XML,在devices节点下增加disk条目,然后执行virsh define让配置生效。
热挂载用以下命令:
virsh attach-disk test-vm /data/vm-images/test-node1-data.qcow2 vdb --cache none --target bus=virtio这里需要注意几个参数。vdb表示目标设备名,在一台虚拟机里,第一个virtio磁盘是vda,第二个是vdb,以此类推,不能和已有设备重名。--cache none表示关闭宿主机页缓存,对数据库这类有自身缓存机制的应用负载比较推荐,避免双重缓存导致的内存浪费和性能波动。
卸载磁盘时,先确保虚拟机内部已经卸载掉对应文件系统,否则会丢数据:
# 在虚拟机内部执行 umount /data # 然后在宿主机上执行 virsh detach-disk test-vm vdb4.4 克隆与快照
libvirt的快照功能分为内部快照和外部快照。内部快照把快照状态写入qcow2镜像文件本身,执行简单,但快照文件会随镜像一起增大;外部快照将快照状态保存为独立的qcow2文件,管理更灵活,但也更复杂。日常使用中,内部快照足够应付大多数场景。
创建快照:
virsh snapshot-create-as test-vm snap_before_upgrade "snapshot before system upgrade"列出快照:
virsh snapshot-list test-vm恢复快照:
virsh snapshot-revert test-vm snap_before_upgrade一个非常实用的操作是先创建一个干净系统盘的快照,然后基于这个快照反复做测试,测试完一键恢复。配合qcow2的快照机制,整个流程既快又省空间。
克隆虚拟机时,推荐使用virt-clone这个辅助工具:
virt-clone --original test-vm --name test-vm-clone --file /data/vm-images/test-vm-clone.qcow2virt-clone会复制原始虚拟机的配置,并重新生成新的磁盘镜像。注意克隆前原始虚拟机最好是关机状态,否则镜像数据可能不一致。
5. 虚拟网络配置:NAT模式、桥接模式与带宽限流
5.1 默认网络与NAT模式
libvirt安装完成后通常会自动创建一个名为default的NAT虚拟网络。它的工作机制是:libvirtd在宿主机上创建一个虚拟网桥virbr0,并配置NAT规则,所有连接到default网络的虚拟机通过virbr0访问外部网络,外部网络无法直接访问虚拟机。
查看虚拟网络:
virsh net-list --all查看default网络的配置:
virsh net-dumpxml default默认网络的IP段通常配置为192.168.122.0/24,地址池从192.168.122.2到192.168.122.254。虚拟机获得的IP由内嵌的dnsmasq服务动态分配。
NAT模式最典型的应用场景是开发环境和测试环境。它不需要物理网络介入,只要宿主机能上网,虚拟机就能上网。缺点是外部无法主动连接虚拟机内部服务,需要做端口转发才能暴露服务。
5.2 桥接模式:生产环境的正确选择
生产环境需要虚拟机与物理网络直接互通,这时需要配置桥接模式。桥接的核心思路是创建一个Linux bridge,把宿主机物理网卡加入桥接,再让虚拟机网卡也接入同一个桥,从网络视角看虚拟机就像一台直接连在交换机上的物理机。
创建桥接网络的推荐做法是直接编辑宿主机的网络管理配置,把物理网卡纳入桥接。以常见的NetworkManager为例,需要创建一个br0连接并配置桥接成员。但这里有一个关键问题:libvirt自己创建的桥接网络如果不做额外配置,默认不会有物理网卡的出口。因此实际使用中,我更推荐把桥接创建交给NetworkManager或systemd-networkd完成,然后在libvirt的虚拟网络配置里使用坏桥接方式。
定义桥接虚拟网络时,不需要libvirt创建网桥,而是直接引用已经存在的桥接设备:
<network> <name>br0-net</name> <forward mode='bridge'/> <bridge name='br0'/> </network>然后配置虚拟机interface节点引用这个网络。桥接模式下,虚拟机的IP通常由物理网络里的DHCP服务器分配,libvirt不参与IP管理。
5.3 自定义NAT网络与跨主机通信
如果你需要多个虚拟机组网,又不想用默认网络,可以自定义NAT网络。一个常见的场景是模拟一个私有子网,让里面的虚拟机只通过宿主机与外网交互。
定义一个自定义NAT网络:
cat > /tmp/net-isolated.xml << 'EOF' <network> <name>isolated</name> <ip address='10.10.88.1' netmask='255.255.255.0'> <dhcp> <range start='10.10.88.100' end='10.10.88.200'/> </dhcp> </ip> </network> EOF virsh net-define /tmp/net-isolated.xml virsh net-start isolated virsh net-autostart isolated由于没有配置forward规则,这个网络里的虚拟机只能互相通信,也能和宿主机通信,但出不了外网。如果要把网络升级为NAT模式,在network节点里加上forward mode='nat'即可。
5.4 带宽限流与QoS
libvirt允许在虚拟网络层面配置带宽限制,也可以直接在接口设备上配置。生产环境中,为了防止某台虚拟机占用过多带宽影响其他业务,我通常会在接口层做限流。
在虚拟机XML的interface节点里加一行:
<bandwidth> <inbound average='10240' peak='20480' burst='5120'/> <outbound average='10240' peak='20480' burst='5120'/> </bandwidth>average单位是Kbps,peak是峰值速率,burst是突发大小。测试下来,限流的依据是TC(Traffic Control),对TCP流量效果比较明显。注意限流对UDP流量控制效果有限,而且如果虚拟机里跑的是延迟敏感型应用,限流值设置过小会引入不小的时延,需要根据业务特性谨慎设定。
6. 域生命周期管理与在线迁移实战
6.1 标准生命周期管理操作
把域的生命周期梳理清楚,是排障的基础。一个域从定义到销毁,大致经历:定义(define)→ 启动(start)→ 运行(running)→ 暂停(suspend)/恢复(resume)→ 关机(shutdown)→ 销毁(destroy)→ 删除配置(undefine)。
每次操作虚拟机的状态变化,都对应libvirtd里的一个状态机。了解状态机的好处是你能准确判断当前域处于什么状态,以及下一步应该执行什么操作。
我用一个表格整理各状态之间的转换关系:
| 当前状态 | 操作 | 目标状态 | 说明 |
|---|---|---|---|
| 未定义 | define | 关机 | 只保存配置,不启动 |
| 关机 | start | 运行 | 正常启动 |
| 运行 | shutdown | 关机 | 向guest发送ACPI关机信号 |
| 运行 | destroy | 关机 | 强制断电,相当于拔电源 |
| 运行 | suspend | 暂停 | 内存状态保存,CPU暂停 |
| 暂停 | resume | 运行 | 恢复执行 |
| 运行 | reboot | 运行 | 重启,会先执行guest内部关机流程 |
6.2 在线迁移:从一台宿主机搬到另一台
虚拟机迁移是libvirt的优势场景。在线迁移(live migration)可以在虚拟机不停机的情况下,把整个域的内存状态和磁盘数据从源宿主机迁移到目标宿主机。
在线迁移的前提条件比较严格:
- 源和目标宿主机都需要运行libvirtd,且版本兼容
- 底层hypervisor类型一致,比如源和目标都用KVM
- 虚拟机磁盘镜像必须存放在双方都能访问的共享存储上(如NFS、ceph)
- 网络连通性良好,迁移流量通常走专门的管理网络
执行在线迁移的命令:
virsh migrate --live test-vm qemu+ssh://root@192.168.10.20/system --verbose迁移过程中,源宿主机和目标宿主机之间会传输内存镜像,传输完成后目标宿主机接管虚拟机运行,源宿主机上的虚拟机实例被清理。
我踩过的一个坑是:迁移文件使用NFS共享存储时,NFS网络一抖动,迁移就会超时甚至失败。后来把NFS挂载参数加上了hard和intr选项,稳定性好了很多。另外迁移超时时间不要太长,默认值在某些环境下不够用,可以在migrate命令里用--timeout参数调整,但设置过大会让故障发现变得很慢,需要权衡。
6.3 自动启动配置与开机顺序
生产环境中许多虚拟机需要在宿主机重启后自动拉起。设置自动启动的命令:
virsh autostart test-vm需要注意的是,libvirt默认按域定义的顺序逐个启动,如果你的虚拟机之间有依赖关系(比如应用服务器依赖数据库服务器先启动),光靠autostart不够。这时可以用virsh nodedev序,或者更可靠的做法是在每台虚拟机的启动脚本里加健康检查,等依赖服务就绪后再执行下一步。依赖问题本质上是业务层面的问题,宿主机层面的autostart只解决“是否启动”,不解决“何时启动”。
7. 权限认证与远程管理的安全加固
7.1 本地管理权限配置
libvirtd对本地用户的权限控制通过polkit完成。默认规则下,root用户拥有全部权限,普通用户需要加入libvirt组才能管理系统级的libvirtd实例。
把用户加入libvirt组:
usermod -aG libvirt username组名的具体名称可能因发行版不同有差异,有些系统是libvirt,有些是libvirtd或kvm。加入组之后需要重新登录会话才能生效。
如果你希望特定用户能管理虚拟机但不想给它完整root权限,可以编辑/etc/polkit-1/rules.d/目录下的规则文件。下面这个规则允许名为ops的组拥有虚拟化管理权限:
polkit.addRule(function(action, subject) { if (action.id.indexOf("org.libvirt") === 0 && subject.isInGroup("ops")) { return polkit.Result.YES; } });7.2 远程管理连接方式
libvirt支持多种远程连接传输方式。我用得最多的是SSH传输和TLS传输。
SSH传输的URI格式是qemu+ssh://用户名@宿主机IP/system,优点是无需额外配置libvirtd的监听端口,直接复用SSH认证,适合临时管理和中小规模环境。
配置TLS传输需要生成CA证书、宿主机证书和客户端证书,并在libvirtd.conf里开启listen_tls和listen_tcp。配置完成后,客户端连接URI为qemu+tls://宿主机名/system。
两种方式的选择标准很简单:临时管理用SSH,长期稳定的自动化平台用TLS。TLS的劣势是证书管理有维护成本,但优势是独立于SSH端口,不依赖系统的登录账号体系,在安全审计和权限隔离上更可控。
7.3 安全加固的几条经验
第一,libvirtd默认只监听本地socket,远程管理务必通过TLS或SSH,不要直接打开tcp监听。如果非要开tcp,一定要配合TLS证书和防火墙白名单。
第二,控制某个大宿主机上的虚拟机数量,不要一味堆部署密度。虚拟机的内存和CPU开销相对可控,但IO争抢问题在虚拟机数量增多后会变得明显,尤其共享存储场景下更突出。
第三,定期检查libvirtd日志,默认日志路径是/var/log/libvirt/libvirtd.log。日志里会记录所有连接、认证失败和配置变更操作,是排障和安全审计的第一手来源。
8. 常见问题与排查技巧实录
8.1 虚拟机无法启动,报权限错误
症状:virsh start test-vm提示internal error: process exited while connecting to monitor,或者报Permission denied。
排查路径:先看/var/log/libvirt/qemu/test-vm.log日志,里面往往能看到QEMU启动时的详细错误。常见原因是磁盘镜像文件权限不对,libvirtd以root运行,但QEMU进程以动态生成的用户运行,镜像文件的属主或目录权限不满足要求。解决办法是把镜像目录权限改为755,镜像文件属主改为运行QEMU的用户,或者直接调整/etc/libvirt/qemu.conf里的user和group配置。
8.2 网状网络起不来,提示bridge不存在
症状:virsh net-start br0-net失败,报bridge br0 not found。
这个报错很常见,原因是libvirt的坏桥接网络只引用已有bridge设备,如果你使用的bridge不存在或名称写错,就会启动失败。解决办法是先用ip link show br0确认bridge是否真实存在,如果在NetworkManager里配置了br0但没生效,要检查NetworkManager的桥接扩展是否安装。
还有一种场景:libvirt创建的NAT网络启动失败,报iptables规则冲突。经常是之前配置过其他防火墙管理工具,和libvirt的iptables规则互相冲突。排查思路是检查iptables -t nat -L,清理掉重复的规则或调整firewalld的配置。
8.3 虚拟机内部网卡无法获取IP
症状:虚拟机启动后,内部ifconfig只有回环地址,拿不到DHCP分配的IP。
排查步骤:先在外面看虚拟机网卡是否正常连接,virsh domiflist test-vm;再进虚拟机检查网卡是否识别;能识别的话手动配置IP测试连通性。如果发现虚拟机网卡没有对应到虚拟网络的设备上,通常是虚拟机的interface配置有误,比较常见的是把source network名称写错。如果虚拟机内部网卡被识别但拿不到IP,多半是dhclient服务没启动或防火墙拦了DHCP广播,在虚拟机内手动dhclient重新获取一次可以验证。
8.4 磁盘镜像占用空间膨胀
症状:宿主机磁盘空间告警,排查下来是qcow2镜像文件变大了。
qcow2镜像删除文件后不会自动瘦身,这是设计特性。要回收空间,需要执行qemu-img的收缩操作。做法是先确认虚拟机内部已经删除了不再需要的文件,然后关机或在线执行fstrim(需要virtio-scsi控制器且guest支持discard),最后用qemu-img convert把镜像重新生成一遍:
qemu-img convert -O qcow2 /data/vm-images/test-vm.qcow2 /data/vm-images/test-vm-compact.qcow2转换完成后校验镜像完整性再替换原文件。这个操作最好在虚拟机停机状态下执行,避免数据不一致。
8.5 迁移长时间不结束
症状:virsh migrate命令执行后长时间停留在迁移状态,进度不变。
一般原因有两种:一是迁移网络带宽受限,IO流量占满了链路;二是共享存储本身IO延迟高,脏页速度赶上不内存写入速度。我的排查习惯是先在源宿主机上查看迁移线程和网络流量,再用qemu的迁移统计接口看关键指标,如果发现脏页率持续偏高,考虑调大迁移的带宽上限,或者在业务低峰期执行迁移。调大带宽上限的做法是在migrate命令里添加--bandwidth参数,单位是MiB/s。
8.6 virsh list看不到任何虚拟机
症状:virsh list输出为空,但宿主机上明明有QEMU进程在跑。
最常见的原因是使用了session连接而不是system连接。virsh list默认连接的URI取决于环境变量LIBVIRT_DEFAULT_URI和编译配置,如果没有显式指定,可能走qemu:///session。用virsh -c qemu:///system list --all查看系统连接下的虚拟机,如果能看到,就把连接URI的配置固化到/etc/libvirt/libvirt.conf里:
uri_default = "qemu:///system"这个问题几乎每个刚接触libvirt的人都会遇到一次,遇到之后记住原因,以后就不会再犯了。
9. 最后分享一点个人体会
如果你刚上手libvirt,我的建议是先从virsh的交互模式开始,用domain define、net-define、pool-define把配置文件和概念之间的关系摸清楚,再逐步尝试attach-disk、vol-create这类操作类命令。熟练之后,就会发现libvirt的很多设计其实非常克制:它不替你做决策,而是把管理能力以一致的方式暴露给你,剩下的靠你对虚拟化本身的理解。
我踩过比较深的坑集中在存储和网络这两个层面。存储看似简单,但qcow2的膨胀特性和存储池的autostart设置如果不重视,容易在关键时刻掉链子;网络更是如此,NAT模式默认网络顺手,但生产环境迁到桥接模式时需要打通物理网络和防火墙开关,涉及到的事比想象中多。建议无论什么规模的环境,都把存储池、网络配置、域配置这三类配置文件纳入版本管理,每次变更留痕。这一条习惯,后期省下的排障时间远超你最初投入的那点功夫。