你有没有遇到过这种情况:接手一套KVM虚拟化环境,同事都用virt-manager图形界面点来点去,一旦只能SSH登录服务器,就感觉两眼一抹黑,什么操作都做不了?我在运维生涯里经历过太多次这种场景,后来把virsh用熟了才发现,它才是KVM环境里真正离不开的那个工具。
virsh是libvirt虚拟化管理套件的命令行工具,日常说的“用virsh管理虚拟机”,本质上就是通过libvirt去和QEMU/KVM hypervisor打交道。它覆盖了虚拟机从创建、启动、关机、删除到配置调整、快照、迁移、网络和存储管理的几乎所有操作,而且不受图形界面限制,脚本化批量操作也全靠它。这篇文章我会从实际运维角度出发,把virsh里最常用的命令按场景拆开讲,包括命令背后的原理、容易踩的坑、以及常规文档里不会写的一些经验,希望能帮到正在学习和使用KVM的朋友。
1. 先搞清楚virsh的工作方式,后面才不会犯低级错误
1.1 virsh和libvirt到底是什么关系
很多初学者会直接把virsh当成“KVM的命令行管理工具”,这个说法对也不对。更准确地说,virsh是libvirt的项目之一,libvirt本身是一个虚拟化管理库,它提供了一套统一的API和守护进程libvirtd,让上层工具(包括virsh、virt-manager)可以跨hypervisor管理虚拟机。换句话说,virsh就像是libvirt这个统一管理平台的前端命令,你输入一条virsh命令,实际上是通过libvirtd去操控底层的QEMU/KVM,或者Xen、LXC等。
理解这一点对排查问题特别重要。比如你在命令行执行virsh命令时遇到“error: Failed to connect socket to '/var/run/libvirt/libvirt-sock'”这类报错,第一反应不应该是怀疑虚拟机本身出了问题,而是要去检查libvirtd服务是否正常启动。我遇到过几次这种情况,都是服务器重启后libvirtd没起来,或者systemd的socket激活机制出了岔子,跟虚拟机本身一点关系没有。
1.2 连接URI:system和session的区别一定要分清
virsh默认连接的是qemu:///system,这是libvirt的系统级连接。所谓system,就是由系统守护进程libvirtd管理的,权限要求比较高,通常需要root用户或者属于libvirt组的用户才能操作。system连接下创建的虚拟机是全局的,所有用户可以通过系统级的管理接口看到,也是日常服务器运维中最常使用的连接方式。
另一种是qemu:///session,这是用户级连接,每个用户有自己的libvirtd实例和独立的虚拟机空间,主要用于普通用户用QEMU做本地虚拟化测试,虚拟机配置存放在用户目录下,不干扰系统全局环境。我在自己开发机上跑一些临时虚拟化测试时会用session模式,但生产环境运维一定要统一使用system连接,否则可能导致明明已经创建了虚拟机,换一个用户或者用脚本连接时却怎么都看不到。
如果你需要显式指定连接地址,用virsh -c qemu:///system list --all这样的方式,常规的virsh命令默认就是system连接,所以大多数情况下不需要写-c参数。但写脚本时我建议明确加上连接URI,既清晰又避免环境变量干扰,毕竟不同的发行版对LIBVIRT_DEFAULT_URI的处理可能有差异。
1.3 没有图形界面的服务器上,virsh就是唯一的操作窗口
很多生产服务器不会安装图形环境,哪怕装了X11转发,操作体验也很差。在这种环境下,virsh就是管理虚拟机的核心入口。它的好处不仅在于“能用”,更在于可脚本化。比如需要批量查看所有虚拟机的内存配置,一条for循环加virsh命令就能搞定,比在virt-manager里一台台点效率高出一个数量级。
实际运维中我还会配合virsh的--connect参数编写一些常规巡检脚本,定时检测虚拟机的CPU、内存、磁盘状态。这些自动化能力是图形工具给不了的,也是我建议每一个做虚拟化运维的人都要熟练使用virsh的根本原因。
2. 虚拟机生命周期管理:从创建到删除的完整闭环
2.1 create和define的区别,很多人用了很久都没搞明白
virsh create和virsh define都能从XML文件创建虚拟机,但本质区别非常大:create是从XML临时创建一个虚拟机,这个虚机不会写入libvirt的持久化配置,一旦虚拟机destroy或者宿主机重启,这个虚拟机的配置就丢了,virsh list --all里也看不到它。而define是真正“定义”一台虚拟机,把配置持久化到/etc/libvirt/qemu/目录下,之后可以通过virsh start启动,管理起来符合常规预期。
实际工作中二者的典型用法是:刚拿到一份新XML配置时,先用virsh create跑一下做些验证,确认启动没问题、设备配置正确,再用virsh define正式登记,避免因为XML写错导致污染正式配置。反过来,如果你在生产环境里误用了create,虚拟机和业务还在正常运行,但配置没有持久化,一旦宿主机意外重启,虚拟机就找不回来了——这个坑我见过不止一次,所以大家一定要记住两者的区别。
# 从XML文件定义虚拟机(持久化) virsh define /opt/kvm/vm/web01.xml # 临时启动(不持久化) virsh create /opt/kvm/vm/web01.xml # 启动已定义的虚拟机 virsh start web01虚拟机XML文件本身是KVM管理的核心配置载体,包含内存、CPU、磁盘、网络、显卡、启动顺序等所有关键信息。刚入门时我建议多手动编写几次XML,理解每个节点的含义,后面做批量部署或者写自动化脚本会轻松很多。
2.2 start、shutdown、destroy:优雅关机和强制断电之间的差异
start命令很好理解,就是启动一台已定义的虚拟机。但如果虚拟机上次是强制关闭的,启动时可能会提示需要磁盘检查或者出现文件系统异常,这是虚拟化环境普遍存在的现象,不代表virsh出了问题,只需要进虚拟机做一次fsck或者让业务团队确认状态即可。
shutdown和destroy则是两个完全不同的操作。shutdown是向虚拟机发送ACPI关机信号,相当于你按了一下电脑的电源键,让客户机操作系统自己执行关机流程,涉及服务的平滑停止、缓存刷盘、文件系统卸载等,是日常维护中最应该使用的关机方式。destroy则相当于直接拔掉电源线,不管客户机正在做什么,立刻终止QEMU进程。生产环境务必谨慎使用destroy,否则可能导致数据库文件损坏或者数据丢失。
reboot和reset同理,前者是发送重启信号,让客户机自己重启;后者是硬件级别的复位,对正在运行的进程来说非常粗暴,一般只在客户机卡死且没有其他办法时才会用到。
2.3 undefine删除虚拟机:不是简单的rm -rf
删除虚拟机用virsh undefine,这背后有个容易踩坑的细节。如果虚拟机处于运行状态,或者存在管理快照、启用了UEFI引导且配置了nvram变量,直接执行undefine会报错或者留下一堆残留文件,导致“看起来删了,其实配置碎片还在”的局面。
我自己就处理过一次比较典型的现场:一台使用UEFI引导的虚拟机,执行了virsh undefine之后,/var/lib/libvirt/qemu/nvram/里仍然残留着对应的.fd文件,如果不手动清理,后面创建同名虚拟机时系统可能会因为nvram路径冲突而失败。正确的处理方式是把undefine和存储、固件相关文件的清理结合起来,比如显式传入存储卷删除参数,或者加上--nvram参数:
# 删除虚拟机定义,同时删除与其关联的存储卷 virsh undefine web01 --remove-all-storage # 删除使用UEFI引导的虚拟机定义,连同nvram变量文件一起清掉 virsh undefine web01 --nvram这里尤其要提醒一点:生产环境删除虚拟机之前,务必先确认业务数据是否有备份,或者是否已经完成了迁移。带--remove-all-storage参数的undefine相当于连数据一起删,执行前必须双重确认,条件允许的话建议先把存储卷的输出内容(比如qcow2镜像路径)记录到变更文档,以防后查。
2.4 生命周期操作中的状态机认知
虚拟机在libvirt里有几种状态:未定义、已定义但未运行(shut off)、运行中(running)、暂停(paused)、保存(saved)等。virsh list不加参数时只显示运行中的虚拟机,--all显示所有已定义的虚拟机,--inactive则显示未运行的虚拟机。运维巡检时如果发现虚拟机消失了,先别慌,用--all看看它是不是只是没有启动。
另外还有一组容易混淆的命令:suspend/resume和managedsave/start。suspend是把运行中的虚拟机暂停(类似挂起),内存状态保留在物理内存里,用resume恢复;managedsave是把虚拟机内存状态保存到磁盘文件,然后停止虚拟机,下次start会自动从保存状态恢复,适用于宿主机需要维护但不想让虚拟机完全关机重启的场景。这两组操作在实际维护中很有用,比如内存升级前可以用managedsave把虚拟机先“冻结”起来,宿主机维护完成后快速恢复。
3. 状态查看和配置调整:动手之前先学会“摸底”
3.1 高频查看命令:不看这些信息,很多操作就是盲改
日常使用频率最高的virsh命令,其实是各种查看类的命令。它们看起来简单,但在故障排查和变更评估中作用巨大。
virsh list --all不需要多解释,就是列出所有虚拟机。virsh dominfo <虚拟机名>可以看一台虚拟机的基础配置信息,包括CPU核数、内存大小、运行状态、自动启动设置等。我曾经遇到过一个虚拟机内存配置异常的问题,通过domininfo发现Max memory和当前内存值存在很大偏差,一下就定位到了配置变更时没同步的问题。
磁盘、网卡这些硬件信息的查看,用virsh domblklist <虚拟机名>和virsh domiflist <虚拟机名>就能解决。domblklist会列出虚拟机的所有虚拟磁盘,包括磁盘目标名(如vda、vdb)、后端来源文件路径和驱动类型。domiflist则列出网卡类型、源网络和MAC地址。这些信息在挂载新磁盘、调整网络、做备份恢复时特别常用。
虚拟机的实时运行状态,尤其是CPU和内存,可以用virsh vcpuinfo <虚拟机名>、virsh vcpucount <虚拟机名>、virsh dommemstat <虚拟机名>来看。dommemstat能显示客户机实际使用的内存量,比如unused、available等指标,这对判断虚拟机内存是否吃紧很有参考价值。我巡检时习惯写一个小脚本把这些信息汇总成表格,数据一出来,哪台机器需要扩内存、哪台机器CPU核数明显不够,心里就有数了。
3.2 在线调整CPU和内存:setvcpus和setmem,不要只改一半
虚拟化最吸引人的一点是资源可以动态调整,但动态调整也是有限制的,不了解这些限制会踩到很多坑。
先看CPU。virsh setvcpus <虚拟机名> <数量>可以调整虚拟机的vCPU个数。如果是给运行中的虚拟机增加vCPU,可以直接生效,但前提是客户机操作系统支持CPU热插拔,并且虚拟机的XML里配置了对应的CPU热插拔能力。如果想要持久化修改,需要同时指定--config参数。实际操作中我经常遇到只加了--live或者只改了--config的情况,结果虚拟机重启后配置又变回去了,或者其他节点上看到的规格没有变化,这些大概率都是没同时处理“当前生效”和“配置持久化”这两层。
内存调整类似,virsh setmem <虚拟机名> <内存大小>修改的是当前内存,virsh setmaxmem <虚拟机名> <内存大小>修改的是上限。内存热插拔的兼容性比CPU更复杂,需要客户机系统和虚拟化层同时支持。我建议生产环境调整内存前,先在测试环境验证一遍客户机操作系统是否支持热插拔,否则很可能造成虚拟机内存状态不一致,甚至需要重启才能稳定。特别强调,单位问题要格外留意,setmem参数默认以KB为单位,不写单位很容易把内存配错。
# 调整正在运行的虚拟机CPU为8核,并持久化到配置 virsh setvcpus web01 8 --live --config # 调整运行中虚拟机的内存为16GB,并持久化到配置 virsh setmem web01 16G --live --config virsh setmaxmem web01 16G --config --live实际上,很多虚拟机的CPU和内存调整最终都需要重启客户机才能完全生效,这一点不能只听宿主机命令行的反馈,最好通过客户机内的lscpu、free -h去确认实际结果。
3.3 热插拔磁盘和网卡:attach和detach系列
除了CPU和内存,磁盘和网卡也可以动态挂载。virsh attach-disk <虚拟机名> <源路径> <目标设备> --live --config可以在运行中的虚拟机上添加一块虚拟磁盘,比如:
# 在运行中的web01上挂载一块qcow2磁盘作为vdb virsh attach-disk web01 /data/kvm/images/web01-data.qcow2 vdb --live --config注意,挂载完成后客户机操作系统不一定能立刻识别到新磁盘。Linux下可能需要重新扫描SCSI总线,Windows下可能需要重新扫描磁盘管理器,甚至可能要重启虚拟机才看到新盘。这不能怪virsh,它只是把虚拟硬件加进去了,客户机操作系统层面的识别是另一回事。
卸载磁盘用virsh detach-disk <虚拟机名> <目标设备>,比挂载更需要注意安全:确认磁盘对应的文件系统已经卸载,业务已经不再访问该盘,否则在虚拟机运行状态下强制卸载可能导致数据损坏。除非你明确知道客户机的文件系统层不会报错,否则我建议不要在业务高峰期做热卸载操作。
网卡热插拔使用virsh attach-interface <虚拟机名> network <网络名> --live --config,还可以加上--mac指定MAC地址。比如给web01添加一张连接到bridge0的网卡:
# 给运行中的虚拟机添加一张桥接模式的虚拟网卡 virsh attach-interface web01 network bridge0 --live --config这里特别提醒:频繁的热插拔可能会导致客户机内的网络接口命名混乱(比如eth0、eth1对不上)。生产环境如果对接口顺序有严格要求,最好提前规划好MAC地址和接口命名策略,避免每次热插拔都去客户机里核对IP和网卡对应关系。
3.4 为什么改了配置不生效:live、config、current三者的区别
virsh里很多修改类命令都有--live、--config、--current这几个参数,刚接触时确实容易搞混。简单来说:
- --live表示修改只对当前运行中的虚拟机实例生效,不改变持久化配置,重启虚拟机后就丢了。
- --config表示修改持久化配置,下一次虚拟机冷启动后生效,不直接影响当前运行状态。
- --current表示修改对当前状态生效,它到底是live还是config,取决于虚拟机当前处于运行中还是关闭状态。
对于运行中且需要长期生效的调整,我的习惯是同时加--live和--config,保证当前生效和重启后保留。有些命令还支持--persistent,功能和--config类似,兼容性需要根据发行版和libvirt版本具体确认。改完配置后,用virsh dumpxml <虚拟机名>检查一下修改结果,是避免配置漂移的最好办法。
4. 快照、迁移和恢复:数据不丢才是硬道理
4.1 快照的本质和创建:snapshot-create-as
KVM环境里做数据保护,快照是一个非常重要的手段。virsh snapshot-create-as <虚拟机名> <快照名>可以为虚拟机创建快照。需要明确的是,libvirt的快照功能依赖磁盘镜像格式,qcow2支持内部快照,raw格式不支持,这是选型时要提前考虑的。
快照分为磁盘快照和内存快照。使用snapshot-create-as不额外指定参数时,创建的是磁盘状态快照,不包含客户机内存状态;如果加了--atomic,可以尽量保证磁盘和内存状态的一致性。快照创建期间,虚拟机的磁盘写入可能会产生一定的性能损耗,因为qcow2内部快照需要记录变化块。在业务高峰期做快照,io延迟通常会有明显上升,所以我建议重要环境的快照尽量放到业务低峰期操作。
4.2 快照的回滚与删除:操作前先确认当前状态
回滚用到virsh snapshot-revert <虚拟机名> <快照名>。执行回滚前务必确认快照点之后产生的数据是否需要保留,因为回滚会覆盖当前磁盘状态。如果你不确定,可以先对当前状态做一个快照,再回滚到目标快照,相当于给自己留了一条后路。
查看快照列表用virsh snapshot-list <虚拟机名>,快照比较多时建议加上--tree参数,用树形结构展示快照之间的层级关系。删除某个快照用virsh snapshot-delete <虚拟机名> <快照名>,注意如果快照还有子快照,删除时可能需要额外参数,否则会失败。我遇到过因为快照链太深导致删除异常的情况,后面养成习惯:快照创建时就用带有日期和用途的名称,过期的快照及时清理,不要堆成“屎山”。
4.3 迁移的两种方式和基本流程
virsh migrate是在宿主机之间迁移虚拟机的重要命令。迁移的核心价值在于可以把运行中的虚拟机从一台物理机挪到另一台物理机,业务不中断或者中断极短。
简单做一次在线迁移(live migration)的命令如下:
# 把web01在线迁移到目标宿主机 virsh migrate --live web01 qemu+ssh://target-host/system这里有几个前提条件:两台宿主机的CPU型号和虚拟化特性需要基本一致,否则迁移后虚拟机性能可能下降或者直接无法启动;存储需要是共享存储或者可同时被两端访问的存储,如果虚拟机使用的是本地磁盘,则无法直接在线迁移;libvirtd服务需要正常监听,且目标宿主机有足够的计算资源。
离线迁移相对简单,本质上是把虚拟机配置和磁盘文件转移到新宿主机,再在新宿主机上define。操作上需要先把虚拟机shutdown,然后拷贝磁盘文件,重新生成或修改XML里的磁盘路径,最后virsh define。这个过程虽然业务会中断,但对底层要求更低,也是很多中小团队实际使用的方案。
4.4 blockcommit和blockpull:快照链的“合并”与“拉取”
快照用多了,会产生一个快照链,也就是磁盘镜像之间存在父子引用关系。当快照链太长,或者你想把快照合并到基础镜像中,就会用到blockcommit和blockpull。
blockcommit是把顶层活跃层的数据合并到底层镜像,相当于向前合并;blockpull是把底层镜像的数据拉取到当前顶层,相当于向后合并。操作命令类似于virsh blockcommit <虚拟机名> vda --base /path/to/base.qcow2 --top /path/to/top.qcow2 --active,或者virsh blockpull <虚拟机名> vda --base /path/to/base.qcow2。执行合并操作时要确保目标磁盘路径和镜像语法正确,否则可能把快照链搞坏。这个操作相对进阶,初次使用时建议先在测试环境完整验证一遍,不要直接在关键生产环境上操作。
5. 网络与存储池管理:虚拟机背后的资源管家
5.1 虚拟网络管理:net系列命令
虚拟机的网络离不开虚拟网络,virsh里有专门的一套network命令。virsh net-list --all查看宿主机定义的所有虚拟网络,virsh net-info <网络名>看网络详细信息,virsh net-start <网络名>启动网络,virsh net-destroy <网络名>停止网络,virsh net-undefine <网络名>删除网络定义。
KVM默认的default网络就是NAT模式,原理是在宿主机上创建了一个虚拟网桥virbr0,虚拟机的网卡接到virbr0,通过宿主机的iptables规则做地址转换访问外网。企业环境通常会把guest网络改成桥接模式,直接把虚拟机网卡桥接到物理网卡所在的bridge上,让虚拟机获得和宿主机同网段的IP,便于管理。
实际操作中用virsh net-edit <网络名>可以直接编辑网络的XML配置。改完需要重启网络或重启libvirtd,否则新的网络规则不会生效。如果遇到虚拟机无法上网或者网络不通,优先检查虚拟网络是否active,再检查网络XML中的forward模式和bridge设备是否存在。
5.2 存储池管理:pool系列命令
存储池是libvirt对存储资源的抽象,可以是目录、LVM卷组、iSCSI、NFS等。virsh pool-list列出所有存储池,virsh pool-info <池名>查看存储池详情,virsh pool-create-as <池名> <类型> --target <路径>创建存储池,virsh pool-start和virsh pool-autostart分别表示启动存储池和设置开机自动启动。
比如创建一个基于目录的存储池:
# 创建一个名为vm-images的目录类型存储池 virsh pool-create-as vm-images dir --target /data/kvm/images virsh pool-start vm-images virsh pool-autostart vm-images在KVM中,存储池并不是必须的,很多运维人员直接使用文件路径定义虚拟磁盘,完全绕过存储池的概念。但使用存储池的好处是统一管理、统一配额、方便扩展和备份,尤其是虚拟机数量较多时,把存储资源池化能让磁盘创建、克隆、迁移等操作变得规范很多。
6. 实战中高频遇到的问题和排查思路
6.1 virsh shutdown没反应,客户机就是不关机
这个问题的根源多数时候是客户机操作系统没有正确处理ACPI电源管理事件。服务器版Linux的ACPI支持一般默认开启,但也有系统因为内核设置、桌面环境或服务被精简导致ACPI关机信号被忽略。
排查思路:先确认虚拟机XML里是否有ACPI设备(<acpi/>),很多精简模板会把ACPI去掉,导致shutdown信号根本进不去。其次可以考虑在客户机里安装qemu-guest-agent,这样virsh可以通过guest agent协议发起优雅关机,而不是依赖ACPI。命令是:
# 通过guest agent执行优雅关机 virsh shutdown --mode agent web01这个方法比ACPI更可靠,前提是客户机里已经运行了qemu-guest-agent服务。如果应用层还有自我保护机制拦截了关机信号,那就要靠应用团队一起配合处理了。
6.2 配置文件语法错误导致无法define
手动编写或修改XML时,少一个尖括号、错一个标签层级,virsh define就会报错。这类报错信息通常比较抽象,比如“error: XML document failed to parse”。快速定位的方法是先用xmllint校验XML语法,再用virsh define之前用virsh dumpxml对比一下已有虚拟机的配置,发现差异。
我个人的习惯是每次修改XML之前先备份原文件,修改后立即执行virsh define和virsh start验证。报错时可以先把XML里的部分节点注释掉,逐步缩小范围。需要特别注意,启动方式、磁盘类型、控制器类型这些节点,语法正确不代表业务正确,需要一并与实际环境核对。
6.3 attach-disk后客户机看不到新硬盘
宿主机上attach-disk成功了,但进入客户机没有发现新磁盘。这个现象常见原因有两种:一是客户机操作系统没有触发磁盘扫描,二是XML里新磁盘的target定义有冲突,导致设备名被占用。
Linux客户机内可以通过下面的命令触发重新扫描:
# 查看现有SCSI设备 ls /sys/class/scsi_device/ # 重新扫描所有SCSI主机 echo "- - -" > /sys/class/scsi_host/host0/scan不过请注意,具体扫描方式取决于客户机的内核和设备类型,实测中有些virtio-blk设备不需要扫描就能出现,有些则需要重启或者通过udev规则触发。如果你使用了virtio-blk驱动,还要确认客户机内的virtio模块是否正常加载。总之,热插拔磁盘后看不到盘,不能只盯着宿主机端,要结合客户机操作系统的总线扫描机制一起排查。
6.4 快照文件越来越大,磁盘空间被吃满
快照本质上会记录从快照点以来的数据变化,当你对快照链里的某些块做修改时,新数据会写到新的镜像文件中,qcow2镜像会不断增长。如果快照创建得非常频繁,而且从不清理,磁盘空间会被大量占用。
解决办法就是定期清理无用快照,并监控宿主机和共享存储的剩余空间。注意快照回滚之后,旧快照占用的空间不一定马上释放,需要通过快照链重建或者再次执行blockcommit合并数据。对空间敏感的线上环境,建议用proxmox这类全套虚拟化管理平台,或者自己写定时任务监控qcow2镜像大小,超过阈值就告警。空间告警后再处理,多半已经晚了。
从我实际用virsh管理KVM环境的经验看,命令本身不难,难的是理解虚拟化的核心逻辑:状态的切换、配置的持久化、资源的动态分配、数据的保护。virsh这个工具的价值就在于它让你能够通过命令行精细控制每一个环节,无论是单机管理还是批量运维,都能做到快速和准确。如果你刚接触KVM,建议在测试环境把每一类命令都敲一遍,制造一些故障再尝试用virsh恢复,这样积累的速度比单纯看文档要快得多。后续有机会,我再写一篇关于XML配置详解和批量运维脚本实践的文章,把这些内容串起来,形成一套更完整的KVM运维知识体系。