做虚拟化运维这几年,我发现自己最常用的工具不是图形化管理平台,而是virsh这个命令行工具。virsh是libvirt提供的命令行管理客户端,只要宿主机跑着KVM/QEMU,它就能帮你把创建虚拟机、开关机、查看状态、网络配置、快照管理这一整套操作,全部收敛成一句句可复用的命令。它没有花哨的界面,但胜在稳定和直接,尤其适合批量管理、脚本化交付和远程排查。这篇内容就围绕virsh最常用的那些操作展开,我会从环境准备讲起,带你把虚拟机生命周期、日常监控、存储网络配置走一遍,最后整理一些实战中容易踩的坑和排查思路。不管你是刚接触虚拟化的新手,还是打算从virt-manager切到纯命令行管理的老手,都可以按这篇内容动手试一遍。
1. 环境准备:先搞清楚virsh背后的libvirt
1.1 安装组件时,别只装了客户端
virsh本身只是一个命令行客户端,真正干活的是一整套libvirt服务加qemu-kvm进程。你可以把virsh理解成遥控器,遥控器再高级,电视没接电源也白搭。所以安装时要看清楚,至少需要这几块:
- libvirt服务端,提供libvirtd守护进程,负责创建虚拟机、管理网络和存储池。
- libvirt客户端,包含virsh命令本身。
- qemu-kvm,宿主机上虚拟机的实际执行引擎。
在Debian/Ubuntu系列上,我一般直接装:
apt install libvirt-daemon-system libvirt-clients qemu-kvm在RHEL/CentOS/Fedora系列上,对应的包名是:
yum install libvirt-daemon libvirt-client qemu-kvm安装完成后,先确认libvirtd服务状态正常。新版本的libvirt默认采用socket激活模式,也就是当你第一次执行virsh命令时,systemd会自动拉起libvirtd服务,这是正常行为,不需要惊慌。
systemctl status libvirtd如果显示active(running),就可以继续;如果显示inactive,先执行systemctl start libvirtd,再确认/var/run/libvirt/libvirt-sock这个sock文件存在。还有一个我一直强调的点:日常操作不要用root硬怼,建议把当前用户加入libvirt用户组,这样既安全又方便统一权限管理。
usermod -a -G libvirt $(whoami)注意:改完用户组后需要退出重新登录,组权限才会生效。如果你发现执行virsh list还提示权限不足,问题基本都出在这一步。
1.2 system和session:两种连接方式的取舍
virsh连接libvirt服务时,最常用的URI是两个:qemu:///system和qemu:///session。这两个连接方式决定了你管理的虚拟机层级和权限范围。
qemu:///session是用户级连接,不需要root权限,管理的是当前用户自己启动的虚拟机。听起来很方便,但它没有系统级网络、没有全局存储池,很多生产环境需要的特性都用不了。我一般只在本地快速测试时才用它。
qemu:///system是系统级连接,管理的是整个宿主机上所有用户的虚拟机。它的权限更高,能操作虚拟网络、存储池资源,也说“虚拟机开机自启”这类系统级配置。生产环境统一用这个URI,不要纠结。
我建议把默认连接方式写到环境变量里,避免每次敲-c qemu:///system:
export LIBVIRT_DEFAULT_URI='qemu:///system'然后随时可以用virsh uri确认当前连接到了哪个实例。我刚接手一台新宿主机时,第一件事就是跑这个命令,先把连接环境确认清楚,后面操作才不会挂在莫名奇妙的权限问题上。
1.3 第一次连接前,先跑这几个检查命令
环境准备好后,我会习惯性地跑一组基础检查命令,确认libvirt服务、默认网络和存储池都是可用的:
virsh list --all virsh net-list --all virsh pool-list --allvirsh list --all会列出宿主机上所有虚拟机,包括已经关机的;net-list --all查看当前可用的虚拟网络,一般至少会有一个名为default的NAT网络;pool-list --all则显示存储池。如果这三个命令都能正常返回结果,说明环境基本就绪。如果net-list是空的,先别急着建虚拟机,把default网络找回来再说。
2. 虚拟机生命周期:从定义到销毁
2.1 定义虚拟机:XML配置是核心
virsh管理虚拟机有一个核心概念:每台虚拟机都对应一份XML描述文件,里面写明了虚拟机的名称、内存、CPU、硬盘、网卡、启动顺序等所有属性。你可以把这份XML理解成虚拟机的“出生档案”,libvirt依据它来创建和运行虚拟机。
定义一个虚拟机通常有两种方式:virsh define和virsh create。define是把XML注册到libvirt中,虚拟机会出现在virsh list --all里,但不会立即启动;create则直接根据XML启动虚拟机,并且是临时运行,宿主机重启后这台虚拟机就“消失”了。生产环境我几乎只用define,因为它能保证虚拟机配置被纳入libvirt的管理范畴,方便后续查询和自启。
一份最简的XML长这样:
<domain type='kvm'> <name>demo</name> <memory unit='MiB'>2048</memory> <vcpu placement='static'>2</vcpu> <os> <type arch='x86_64' machine='pc-q35-6.2'>hvm</type> <boot dev='hd'/> </os> <devices> <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/var/lib/libvirt/images/demo.qcow2'/> <target dev='vda' bus='virtio'/> </disk> <interface type='network'> <source network='default'/> <model type='virtio'/> </interface> </devices> </domain>我平时不会手写整份XML,一般是先用virt-install或图形工具创建一台参考虚拟机,然后通过virsh dumpxml demo导出XML,在这基础上改名字、改磁盘路径。这样比从零写要稳妥得多,毕竟XML里有些隐藏字段,比如设备地址、控制器类型,手写容易漏。
virsh define /path/to/demo.xml执行define后没有任何报错,就说明XML格式没问题,虚拟机已经注册成功。
提示:拿到一份陌生XML时,先用
virsh define去验证它,而不要直接create。define只做语法和结构校验,不会真的启动进程,排错会更安全。
2.2 开关机和重启的正确姿势
定义好虚拟机后,启动它很简单:
virsh start demo但真正涉及关机和重启时,很多新手会踩坑。virsh的关闭操作分几种,各自语义完全不同:
| 操作 | 命令 | 行为 | 适用场景 |
|---|---|---|---|
| 优雅关机 | virsh shutdown demo | 发送ACPI关机信号,虚拟机内部系统正常关机 | 需要安全停止服务的场景 |
| 强制关机 | virsh destroy demo | 模拟拔掉电源,立即终止虚拟机进程 | 系统卡死、测试环境应急 |
| 优雅重启 | virsh reboot demo | 发送ACPI重启信号 | 需要重启虚拟机内部系统 |
| 强制重启 | virsh reset demo | 模拟硬件复位,不经过系统关停 | 内核卡死,快速恢复 |
shutdown命令要求虚拟机内部内核支持ACPI,并且系统里没把电源按钮事件屏蔽掉。很多精简版系统镜像默认不会响应ACPI信号,导致你执行shutdown后虚拟机毫无反应。这时候先别急着destroy,先尝试virsh send-key demo KEY_ENTER或者KEY_CTRL+KEY_ALT+KEY_DELETE,让系统内部触发一次C-A-D重启。如果设备上跑的是已有业务的虚拟机,我绝对不会先用destroy,因为那等于直接拔电源,磁盘缓存里的数据可能没来得及落盘。
2.3 删除虚拟机并不是删个文件那么简单
清理一台虚拟机时,很多没经验的同学会把virsh undefine当成删除键,然后跑完发现磁盘文件还在,怎么解释都解释不清。undefine默认只删除XML配置,不会动虚拟机的磁盘文件,这其实是libvirt刻意设计的安全行为。想一想,误删一台虚拟机的配置还有机会重新define回来,误删磁盘文件就真的什么都找不回来了。
删除一台虚拟机我的标准流程是:
# 1. 优雅关闭,如果关不掉再考虑destroy virsh shutdown demo virsh destroy demo # 2. 删除配置本身 virsh undefine demo # 3. 确认磁盘文件路径 virsh vol-list default # 4. 确认无误后再手动删除磁盘文件 rm /var/lib/libvirt/images/demo.qcow2如果你想一次性删掉配置和磁盘,可以用--remove-all-storage参数,但我强烈建议不要在生产环境用这个全量参数。它会把这台虚拟机关联的所有存储全部干掉,包括可能共用的物理磁盘分区。更稳妥的做法是,先用domblklist查看这台虚拟机用了哪些磁盘,再逐个删除。
3. 日常查询与状态监控
3.1 list命令的几种姿势
日常运维中,最频繁的操作就是查看宿主机上有哪些虚拟机、它们处于什么状态。virsh list的默认输出只显示正在运行的虚拟机,想要看全部,必须加--all。我这里列几个平时用得多的变体:
virsh list --all virsh list --state-running virsh list --state-shutoff virsh list --state-paused--state-running只看在运行的,--state-shutoff只看已关机的,--state-paused则是暂停中。批量运维脚本里,我经常会用virsh list --name --state-running直接取运行中虚拟机的名字列表,省掉一堆解析步骤。
我自己还有一个习惯:每当要批量操作之前,先跑一遍virsh list --all加上virsh dominfo把状态和配置确认清楚。盲目地执行start或shutdown,很容易对错虚拟机。比如两台虚拟机同名的不同实例,虽然libvirt内部不允许同名域存在,但不同宿主机上的同名域在批量脚本里也容易出现混淆。
3.2 CPU、内存、磁盘怎么看
启动后想了解虚拟机内部资源使用情况,virsh也提供了对应的命令:
virsh vcpuinfo demo这个命令会列出vCPU的亲和性、运行时间、CPU占用时间等信息。判断虚拟机是否吃满CPU,我一般看virsh domstats里的vcpu.time,它表示vCPU累计占用的CPU时间,对比两次采样差值就能算出实时负载。
内存方面用virsh dommemstat,它能输出实际的available和rss值,数值的单位是KiB。rss就是qemu进程实际占用的物理内存,它往往比XML配置的内存量要大一些,因为qemu进程本身还有额外开销。如果rss常年超过配置值很多,就要考虑是不是虚拟机内部内存压得比较死,或者需要开启内存气球设备。
磁盘信息用virsh domblklist查看,它会列出一台虚拟机当前挂了哪些块设备,以及对应的镜像文件路径。例如:
virsh domblklist demo这个命令在做磁盘扩容、迁移镜像前必须跑一遍,确认源文件和目标设备一一对应。你以为是vda的设备,实际操作时发现是sda,那后面的JSON配置、分区操作全都会错位。
3.3 想进入虚拟机的两种方式:console和图形协议
有一段时期我管理虚拟机完全依赖SSH,直到有些虚拟机网络配置出错,我才意识到必须掌握virsh console这个救急手段。virsh console demo会接入虚拟机的串口控制台,但有个前提:虚拟机内部系统需要启用串口终端,对于Linux,启动参数里要加上console=ttyS0,并且/etc/default/grub里配置好后执行update-grub。如果没配置,执行virsh console后会卡在一个空界面,按回车没反应,这不是命令坏了,而是虚拟机根本没往串口输出。
如果你需要图形界面访问,可以用virsh vncdisplay demo查询虚拟机当前的VNC显示编号,然后通过本机VNC客户端连接。但VNC协议本身不安全,建议通过SSH隧道转发到本地再连。这里我多说一句:在纯内网环境,只要宿主机SSH能连通,VNC隧道访问就很丝滑;跨公网环境,别直接把VNC端口暴露出去。
4. 存储与网络:基础但经常被搞混
4.1 存储池和存储卷的关系
virsh的存储管理模型里,存储池(pool)是底层存储资源的抽象,存储卷(vol)则是池里的具体单元。举例来说,一个目录池指向/var/lib/libvirt/images,这个目录下每一个qcow2文件就是一个卷。虚拟机所用的磁盘,本质上就是存储卷。
查看宿主机有哪些存储池:
virsh pool-list --all默认安装后一般会有一个名为default的目录池,对应路径通常是/var/lib/libvirt/images。你也可以手动建立自己的目录池:
virsh pool-define-as data dir --target /data/libvirt virsh pool-start data virsh pool-autostart data创建存储卷的命令是vol-create-as,比如创建一台20G的qcow2镜像:
virsh vol-create-as data demo.qcow2 20G --format qcow2这里有个容易忽略的点:--format qcow2如果不指定,默认可能是raw格式。raw格式的优势是性能好,但不支持快照,也无法精简分配。我几乎全部用qcow2,因为它支持快照、支持动态增长,功能更符合日常虚拟化管理需求。对于要求极致I/O的场景,再考虑raw。
经验:创建大容量qcow2卷时,可以加上
--prealloc-metadata参数,预分配元数据,避免首次启动时因为元数据初始化导致一瞬间的卡顿。这个参数不会占用全部20G空间,只是提前写好qcow2的结构信息,成本很低。
4.2 默认NAT网络和桥接到物理网络,选哪个
默认情况下,libvirt会建立一个名为default的虚拟网络,它通过宿主机上的virbr0网桥提供NAT转发,虚拟机可以访问外网,外网却无法主动访问虚拟机。对测试环境来说很好用,省事、安全,还不占IP资源。
但如果你想让虚拟机拥有局域网里的独立IP,让其他机器直接访问,就得用桥接模式。桥接模式下,虚拟机网卡直接接到宿主机所在的物理网络,相当于在交换机上多接了一台机器。
两种方式的取舍,我通常这么看:
| 对比项 | NAT模式(default) | 桥接模式 |
|---|---|---|
| 外网访问 | 虚拟机可出去,外部进不来 | 双向可达 |
| IP分配 | 内部DHCP,无需外部关心 | 需要跟物理网络协调IP |
| 适用场景 | 测试、隔离环境 | 生产、需要直连的服务 |
| 配置复杂度 | 低 | 中 |
配置桥接网络时,先用virsh net-list确认现有网络,再定义bridge类型网络。也可以直接用virsh net-define加载桥接XML,或者用nmcli在宿主机上配置好br0后,在虚拟机XML里让网卡指向这个桥接器:
<interface type='bridge'> <source bridge='br0'/> <model type='virtio'/> </interface>我踩过的一个坑是:桥接网络配好后,虚拟机还是ping不通外网。排查了半天发现是宿主机防火墙没放行虚拟机发出的转发流量,和虚拟机自身的路由无关。记住桥接模式下,虚拟机的流量是要经过宿主机iptables的,不是天生无障碍。
4.3 设备热插拔,不是你想的那么万能
virsh确实支持运行中给虚拟机添加或移除设备,比如网卡、磁盘、USB控制器。命令是这样的:
virsh attach-disk demo /data/libvirt/new.qcow2 vdb --live virsh detach-disk demo vdb但热插拔有一个重要限制:libvirt说支持热插拔,也不代表虚拟机内所有驱动都支持。vda这类virtio磁盘在Linux下支持热插拔,但如果你用的是IDE控制器,大概率要重启才能识别。最稳妥的做法是给虚拟机加磁盘时,同时指定--persistent,让它在本次运行和下次启动都保持有效:
virsh attach-disk demo /data/libvirt/new.qcow2 vdb --live --persistent如果只加--live不加--persistent,重启后这个磁盘就消失了,这在批量交付场景里会被坑得很惨。我自己就遇到过:给一个虚拟机加了数据盘,过了几天宿主机重启,业务上找不到那块盘,数据还是没挂上,等于白做。后来我强制要求所有热插拔操作必须加--persistent,除非明确是临时挂载验证。
5. 快照、克隆和迁移:批量交付的进阶操作
5.1 快照不是随时乱打的
快照是虚拟机管理中非常实用的功能,它让你能快速回到某个历史状态。创建快照的命令很简单:
virsh snapshot-create-as demo snap1 "first snapshot before update"查看已有快照:
virsh snapshot-list demo恢复快照:
virsh snapshot-revert demo snap1快照听起来很简单,用起来有几个关键点。首先快照只对qcow2磁盘生效,raw格式不支持内部快照,强行创建会直接报错。其次,创建快照时虚拟机最好是已关闭状态,或者至少保证文件系统是干净的。如果运行中创建快照,虽然框架支持,但恢复时可能遇到文件系统不一致的问题,尤其是有数据库写入的场景。恢复快照时还要注意,如果快照是在虚拟机启动时拍的,revert的时候可以加上--running参数让恢复后自动启动。
我实际管理中很少依赖快照做备份,它更适合做变更前的安全网。比如升级内核、改网络配置前,拍一个快照,出问题秒回退。真正的数据备份还得靠外部存储落盘,快照如果和源文件放在同一块物理盘上,盘崩了快照也一起没。
5.2 克隆虚拟机时最容易忽略的事
批量交付环境时,克隆虚拟机是最常用的手段。virsh本身没有直接叫clone的命令,需要借助virt-clone这个工具:
virt-clone --original demo --name demo-clone --auto-clone这条命令会自动复制原虚拟机的磁盘镜像,并生成一份新的XML配置。别高兴太早,克隆出来的虚拟机虽然磁盘内容完全一样,但里面有几个关键配置是必须改的:hostname、机器ID、网卡的MAC地址(virt-clone会默认生成新的MAC,但hostname不会自动改)、还有可能重复的IP地址。如果你把原机里的网卡IP写成了静态地址,克隆后就得手工进系统改。
另外,克隆前一定要确认原虚拟机的磁盘处于一致性状态,最保险的做法是关机后再克隆。有业务的虚拟机不建议在线克隆,除非你很清楚文件系统层面的风险。
5.3 迁移:从一个宿主机到另一个宿主机
virsh也支持虚拟机在线迁移,把虚拟机从一台宿主机挪到另一台宿主机,业务不中断。命令格式大致是:
virsh migrate --live demo qemu+ssh://host2/system执行迁移需要满足几个前提:两台宿主机之间网络互通,目标宿主机有足够的资源,虚拟机的磁盘要么放在两台宿主都能访问的共享存储上,要么在迁移时指定--copy-storage-all把镜像一并拷贝过去。另外,宿主机之间的SSH信任关系要提前配好,qemu+ssh需要免密登录才能跑通全程。
在线迁移属于相对进阶的运维操作,真正生产环境里我建议先在测试宿主机上跑通一次。我自己第一次迁移就栽过跟头:由于两边宿主机CPU型号不一致,虚拟机迁移后启动直接报CPU不兼容的错误。后来用virsh migrate --live demo qemu+ssh://host2/system --unsafe配合CPU型号统一调整,才算顺利。基础使用层面,先掌握迁移思路和前提条件就够了,真要跑生产迁移,还需要额外考虑存储、网络、安全组各种因素。
6. 常见问题与排查技巧实录
6.1 虚拟机启动失败:先分清三类错误
用得越多,遇到start失败的频率就越高。我帮你把最常见的报错归好类,排查会快很多。
第一类是XML配置错误。启动时提示类似“error: Failed to start domain”后面跟着XML解析错误,那多半是配置里的设备地址冲突、控制器类型不对,或者引用了不存在的存储路径。这时用virsh dumpxml demo导出现有配置,对照检查,或者直接用virt-xml-validate做校验。
第二类是磁盘文件权限问题。libvirtd服务通常以root身份运行,但如果你把镜像文件放在某个普通用户目录下,且目录没有正确的读权限,qemu进程就没法读取镜像。表现为启动失败,但日志里能看到“permission denied”。解决办法要么调整目录权限,要么把镜像挪到默认目录池里。
第三类是物理资源不足。宿主机的CPU、内存不够时,start命令也能失败,报错里通常有“cannot allocate memory”字样。检查方法很简单:
virsh freecell这条命令能显示当前NUMA节点上剩余的内存资源,如果可用值很小,先考虑停掉一些闲置虚拟机或者增加宿主内存。
6.2 控制台卡住、ping不通,排查顺序是什么
控制台没反应,不要觉得是virsh的问题。先确认虚拟机有没有在运行,然后确认XML配置里有没有串口设备。用这条命令查看设备列表:
virsh domiflist demo virsh dumpxml demo | grep -A 5 "console"如果串口部分没有配置ttyS0相关的参数,控制台是连不进的。解决方案是修改grub配置加console=ttyS0,115200,重启虚拟机后控制台就正常了。
ping不通虚拟机时,按这个顺序排查:先virsh domiflist确认网卡存在,再检查虚拟机内部网络是否配置了正确的IP,如果虚拟机网卡是NAT模式,还要确认宿主机上iptables的MASQUERADE规则没被其他规则覆盖。多数情况下,问题出在虚拟机内部网卡没启用,而不是宿主机网络坏了。
6.3 libvirtd服务异常时的快速定位
如果发现virsh命令执行时一直提示“unable to connect to libvirtd”,先确认libvirtd进程状态。有时候并不是服务挂了,而是socket激活模式在等待第一次连接。但如果服务真的起不来,日志是最好的线索:
journalctl -u libvirtd -n 100常见的一个坑是:系统里残留了旧版本的libvirt配置,升级后服务启动失败,日志里提示加载模块冲突。解决办法通常是把相关配置文件备份后重新生成。还有种情况是误删了/var/lib/libvirt下的状态目录,导致libvirt无法恢复之前的虚拟机状态。遇到这种问题,先检查/var/lib/libvirt/qemu目录是否存在且权限正确,再重启libvirtd服务。
排查时我有个习惯:遇到不认识的报错,先跑一次virsh -c qemu:///system version确认版本号,很多错误跟版本特性相关,旧版本的libvirt不支持新XML里的某些字段,报错信息又晦涩,这时候升级libvirt往往比改配置更有效。
我现在重装完一台新的宿主机,第一件事一定是用virsh把常用虚拟机模板重新define一遍,而不是急着打开图形界面。原因很简单,命令行操作天然适合沉淀成脚本和文档,把命令保存成文件,换一台机器也能照跑。最后分享一个小习惯:任何一台要上生产环境的虚拟机,我都会在首次启动前virsh dumpxml demo > demo-init.xml备份一份初始配置,之后每次更新硬件或磁盘,再导出一份新的XML作为标记。这样即使环境出了大问题,也能照着重现配置。virsh就是这样一个工具,它不会替你做决策,但会把所有操作留成清晰可追溯的接口,越用越顺手。