ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

libvirt XML配置详解:域、存储池与网络

libvirt XML配置详解:域、存储池与网络 直接先说明白libvirt这套东西不管你是用virt-manager在界面上点来点去还是用OpenStack这种云平台在背后批量创建虚拟机最终落到hypervisor上的操作指令几乎都是从一个XML描述文件来的。换句话说XML就是libvirt和KVM/QEMU之间沟通的“施工图纸”。很多人玩虚拟机卡在第一步不是不会敲virsh命令而是看不懂那张XML里各个标签到底在干什么、为什么这么写。这篇就把libvirt XML配置文件里最常见的域domain、存储池storage pool、虚拟网络network三类配置逐一拆开把标签含义、参数选型、踩坑经验都讲清楚适合刚接触KVM虚拟化、或者已经被XML配置折腾过的运维和开发同学参考。1. libvirt为什么选择XML作为配置载体1.1 一份配置处处可迁移libvirt管理的不只是KVM/QEMU一家Xen、LXC、VirtualBox等后端它都能接。要是每种hypervisor都搞一套私有格式的配置文件那上层工具比如virt-manager、OpenStack Nova得写一堆适配层维护成本很高。XML是结构化文本有明确的层级libvirt就把它定义成一套“通用中间描述格式”上层只跟XML打交道由libvirt后端驱动去翻译成各家hypervisor的动作。所以同一份XML换个宿主机、换套虚拟化后端只要硬件兼容虚拟机照样能起来。这种“配置与实现解耦”的思路是我认为libvirt选XML最核心的原因。还有一个实际好处XML可以随时给人看、给人改、做版本管理也能轻松做diff对比。我维护的宿主机上所有虚拟机配置都存在/etc/libvirt/qemu/目录一台机器一个XML文件。备份就打包目录恢复就直接把文件拷回去重新define比在数据库里存一份二进制配置直观得多。要是某天虚拟机起不来先看XML有没有被改坏是排查问题的第一直觉。1.2 配置文件的分类与整体层次libvirt的XML配置大体分三类分别对应宿主机上的不同资源域配置domain定义一台虚拟机包括虚拟CPU、内存、磁盘、网卡、显示设备等路径在/etc/libvirt/qemu/文件名一般是虚拟机名.xml。存储池配置storage pool定义宿主机上的存储资源池比如一个目录、一块LVM卷组、一台iSCSI靶机路径在/etc/libvirt/storage/。虚拟网络配置network定义NAT网络、桥接网络、隔离网络路径在/etc/libvirt/qemu/networks/。域配置是我们平时打交道最多的。一个典型的domain XML大致结构是domain typekvm namevm01/name memory unitGiB8/memory vcpu placementstatic4/vcpu os type archx86_64 machinepc-q35-7.2hvm/type boot devhd/ /os devices disk typefile devicedisk driver nameqemu typeqcow2/ source file/var/lib/libvirt/images/vm01.qcow2/ target devvda busvirtio/ /disk interface typenetwork source networkdefault/ model typevirtio/ /interface /devices /domain这个骨架从上到下大致是创建者信息、性能参数、操作系统引导参数、设备列表。libvirt的XML解析器比较严格标签不匹配或属性值写错virsh define直接报错。想快速生成一份合法的初始配置建议先让libvirt自己生成再改后面第4部分细说。2. 虚拟机域domainXML核心配置逐段拆解2.1 基础信息与通用属性domain标签有两个属性值得注意type和id。type表示后端hypervisor类型KVM环境就是typekvmid是运行时的临时编号只读不需要手工写它是libvirt给正在运行的虚拟机临时分配的。name是虚拟机名字字符集一般限制在字母、数字、-、_不能有中文和空格。uuid建议保留因为OpenStack这类平台会生成严格唯一的UUID迁移或纳管别的平台时靠它识别身份。在name后面还有一个容易被忽略的小标签title和description它们不会参与任何运行逻辑纯粹是给人看的备注。我习惯在title里写这台机器所属的业务线description里写部署日期和负责人。后续交接、巡检的时候不用开监控系统也能快速知道这机器是干嘛的。2.2 内存与CPU分配从“能用”到“合理”内存配置核心是两个标签memory表示最大内存currentMemory表示当前分配内存。如果两个值不一样就意味着允许通过热插拔内存把最大内存逐步加满。这一点在生产环境特别实用需要临时扩容内存时不用关机就能加。但要注意热插拔内存不是无限支持的宿主机内核、虚拟机内操作系统都要开好对应支持而且内存条粒度和对齐策略也影响可插拔的单位。CPU部分vcpu标签的placement属性决定CPU是怎么分配的static是每个vCPU固定分配一个物理核auto则让内核按照负载动态迁移。性能敏感的业务我建议用static配合vcpupin做绑核让vCPU和物理核一对一固定减少上下文切换和缓存抖动。反之如果宿主机上是大量负载不高的小虚拟机auto更划算能让CPU资源被充分复用。cpu标签还能定义CPU模型比如cpu modehost-passthrough topology sockets1 cores4 threads1/ /cpuhost-passthrough表示虚拟机直接使用宿主机物理CPU的全部特性性能和运行环境匹配度最高。但代价是虚拟机一旦live migration到一台CPU型号不同的宿主机上就可能起不来。host-model是libvirt从宿主机CPU能力里挑一个最接近的标准模型来模拟兼容性更好。团队内部如果没有统一CPU型号我一般用host-model稳一点。2.3 磁盘设备驱动、总线、控制器要对应虚拟机的磁盘配置是domain XML里最讲究的一块因为它直接关联性能和兼容性。一个常见的disk长这样disk typefile devicedisk driver nameqemu typeqcow2 cachenone ionative/ source file/var/lib/libvirt/images/vm01.qcow2/ target devvda busvirtio/ boot order1/ /disktypefile表示后端是一个普通文件镜像devicedisk表示它是块磁盘而不是光驱cdrom或软驱。driver里的cache参数生产上我通常写none或者writeback前者对一致性和IOPS更稳妥后者性能更好但掉电有轻微风险测试环境无所谓数据库主机就别赌了。ionative让QEMU用宿主机的原生AIO方式访问镜像相比默认的线程池模拟IO路径更短。target里的dev名字其实不完全由你说了算它受bus类型约束。busvirtio的设备Linux一般会按顺序识别成vda、vdb所以第一块盘写vda第二块写vdb不能跨总线乱取。Windows虚拟机里面需要额外装virtio驱动否则看不到盘。跨虚拟化平台迁移时bus是最大的坑SATA盘识别成sdavirtio盘识别成vda迁移后fstab如果还挂着旧设备名系统直接进不去。所以业务侧能用UUID挂载就尽量用UUID。2.4 网络接口三种最常用的连接方式domain XML里的interface决定虚拟机网卡怎么连到宿主机网络。常用的三兄弟是typenetwork连接到一个libvirt虚拟网络比如default NAT网络适合虚拟机需要出外网、但不想占用局域网IP的场景。源网络名称写在source networkdefault/。typebridge直接桥接到宿主机物理网卡对应的bridge设备上虚拟机就像局域网里的一台独立机器。源设备名写在source bridgebr0/。typedirect用macvtap直通宿主机物理网卡性能比bridge好但宿主机和虚拟机之间不能直接通信macvtap的天然限制这个坑很多人踩过。model typevirtio/是我目前的标准选择virtio半虚拟化驱动的吞吐量和CPU占用都比模拟e1000要好太多。给Windows虚拟机装系统时如果引导阶段就需要网络可以考虑先用e1000装着系统起来后再换virtio并安装驱动不然安装器可能找不到网卡。2.5 图形、控制台与附加设备devices里通常还有一个graphics标签定义VNC或Spicegraphics typevnc port-1 listen0.0.0.0 autoportyes listen typeaddress address0.0.0.0/ /graphicsport-1加上autoportyes表示让libvirt自动分配空闲端口而不是写死5900之类。多台虚拟机同时开VNC写死了必然冲突。access控制这里额外提一嘴VNC默认不加密听着都有点刺激。生产上要么把VNC绑到内网管理网段要么配合SSH隧道访问总之别裸奔在公网。serial和console标签用来接串行控制台对排障极其有用。Linux虚拟机内核加上consolettyS0启动参数后宿主机上执行virsh console 虚拟机名就跟插了一根真实串口线一样能看到完整的内核启动日志。网络全挂、SSH不通的时候这是最后一根救命稻草。3. 存储池与虚拟网络的XML配置3.1 存储池统一管理镜像存放空间存储池配置不太需要天天改但它能省掉大量重复劳动。最简单的目录型存储池长这样pool typedir nameimages/name target path/var/lib/libvirt/images/path /target /pool在命令行里用virsh pool-define-as images dir - - - - /var/lib/libvirt/images也能创建同样的池。有了存储池之后创建虚拟机磁盘就不用写绝对路径了直接virsh vol-create-as images vm02.qcow2 40G --format qcow2libvirt会自动帮你把卷放到池对应的目录里。OpenStack对接libvirt后端时也是通过存储池管理Ceph、LVM等后端存储所以这个概念不只是单机运维的知识。需要留意的点是pool类型一旦确定后端存储原型就定了dir对应目录logical对应LVM卷组iscsi对应iSCSI靶机rbd对应Ceph RBD。想从目录池改成LVM池不是改两行XML就行得重做整个池数据迁移要提前规划。3.2 虚拟网络NAT、隔离与桥接的XML实现libvirt默认安装后会生成一个default虚拟网络配置在/etc/libvirt/qemu/networks/default.xml大概长这样network namedefault/name forward modenat/ bridge namevirbr0/ ip address192.168.122.1 netmask255.255.255.0 dhcp range start192.168.122.2 end192.168.122.254/ /dhcp /ip /network这就是一个标准的NAT网络宿主机上会创建一个virbr0网桥虚拟机的流量通过iptables规则做NAT转发出去。业务上如果只要求虚拟机出外网、不求外网直接访问虚拟机这个配置够用到退役。如果虚拟机要暴露服务给外部访问省事的做法是给domain XML里的interface加一个hostdev或者配合iptables做端口映射但那都是绕路。更常规的做法是建立一个桥接型网络直接把虚拟机的虚拟网卡桥到br0上。桥接型虚拟网络的XML只需要把forward/去掉改成network namebr0net/name bridge namebr0/ /network然后在domain XML里把source networkbr0net/配上。这样虚拟机就和宿主机在同一二层网络里租户侧完全无感。4. 从零开始一条完整虚拟机的落地实操4.1 用模板生成XML不手写手动敲一份几百行的XML容易在缩进、闭合标签、属性拼写上翻车。我从来都是从模板起步的。最快的模板来源是现有虚拟机virsh dumpxml vm01 /tmp/vm02.xml把里面的name、uuid、磁盘路径、MAC地址改成新虚拟机的值就行。dumpxml出来的配置是正在运行实例的实际状态里面会有很多运行时附加的标签比如alias、state这些不必手工删直接改完定义后libvirt会自己重新生成。源虚拟机如果开着dumpxml的结果比关着的virsh dumpxml --inactive多一部分运行时信息做模板建议用--inactive更干净。没有现成模板时用virt-install生成初始化XML也很快virt-install \ --name vm03 \ --memory 4096 \ --vcpus 2 \ --disk path/var/lib/libvirt/images/vm03.qcow2,size40,formatqcow2 \ --network networkdefault \ --os-variant centos7.0 \ --graphics vnc \ --cdrom /var/lib/libvirt/isos/CentOS-7-x86_64.isovirt-install走完前几步后会在后台自动生成一份domain XML并define好然后进入安装态。此时可以virsh dumpxml vm03把配置导出来作为后续修改的基础比纯手写靠谱得多。4.2 校验、定义与启动的正确姿势拿到XML后先别急着define我是先做一遍语法和语义检查xmllint --noout /tmp/vm02.xmlxmllint只查XML语法合法性比如标签闭合、属性引号。真正检查libvirt语义是否合法要执行definevirsh define /tmp/vm02.xml这个命令如果有错误绝大多数情况会直接报“error: parsing /tmp/vm02.xml: ...”并定位到具体行号。确认define成功后再启动virsh start vm02我还习惯在start前用virsh dumpxml vm02 | grep -E mac|source file核实一遍MAC和磁盘路径防止define过了一台配置张冠李戴的机器。4.3 运行中的虚拟机想改配置热改有讲究虚拟机运行期间virsh edit修改XML里的某些参数是不会立即生效的需要在shutdown后重启才读取新配置。但有一部分设备支持热插拔比如第二块网卡、第二块磁盘、内存。热插拔的正规操作也是先编辑domain XML然后执行virsh attach-device vm02 /tmp/new-disk.xml virsh detach-device vm02 /tmp/old-disk.xml而不是跑virsh start加载。真正生产环境的热插拔我建议先把设备XML单独写成一个文件方便回滚和审计。设备XML里有个容易忽略的小规则热插拔磁盘的target不能和现有设备重名否则libvirt会直接拒绝attach热插拔网卡要避开已有MAC地址冲突了驱动在虚拟机里会异常。5. 常见问题与排查技巧实录5.1 配置不生效先看是不是有第二份XML一个非常隐蔽的坑virsh edit vm02编辑时libvirt默认打开的文件路径是/etc/libvirt/qemu/vm02.xml。但如果这台虚拟机是从别的宿主机迁移过来的或者是用OpenStack纳管的实际上可能存在多个来源的配置缓存。此时改完系统里那份XML再用virsh start vm02可能会发现启动的还是旧配置因为libvirt当前运行的实例配置可能缓存在内存和/var/run/libvirt/qemu/下。我的排查顺序是virsh dumpxml vm02 virsh dumpxml --inactive vm02两条命令输出的差异基本就是当前实际生效配置和磁盘定义配置的差异。清理掉异常状态可以用virsh undefine vm02再重新define一份干净配置但要确认该操作不会一并删除磁盘数据。5.2 设备编号冲突导致找不到启动盘症状是Windows虚拟机里某个盘符丢失或者Linux系统启动时卡在/dev/vdb超时。我在现场排查过好几次最后发现都是XML里手工写了两个disktarget devvda和target devvdalibvirt居然没有完全拦截这种明显冲突。解决方法是先把矛盾的设备摘除让libvirt自己根据bus顺序分配设备名再重启确认。这里也是最推荐给新手的一句不要手工编设备名让libvirt自动生成绝大多数冲突可以避免。5.3 迁移后起不来CPU模型和总线是两大元凶live migration失败的原因很多但CPU模型不兼容和磁盘bus不一致基本占了大头。CPU不兼容的表现一般是“target domain does not support某CPU特性”。解法要么在源和目标宿主机都使用host-model要么把虚拟机配置里的cpu特性和目标宿主机相比做减法。磁盘bus不一致的表现是迁完启动后系统找不到根文件系统。这个没太多捷径只能在建虚拟机时就统一让磁盘走virtio并把虚拟机内的fstab改成UUID挂载。这两点做好了跨宿主机迁移的成功率能往上拉一大截。5.4 配置文件版本备份与审计这可能是我最想强调的一个习惯把/etc/libvirt/qemu/下的所有XML纳入git管理。libvirt配置是文本文件非常适合做版本控制。宿主机上搭一个裸仓库定期提交改配置前先commit一个快照万一改崩了还能git diff看到底改了什么。别小看这个动作我见过不止一次因为手快删了一个标签导致宿主机上所有虚拟机批量注册失败所有人都不知道是谁改的。有了版本管理谁的改动、什么时候改的、改了什么一目了然。另外有条件的场景建议把XML配置备份到宿主机外的独立存储上。因为/etc/libvirt/qemu/是在系统盘上的一旦系统盘损坏所有虚拟机定义都没了磁盘镜像还在但没法启动恢复只能手动重建XML。用脚本每小时打包整个目录至少是个救命方案。再说点实际体会坦白说libvirt的XML学起来确实有一点点枯燥标签很多参数组合更大。但一旦上手之后它能带来非常强的掌控感一台虚拟机的CPU、内存、磁盘、网络、串口、电源策略全部浓缩在一个文本文件里你可以精确知道它的一切可以在崩溃现场快速定位到是哪个标签出了问题。我在实际操作中最大的体会是不要被复杂的标签吓到先用virt-install生成一份最小可用配置然后每次只改一个参数、启动验证一轮这样积累出的每一步都是可复现的。等你的XML配置文件能像读文章一样顺畅读完时KVM虚拟化的门槛基本就迈过一大半了。
返回列表