维护过 KVM/libvirt 虚拟化环境的朋友应该都遇到过这样一个“惊魂时刻”某天登录服务器打开 virt-manager 或执行virsh list --all发现原本好好的虚拟机列表变成了空或者只剩零星几台而磁盘镜像文件明明还在/etc/libvirt/qemu/下的 XML 配置文件也完好无损。第一反应往往是“虚拟机是不是丢了是不是有人误删了是不是文件系统出问题了”实际上虚拟机大概率没丢真正的问题是你当前连接的根本不是之前那套 libvirt 环境。这个现象在 Rocky Linux 10、AlmaLinux 10、CentOS Stream 10 这类较新版本系统上并不少见。随着 libvirt 守护进程从单一大进程拆分为多个模块化守护进程以及虚拟化工具链支持普通用户 rootless 模式一台宿主机上同时存在多套 libvirt 实例的情况变得越来越常见。本文就以 Rocky 10 环境为例完整拆解这个“虚拟机没丢但连到了另一套 libvirt”的排障过程。1. 先搞清楚“虚拟机丢了”的真实场景1.1 典型现象在真实运维中以下几种表现最容易让人误判用virsh list --all查看虚拟机列表是空的或者缺少某几台关键虚拟机。打开 virt-manager只看到QEMU/KVM连接名下有一台刚刚新建的测试虚拟机之前生产环境里的虚拟机全部消失。重启服务器后虚拟机没有自动启动virsh list显示为空但/var/lib/libvirt/images/下的 qcow2 磁盘文件都还在。用systemctl list-units | grep qemu或ps aux | grep qemu能看到进程但virsh就是查不到对应虚拟机。还有一个很容易踩的坑在终端里用普通用户执行virt-install安装了一台新虚拟机创建成功后再用sudo virsh list却看不到它重启之后它也没有自动启动。如果出现以上任何一种情况先不要急着恢复备份、重建虚拟机大概率是连接到了错误的 libvirt 实例。1.2 为什么会出现“另一套 libvirt”很多人对 libvirt 的理解是“宿主机上只有一个 libvirtd 守护进程virsh 就是连这个进程”。这个理解在早期版本基本没错但现在已经不准确了。较新的 Linux 发行版中libvirt 支持两种运行模式qemu:///system由系统 libvirtd 守护进程管理通常在 root 权限下运行虚拟机文件位于/etc/libvirt/qemu/、/var/lib/libvirt/。qemu:///session以当前用户身份运行的 session 级 libvirt属于普通用户的私有环境配置文件位于~/.config/libvirt/qemu/。关键点在于不同的连接 URI 对应的可能是完全不同的 libvirt 实例。如果你之前一直通过qemu:///system管理虚拟机后来某个图形工具或者终端默认连接到了qemu:///session那你看到的虚拟机列表自然就是另一套环境了。除此之外libvirt 在新版本中还引入了 modular daemon 模式例如virtqemud、virtnetworkd、virstoraged等按功能拆分的守护进程。某些软件安装后可能会启用自己的 socket而不是复用原有的libvirtd.socket。这也会导致“同一个命令行工具连的却是不同后端”的情况。2. 核心概念libvirt 连接 URI 与守护进程模型2.1 什么是 libvirtlibvirt 是一个开源的虚拟化管理 API它提供了一套统一的接口让上层工具能够管理不同的虚拟化平台包括 KVM/QEMU、Xen、LXC 等。常见的virsh、virt-manager、virt-install、virt-top都是 libvirt 生态中的客户端工具。当你在终端输入virsh list --all时本质上是在向 libvirt 守护进程发送查询请求而不是直接读取 XML 文件。因此你最终看到的是哪一个守护进程里的虚拟机列表完全取决于连接 URI。2.2 关键连接 URI 对照URI含义配置目录可见域qemu:///system系统级 QEMU/KVM/etc/libvirt/qemu/全局可见任何有权限的用户都能管理qemu:///session用户级 QEMU/KVM~/.config/libvirt/qemu/仅限当前用户qemussh://roothost/system远程系统连接目标主机/etc/libvirt/qemu/远程宿主机的系统环境qemutcp://roothost/system远程 TCP 连接目标主机/etc/libvirt/qemu/需要配置 libvirt TCP 权限其中最容易混淆的就是system和session两者。很多 Linux 发行版的默认行为是普通用户直接执行virsh时如果配置文件里未指定默认 URI工具可能会自动连接到qemu:///session而使用sudo virsh时则会连接到qemu:///system。2.3 多套 libvirt 实例为什么可以共存libvirt 的 session 模式实际上为每个用户创建了一套独立的 socket 和守护进程。你可以把 session 和 system 理解成两台独立的“虚拟化管理服务器”它们互不感知对方管理的虚拟机。system 实例读取/etc/libvirt/qemu/。session 实例读取/home/xxx/.config/libvirt/qemu/。它们监听的 socket 路径也不一样system 实例通常监听/var/run/libvirt/libvirt-socksession 实例通常监听/run/user/uid/libvirt/libvirt-sock因此当你用 root 执行命令时访问的是 system 实例当你切换成普通用户或者某些图形工具默认没有提升权限就启动时访问的可能就是 session 实例。两台“服务器”里的虚拟机列表自然不一样。3. 环境准备与版本说明本文的排障过程以 Rocky Linux 10 为例重点在于讲解 libvirt 多实例的识别与切换方法。确认系统是否安装了 libvirt 客户端工具rpm -qa | grep libvirt预期会出现libvirt-daemon、libvirt-client、libvirt-daemon-driver-qemu等相关软件包。如果没有安装可以使用 dnf 安装sudo dnf install -y libvirt virt-install virt-manager然后确认 libvirtd 服务状态sudo systemctl status libvirtd如果服务未运行启动并设置开机自启sudo systemctl enable --now libvirtd实际环境中系统版本、libvirt 版本可能不同但排查思路是一致的。本文的命令输出以常见的 Rocky 10 环境为参考。4. 排障实战找回“丢失”的虚拟机下面我们按步骤还原完整的排障流程。为了便于理解假设宿主机上有两台虚拟机都创建在/etc/libvirt/qemu/目录下web-server.xmldb-server.xml现在普通用户打开终端执行virsh list --all发现结果是空的。4.1 查看当前连接 URI输入以下命令查看当前virsh实际连接的是哪个 URIvirsh uri如果输出类似qemu:///session说明当前处于 session 连接。在 session 中查询list --all只会看到当前用户自己的虚拟机自然看不到 root 管理的 web-server 和 db-server。如果输出qemu:///system但虚拟机列表仍然为空则需要进一步检查/etc/libvirt/qemu/目录是否存在 XML 文件以及 libvirtd 服务是否正常加载。4.2 显示“丢失”的虚拟机执行以下命令强制连接 system 实例查看所有虚拟机virsh -c qemu:///system list --all如果这台宿主机之前的虚拟机确实在 system 实例中现在应该能看到类似输出Id 名称 状态 ------------------------------------- - web-server 关闭 - db-server 关闭到这里问题基本可以确认虚拟机没有丢只是刚才连到了另一套 session 实例。4.3 对比两个实例的配置文件为了强化确认可以分别检查两个实例的配置目录。root 管理下QEMU 虚拟机配置位于ls /etc/libvirt/qemu/普通用户 session 实例的配置位于ls ~/.config/libvirt/qemu/如果前者有两个 XML 文件后者为空或者只有刚创建的新虚拟机就能验证“连到了另一套 libvirt”的判断。4.4 检查 libvirt 相关 socket 与环境变量有时候用户当前 shell 设置了环境变量比如export LIBVIRT_DEFAULT_URIqemu:///session这会让所有 virsh 命令默认走 session。检查当前环境变量echo $LIBVIRT_DEFAULT_URI如果有输出临时取消或改为 systemunset LIBVIRT_DEFAULT_URI或者主动切换到 systemexport LIBVIRT_DEFAULT_URIqemu:///system另外socket 文件也能反映实例情况ls -l /var/run/libvirt/libvirt-sock ls -l /run/user/$(id -u)/libvirt/libvirt-sock如果两个 socket 都存在说明宿主机上确实同时运行着 system 和当前用户的两套 libvirt 环境。4.5 恢复自动启动如果“丢失”的虚拟机之前设置了开机自启切换到 system 实例后可以通过以下命令确认virsh -c qemu:///system list --autostart如果原本的 autostart 配置丢失可以重新设置virsh -c qemu:///system autostart web-server virsh -c qemu:///system autostart db-server确认无误后后续管理虚拟机时统一使用-c qemu:///system。4.6 将 session 虚拟机迁移到 system如果部分虚拟机确实创建在 session 实例中但需要纳入 system 统一管理可以参考以下思路。首先生成 session 里虚拟机的 XML 描述virsh -c qemu:///session dumpxml test-vm /tmp/test-vm.xml然后关闭该虚拟机virsh -c qemu:///session shutdown test-vm等待虚拟机完全关闭后在 system 实例中重新定义virsh -c qemu:///system define /tmp/test-vm.xml此时需要注意两点如果 session 中的虚拟机磁盘文件位于普通用户的家目录或临时目录system 实例中的 QEMU 进程可能没有足够权限访问建议先把磁盘文件移动到/var/lib/libvirt/images/或者调整目录权限。操作生产环境前务必做好 XML 和磁盘镜像的完整备份并确认业务低峰期。5. 除了 URI还有哪些容易混淆的“另一套 libvirt”5.1 模块化守护进程导致的多实例较新版本 libvirt 默认启用模块化守护进程例如virtqemud、virtnetworkd、virtnodedevd、virtstoraged。在某些升级或装包场景下系统可能同时存在旧的libvirtd服务和新的virtqemud服务两者如果没有正确协调客户端可能连接到一个旧的 socket 上而虚拟机实际由另一个守护进程管理。排查方法systemctl list-units | grep -E libvirtd|virt.*d如果发现多个与 libvirt 相关的守护进程处于 active 状态需要以实际管理虚拟机的那个进程为准。生产环境建议保持默认配置一致避免一半虚拟机走libvirtd、另一半走virtqemud。5.2 容器环境或嵌套虚拟化引入的实例如果你在容器中运行 libvirt 相关工具或者宿主机本身就是虚拟机安装了嵌套虚拟化环境那么容器内看到的 libvirt 实例与宿主机上的实例通常不是同一套。容器内的/var/run/libvirt/通常来自容器自身而不是宿主机的真实服务。排查思路在容器内执行virsh uri查看连接地址对比宿主机执行结果同时观察/proc、systemd等系统信息确认自己所在的运行环境是宿主机还是容器。5.3 远程连接配置不一致多台宿主机之间如果配置了互信用户从本机远程连接另一台宿主机时如果连接命令少了主机名或 URI 写错也可能导致看到“另一台服务器的虚拟机”。典型命令是virsh -c qemussh://rootremote-host/system list --all如果写成了virsh -c qemussh://rootlocal-host/system list --all自然看到的还是本机甚至误以为远程虚拟机丢了。5.4 virt-manager 中多个连接并存图形化工具 virt-manager 允许用户保存多个连接。如果之前在 virt-manager 中保存了QEMU/KVM 用户连接后来新增了QEMU/KVM 系统连接那么不同连接下看到的虚拟机列表是不同的。打开 virt-manager 后依次检查文件添加连接Hypervisor 是否选择 QEMU/KVM连接类型是“系统”还是“用户”需要管理全局虚拟机时应选择系统连接只是临时用普通用户测试才会考虑用户连接。6. 常见问题与排查清单6.1 常见问题对照表问题现象常见原因解决思路virsh list --all为空但/etc/libvirt/qemu/有 XML当前连接到了 session 实例使用virsh -c qemu:///system list --all查看普通用户连接 system 报错“拒绝连接”用户没有 libvirt 访问权限将用户加入libvirt组并检查 polkit 规则virt-manager 只看到新装虚拟机看不到存量虚拟机virt-manager 保存的是用户级连接添加系统级连接或者关闭 virt-manager 后使用 sudo 启动virsh -c qemu:///system能连上但 list 为空libvirtd 配置或 XML 加载异常检查/etc/libvirt/日志确认 qemu.conf 配置重启后之前创建的虚拟机没有自动启动创建时使用的是 session 实例使用 system 实例重新定义并设置 autostart环境变量导致命令默认连接 sessionLIBVIRT_DEFAULT_URI被设置执行unset LIBVIRT_DEFAULT_URI或改为qemu:///system远程连接时看到的是另一台宿主机连接 URI 主机名写错核对目标主机和当前主机 hostname6.2 快速排查清单当你再次遇到“虚拟机消失”时建议按以下顺序排查# 1. 看当前连接 virsh uri # 2. 切换到 system 看全部虚拟机 virsh -c qemu:///system list --all # 3. 看系统级 XML 目录 ls -l /etc/libvirt/qemu/ # 4. 看用户级 XML 目录 ls -l ~/.config/libvirt/qemu/ # 5. 看是否设置了默认连接环境变量 echo $LIBVIRT_DEFAULT_URI # 6. 看 libvirt 守护进程状态 systemctl status libvirtd virtqemud # 7. 看磁盘镜像文件是否完好 ls -lh /var/lib/libvirt/images/这套流程基本能在 5 分钟内定位问题。7. 最佳实践与工程建议排查完这个问题后更重要的是避免下次再踩坑。以下是一些值得养成的虚拟化运维习惯。7.1 统一使用 qemu:///system对于生产环境虚拟机的生命周期管理应统一走系统级 libvirt。普通用户 rootless 的 session 模式适合开发测试但不应成为生产虚拟机的默认环境。为避免误操作可以在相关用户的~/.bashrc中设置export LIBVIRT_DEFAULT_URIqemu:///system这样即使普通用户执行 virsh也会默认连接系统实例。7.2 使用别名锁定连接地址对于经常操作虚拟机的运维人员建议在 shell 配置中添加别名避免手误alias virshvirsh -c qemu:///system alias virt-listvirsh -c qemu:///system list --all但要注意这个别名会影响所有 virsh 子命令执行时需要确保当前用户对 system 实例有访问权限。7.3 建立 XML 配置备份机制/etc/libvirt/qemu/下的 XML 文件是虚拟机的核心描述相当于“注册表”。建议定期备份sudo mkdir -p /backup/libvirt sudo cp -r /etc/libvirt/qemu/ /backup/libvirt/在修改虚拟机配置之前virsh -c qemu:///system dumpxml web-server /backup/libvirt/web-server-$(date %F).xml改动后如果出现问题可以随时通过 define 命令恢复到任意历史版本。7.4 创建虚拟机时明确指定连接使用virt-install创建虚拟机时如果当前 shell 没有默认 URI建议显式加上连接参数sudo virt-install \ --connect qemu:///system \ --name web-server \ --memory 2048 \ --vcpus 2 \ --disk path/var/lib/libvirt/images/web-server.qcow2,size20 \ --os-variant rhel9.0 \ --network networkdefault \ --graphics none关键是--connect qemu:///system。这样创建的虚拟机一定会注册到系统级 libvirt不会出现“创建完就消失”的困惑。7.5 合理配置用户权限如果团队多人需要管理虚拟机可以通过系统组授权而不是把 root 密码到处分发。官方推荐做法是把管理员用户加入libvirt和libvirt-qemu组sudo usermod -aG libvirt user1 sudo usermod -aG libvirt user2重新登录后用户就可以连接qemu:///system查看和管理虚拟机。更细粒度的权限可以配合 polkit 规则实现。7.6 给虚拟机命名和标签规范化遇到“多套 libvirt 实例”问题时命名规范能帮你更快区分虚拟机的归属。建议生产虚拟机命名采用业务前缀例如prd-web-01、prd-db-01。测试虚拟机使用dev-、test-前缀。在 virt-manager 或 virsh 中通过名字即可判断环境来源。如果同一台宿主机上不同项目很多还可以用 libvirt 的 metadata 字段记录项目、负责人、用途。7.7 监控 libvirt 连接与虚拟机数量变化规模较大的环境可以写一个简单的巡检脚本每天检查当前宿主机 system 实例中虚拟机数量、运行状态和 XML 完整性#!/bin/bash total$(virsh -c qemu:///system list --all | grep -E 运行|running|关闭|shut off | wc -l) running$(virsh -c qemu:///system list | grep -E running | wc -l) echo 当前总虚拟机数: $total echo 当前运行虚拟机数: $running将脚本加入 crontab 或定时任务配置邮件/IM 通知可以第一时间发现虚拟机异常消失或异常关闭。7.8 生产环境变更谨慎操作当排查到需要重新 define 或迁移虚拟机的时候请牢记先备份备份 XML、磁盘镜像、存储池配置。先确认确认虚拟机确实在目标实例中并记录原 URI。先测试在测试环境验证 define、autostart、权限设置是否生效。最小权限不要随手用 root 修改所有文件尽量通过 libvirt 自身的管理命令操作。8. 总结回到一开始的问题Rocky 10 环境下virsh list显示虚拟机消失绝大多数情况不是虚拟机文件损坏或被误删而是连接到了另一套 libvirt 实例。排查时只需要三步运行virsh uri确认当前连接。切换qemu:///system查看系统级实例。对比/etc/libvirt/qemu/和~/.config/libvirt/qemu/中的 XML 文件。真正理解了 libvirt 的 URI 与守护进程模型后你会对这个报错产生“完全免疫”的效果。以后再看到“虚拟机不见了”第一件事就不会是恢复备份而是先冷静地输入一行virsh -c qemu:///system list --all