手里的 Rocky 10 虚拟机最近开始卡得离谱:CPU 在宿主机里动不动就 100%,Guest 系统里打开一个配置界面都要等好几秒,应用更是一顿一顿的。一开始我以为是资源不够,先后加了 vCPU 和内存,结果几乎没有改善。后来抱着试试看的心态,进虚拟机里执行了一个命令,才确认一个让人哭笑不得的事实:这台虚拟机根本没有跑在 KVM 上,而是退回了纯软件模拟。
虚拟机的“慢”有很多种原因,但如果你问我在 Rocky 10 这种 Linux 虚拟化环境里排障,第一件事永远不是调参数,也不是加资源,而是先确认虚拟机到底有没有真正使用 KVM 加速。因为 KVM 和 QEMU 软件模拟之间的性能差距,不是百分之二十三十的问题,而是数量级的问题。本文就围绕这个判断,把确认方法、常见原因、修复路径和长期预防一次说清楚。
1. 虚拟机慢,为什么第一步要先问“它真的是 KVM 吗”
1.1 一个典型的“慢”现场
先还原一个我遇到的场景。
某台测试机起初在物理服务器上跑得很正常,后来因为机房调整,用备份镜像迁移到了另一台机器。迁移之后,虚拟机里的 Rocky 10 开始出现明显的卡顿。打开终端窗口有延迟,编译一个项目的时间比以前长了三倍,查看系统监控时始终能看到一个 QEMU 进程的 CPU 占用很高。
这时常见的排障思路是什么?先看内存够不够,再看磁盘 I/O,然后逐个排查 Guest 内部的进程,最后甚至会把压力归到网络或负载均衡上。但如果你在宿主机上用top看,会发现占用 CPU 的是qemu-system-x86_64进程本身,而这个进程正是虚拟机所有资源和指令的执行者。也就是说,性能瓶颈极有可能发生在虚拟化层面,而不是 Guest 内部。
如果这时候不去确认虚拟化加速是否生效,后面很多优化动作都是无效的。加 CPU、加内存、调节 I/O 调度,可能都解决不了根本问题。因为问题的本质不是“资源不够”,而是“指令执行方式从硬件加速退回到了软件翻译”。
1.2 慢的本质:KVM 和软件模拟差在哪儿
要理解为什么必须先确认这一点,得先搞清楚 KVM 和 QEMU 的关系。
KVM 是 Linux 内核里的虚拟化模块,它利用 CPU 的硬件虚拟化扩展(Intel 的 VT-x 或 AMD 的 SVM),把 Guest 中的大部分特权指令直接交给物理 CPU 执行。QEMU 则负责模拟设备、管理内存、处理 I/O 等,但它本身在指令执行层面有两个模式:
- 当
/dev/kvm存在并且 QEMU 以-accel kvm方式启动时,Guest CPU 指令会尽可能直接跑在物理 CPU 上,这叫硬件辅助虚拟化。 - 当没有 KVM 加速时,QEMU 会退回到 TCG(Tiny Code Generator)纯软件模拟,把 Guest 的二进制指令翻译成宿主机指令再执行。这种翻译过程有一个比较直观的表现:CPU 会一直忙,但实际进度很慢。
用一个不太严谨但容易理解的类比:KVM 加速有点像一个掌握同声传译能力的翻译,说话人讲一句,他几乎同步翻译一句;而 TCG 则是先录下整段话,再逐句查字典翻译,过程中还要反复确认是否理解正确。翻译能力的差距直接体现在交流效率上。
具体到虚拟机上,普通工作负载在 TCG 模式下性能下降一两倍很正常,某些 CPU 密集型的计算任务下降得更多。这不是“慢一点”,而是“不可用”。所以虚拟机变慢时,第一步先确认“是不是跑在 KVM 上”,是整个排障流程的地基。
2. 三种确认方式:从宿主到 Guest,从进程到日志
确认一个虚拟机是否真的跑在 KVM 上,比很多人想象中简单。不需要复杂工具,只要按宿主、配置、Guest 三层来验证。
2.1 宿主侧:先看 /dev/kvm 和 QEMU 进程参数
第一种方式是在宿主机上直接看虚拟机的实际运行参数。KVM 加速的前提是宿主机存在/dev/kvm设备节点,并且内核已经加载了对应的 KVM 模块。
# 查看 /dev/kvm 设备是否存在 ls -l /dev/kvm # 查看 KVM 内核模块是否加载 lsmod | grep kvm # 查看 CPU 是否支持虚拟化扩展(物理机或已开启嵌套的虚拟机) grep -E 'vmx|svm' /proc/cpuinfo正常情况下,/dev/kvm应该是一个字符设备,权限通常属于kvm组。lsmod会看到kvm_intel或kvm_amd模块,以及kvm模块本身。
接下来看 QEMU 进程是怎样启动的。如果你用的是 libvirt 管理虚拟机:
# 查看所有虚拟机列表 virsh list --all # 查看正在运行的 QEMU 进程的完整命令行 ps -ef | grep qemu-system重点关注命令行里有没有这些东西:
-machine accel=kvm-cpu host- 或者在 QEMU 参数中出现
accel=kvm
如果看到的是-accel tcg,或者没有显式的kvm加速参数,那就要高度怀疑这台虚拟机和 KVM 加速没有关系了。
2.2 Guest 侧:systemd-detect-virt 和 CPU 型号的真相
在虚拟机内部验证是更直接的判断方式。Rocky 10 使用 systemd,所以天然有一个命令可以用。
systemd-detect-virt如果虚拟机确实跑在 KVM 加速上,这条命令通常会输出kvm。如果输出的是qemu,说明它运行在 QEMU 模拟环境里,但不能确定是否硬件加速;如果输出none,那就是物理机或某些特殊虚拟化环境。
再配合看 CPU 信息:
lscpu在 KVM 加速且 CPU 模式为host-passthrough或host-model的情况下,Guest 里的 CPU 型号会和宿主机比较接近,通常能看到真实 CPU 系列名。如果看到QEMU Virtual CPU version 2.5+这类名字,多半是用了 QEMU 内置的虚拟 CPU 模型,性能会低于真实 CPU。虽然这并不代表一定没有 KVM 加速,但至少说明配置没有把加速能力充分发挥出来。
2.3 配置侧:libvirt XML 里的虚拟化类型
如果你使用virsh管理虚拟机,配置信息保存在 domain XML 里。里面最关键的一行是<domain>标签的type属性。
# 查看虚拟机的 XML 配置 virsh dumpxml <虚拟机的名字>正常情况下,KVM 加速配置应该是:
<domain type='kvm'>如果显示是:
<domain type='qemu'>那就说明 libvirt 会默认使用纯 QEMU 模式启动虚拟机,也就是 TCG 软件模拟。这是最容易识别的信号之一。
同样重要的是 XML 里的 CPU 配置:
<cpu mode='host-passthrough'/>或:
<cpu mode='host-model'/>如果看到的是:
<cpu mode='custom' match='exact'> <model fallback='allow'>qemu64</model> </cpu>那么即使已经用了 KVM 加速,CPU 指令集也会被限制在一个非常保守的兼容模型上,部分对指令集敏感的工作负载照样会变慢。
3. 没有跑在 KVM 上的常见原因和修复路径
确认了“没有真正跑在 KVM 上”之后,接下来就要弄清楚为什么。根据经验,最常见的原因集中在硬件、内核模块、权限和配置这四个层面。
3.1 硬件虚拟化没有打开或不可用
KVM 必须依赖 CPU 硬件虚拟化扩展。如果宿主机是物理机,要先确认 BIOS/UEFI 里是否开启了 Intel VT-x 或 AMD-V。不同厂商的主板菜单不一样,但关键词通常有Intel Virtualization Technology、SVM Mode等。确认方法是在宿主机上执行:
grep -E 'vmx|svm' /proc/cpuinfo有输出说明 CPU 支持且当前环境可用。没有输出,就需要进 BIOS 开启,开启后重启再检查。
还有一种情况容易被忽略:宿主机本身也是一个虚拟机。比如在这台虚拟机里再跑 Rocky 10 虚拟机,而底层没有开启嵌套虚拟化,那么上层 Guest 自然就用不了 KVM。这种场景下要先去底层宿主确认嵌套虚拟化参数,而不是在 Guest 里反复折腾。
3.2 KVM 模块没加载、被版本升级覆盖
如果 CPU 支持虚拟化,但执行lsmod | grep kvm没有输出,说明内核模块没有加载。可以先手动加载再确认:
# Intel CPU modprobe kvm_intel # AMD CPU modprobe kvm_amd加载成功后再次执行lsmod | grep kvm和ls -l /dev/kvm。如果加载时报错,查看内核日志:
dmesg | grep -i kvm常见报错信息包括 “module is already loaded” 或 “Operation not supported” 等。其中Operation not supported往往说明当前环境没有可用硬件虚拟化能力。在 Rocky 10 这类系统上,内核一般会携带 KVM 模块,但不会保证一定会自动加载。为了避免重启后回到“无加速”状态,可以把对应的模块写入自动加载配置:
# /etc/modules-load.d/kvm.conf kvm kvm_intel等下次机器重启后,再检查模块是否自动加载。
3.3 权限和嵌套虚拟化配置
有时候/dev/kvm设备存在,但当前运行虚拟机的用户没有访问权限,导致 libvirt 在启动时无法使用 KVM 加速。这种问题通常表现为创建虚拟机失败,或者 libvirt 自动回退到 QEMU 模式。
解决方法是把运行虚拟机的用户加入kvm组:
usermod -aG kvm your-user新添加的组成员关系需要重新登录才能生效。如果使用 root 运行 libvirtd,则需要确认/etc/libvirt/qemu.conf中配置的user和group是否有权限访问/dev/kvm。
嵌套虚拟化场景还要额外检查:
# 查看是否允许嵌套 cat /sys/module/kvm_intel/parameters/nested输出为1、Y或y表示允许。如果为0,需要卸载模块后重新加载,并设置嵌套参数:
modprobe -r kvm_intel modprobe kvm_intel nested=1不过在正式环境里,我更建议在引导配置中固定这个参数,避免每次手工处理。
3.4 修复后如何验证
修复不能只用一条命令确认。我习惯按这个顺序走一遍:
- 宿主机检查
grep -E 'vmx|svm' /proc/cpuinfo是否正常。 - 检查
lsmod | grep kvm是否已经加载。 - 检查
ls -l /dev/kvm是否存在。 - 启动一台测试虚拟机。
- 在宿主机上用
ps -ef | grep qemu-system查看 QEMU 参数是否包含accel=kvm。 - 进入 Guest 执行
systemd-detect-virt,看输出是否为kvm。
这里有一个容易被忽略的建议:不要一次性把所有虚拟机都启动。先挑一台测试机验证,确认加速已经生效后,再批量恢复业务虚拟机。否则如果配置本身有问题,可能所有虚拟机都会以错误的模式运行很长时间。
4. 一套可复用的“KVM 加速自检”流程
前面这些命令每次逐步执行也能用,但排障最怕的是漏掉某一层。把检查步骤固化成一个脚本,可以明显减少人为遗漏。
4.1 快速检查脚本设计思路
脚本只需要做“检查”和“提示”,不需要直接修改系统。原则是尽早暴露出问题,而不是自动修复。下面是一个适合作为参考的脚本结构:
#!/usr/bin/env bash echo "== 1. CPU 硬件虚拟化扩展 ==" if grep -E 'vmx|svm' /proc/cpuinfo > /dev/null; then echo "OK: CPU 支持虚拟化扩展" else echo "FAIL: 未检测到 vmx/svm,请在 BIOS/固件中开启,或确认嵌套虚拟化已开启" fi echo "" echo "== 2. KVM 内核模块 ==" if lsmod | grep -q '^kvm'; then lsmod | grep '^kvm' else echo "WARN: 没有检测到 KVM 模块" fi echo "" echo "== 3. /dev/kvm 设备 ==" if [ -e /dev/kvm ]; then ls -l /dev/kvm else echo "FAIL: /dev/kvm 不存在" fi echo "" echo "== 4. QEMU 进程参数 ==" if pgrep -f qemu-system > /dev/null; then ps -ef | grep qemu-system | grep -v grep || true else echo "INFO: 当前没有正在运行的 QEMU 进程" fi脚本本身不复杂,但它能在第一时间区分出问题在哪一层。实际使用中,可以根据业务环境补充“检查 libvirt XML”的步骤,比如用virsh dumpxml提取<domain>的type属性,再判断是不是kvm。
4.2 纳入日常健康检查
单次执行脚本只是排障,长期价值在于把它放进日常巡检或监控系统。
现在很多团队已经有周期监控,但监控位置往往停留在“虚拟机能不能通”、“磁盘空间是否够用”这一层。更建议至少把以下两个指标纳入检查:
- 宿主机上是否存在应该运行但退化为 TCG 模式的虚拟机。
- /dev/kvm 设备是否从可用变成不可用。
实现思路不算复杂:可以定期跑脚本,如果检测到“应该使用 KVM 加速,但进程参数没有包含 accel=kvm”的情况,就把警告发到告警群里。这一步真正能避免的是“偷偷回退”的坑,比如某个虚拟机从备份恢复后,配置被改写成了 qemu 模式,业务还在跑,只是慢得离谱。
4.3 边界:哪些情况确认是 KVM 但还是慢
必须说清楚,确认 KVM 加速只是排障的第一步,并不等于性能问题一定解决。现实中还有一种情况:确实跑在 KVM 上了,但虚拟机和宿主机两层配置都不合理。
比如宿主机没有配置 CPU pinning,虚拟机的 vCPU 频繁在不同物理核之间跳动,缓存亲和性变差;比如虚拟机的磁盘设备用的是纯 IDE 模拟,而不是 virtio;比如 Guest 内的 virtio 驱动没有安装完整,导致网络和磁盘吞吐上不去;再比如物理机的 NUMA 拓扑没有被 vCPU 内存亲和性利用好。
这些优化方向和“确认 KVM 加速”并不冲突,恰恰相反,只有先确认加速引擎已经点火,后面的优化才有意义。否则在 TCG 模式下调 I/O 队列长度,效果自然很差,因为瓶颈根本不在 I/O 配置上。
5. 落到长期维护:不要只做一次确认
很多虚拟机性能问题,最麻烦的不是当次排障,而是“今天修好了,下周又坏了”。KVM 加速失效这件事,很容易在几次看似无关的操作后悄悄发生。
5.1 升级后回退陷阱
Rocky 10 作为一个 RHEL 系发行版,内核和虚拟化相关组件都会持续更新。历史上并不缺少这种情况:系统更新后需要重启,重启后发现/dev/kvm没生成,或者 KVM 模块没有加载。
如果你没有配置/etc/modules-load.d/kvm.conf,也没有把检查命令放进运维手册里,那么一次内核升级就可能让所有虚拟机重新进入软件模拟模式。而且如果使用的是 libvirt 默认的type='qemu'配置,可能连报错都不会有,业务照样启动,只是性能崩了。
5.2 迁移/备份恢复后配置漂移
工程项目里最常见的问题是:虚拟机从 VMware 或 Hyper-V 迁移过来,或从备份镜像恢复后,domain XML 发生了变化。这类跨虚拟化平台的迁移很容易把<domain type='kvm'>改成<domain type='qemu'>,或把 CPU 模式改成兼容性最好的 qemu64。从另一个角度看,这其实是迁移工具在“降低兼容性风险”,目的是让镜像能启动。但如果迁移后不加确认,它就会成为性能隐患。
备份恢复也一样。如果你用virsh dumpxml保存过 XML,恢复时要检查保存的 XML 是否被修改过;如果你使用的管理平台自动生成 XML,也要防止平台默认模板覆盖原有配置。
5.3 一个简单的清单
建议把下列检查项保存成运维清单,每次迁移、升级、恢复后跑一遍:
| 检查项 | 期望结果 | 检查方式 |
|---|---|---|
| CPU 硬件虚拟化扩展 | 宿主机/proc/cpuinfo含 vmx 或 svm | grep -E 'vmx|svm' /proc/cpuinfo |
| KVM 内核模块 | lsmod包含 kvm 和 kvm_intel/kvm_amd | lsmod | grep kvm |
| /dev/kvm 设备 | 文件存在且可访问 | ls -l /dev/kvm |
| libvirt 域类型 | domain type 为 kvm | virsh dumpxml <vm> | grep "<domain" |
| QEMU 进程参数 | 包含 accel=kvm 或 -machine accel=kvm | ps -ef | grep qemu-system |
| Guest 虚拟化检测 | 输出 kvm | systemd-detect-virt |
| CPU 模型 | 使用 host-passthrough 或 host-model | virsh dumpxml <vm> | grep -A5 "<cpu" |
这个清单看起来很简单,但每一步都在回答“虚拟机的指令到底由谁执行”这个问题。指令执行方式正确,性能优化才有讨论的基础;指令执行方式不对,再复杂的参数调优都是在错误的地基上盖楼。
下次再遇到 Rocky 10 虚拟机变慢,先别急着加 vCPU,也别急着重装系统。花两分钟确认它真的跑在 KVM 上,这个动作成本极低,但往往能帮你省下半天甚至一天的排障时间。虚拟化性能是一条很长的链路,从 CPU 指令执行,到设备模拟,再到 Guest 驱动,每一环都有优化空间。而这条链路的第一环,永远是确认加速引擎已经点火。