1. 从一台闲置物理机说起:为什么我又回头折腾KVM
手里攒了几台退役的塔式服务器,配置放在今天看不算亮眼,双路四核、32G内存、一块SSD加两块机械盘,但就这么闲置着实在可惜。我最初的想法很简单,把它们合并成一台能跑多个独立环境的大机器,用来做测试、跑一些内部服务、偶尔给团队做演示环境。这个需求听起来像是虚拟机该干的活,于是我又一次回到了KVM这个老朋友面前。
KVM,全称Kernel-based Virtual Machine,直译就是“基于内核的虚拟机”。它不是一个独立的软件,而是Linux内核自带的一个虚拟化模块。只要你的CPU支持硬件虚拟化指令集,加载这个模块之后,Linux本身就能变成一个Hypervisor,也就是虚拟机监视器。这件事的意义在于,你不需要额外安装一套笨重的虚拟化操作系统,宿主机该干嘛还干嘛,虚拟化能力是“长”在系统里的。
我这次要做的,就是把一台物理机改造成KVM宿主机,在上面跑起若干台虚拟机,并且让这些虚拟机之间、虚拟机和宿主机之间、虚拟机和外部网络之间都能按我的意图通信。这个过程中涉及的东西不少:CPU虚拟化指令的确认、内核模块的加载、用户态管理工具的选择、存储池的规划、网络桥接的配置、虚拟机的创建与调优。每一环都有坑,而且很多坑是文档里不会写的。
这篇文章适合谁看?如果你手里有闲置的Linux机器,想把它变成一台能跑多台虚拟机的实验平台;或者你正在学习云计算底层,想搞清楚那些云主机到底是怎么被“造”出来的;又或者你只是单纯对虚拟化好奇,想亲手摸一摸KVM的脾气,那接下来的内容应该对你有用。我会从最底层的硬件检查开始,一路讲到虚拟机的网络调优,中间穿插我自己踩过的坑和实测有效的参数。
需要提前说明的是,KVM这套东西在Linux世界里已经非常成熟,但成熟不代表没有门槛。它的门槛不在于操作有多复杂,而在于概念之间的关联需要理清楚:内核模块、QEMU、libvirt、virsh、virt-manager、桥接网络、存储池,这些名词如果只是零散地记,很容易晕。我会尽量把它们串成一条线,让你知道每一步在干什么、为什么这么干。
2. 动手之前:把KVM的技术底座摸清楚
2.1 KVM、QEMU、libvirt到底是什么关系
很多人第一次接触KVM会被这几个名词绕晕,我当初也是。这里用一个生活化的类比把它讲透。
想象你要开一家“虚拟机旅馆”,每个虚拟机就是一位客人,客人需要独立的房间(CPU、内存、磁盘、网卡)。KVM扮演的角色是旅馆的“硬件基础设施”——它利用CPU的硬件虚拟化能力,把物理资源切分成可以分配给客人的虚拟资源。但KVM本身只提供了最底层的虚拟化能力,它不会主动去“接待客人”。
真正负责接待和安排的是QEMU。QEMU是一个用户态的模拟器,它可以模拟出完整的硬件环境:主板、芯片组、显卡、网卡、磁盘控制器等等。当QEMU和KVM结合使用时,QEMU负责设备模拟和虚拟机生命周期管理,KVM负责CPU和内存的硬件加速。所以严格来说,你启动一台KVM虚拟机,实际运行的是“QEMU进程 + KVM内核模块”的组合。
那libvirt又是什么?它是旅馆的“前台管理系统”。直接调用QEMU的命令行参数非常繁琐,而且不同版本的参数还不一样。libvirt提供了一套统一的API和工具,把QEMU的复杂性封装起来。你用virsh或者virt-manager操作虚拟机,底层其实是libvirt在帮你生成QEMU命令、管理进程、维护状态。libvirt还引入了“域(domain)”这个概念来指代虚拟机,引入了“存储池(storage pool)”和“网络(network)”来统一管理资源。
理清这三者的关系,后面操作时就不会迷茫:内核模块提供能力,QEMU提供设备模拟和进程,libvirt提供管理接口。
2.2 硬件虚拟化:你的CPU到底支不支持
KVM依赖CPU的硬件虚拟化扩展。Intel平台叫VT-x,AMD平台叫AMD-V。绝大多数2010年之后的服务器和工作站CPU都支持,但不是所有都默认开启。
检查方法很简单,在Linux终端执行:
grep -Eoc '(vmx|svm)' /proc/cpuinfo如果输出大于0,说明CPU支持。vmx对应Intel的VT-x,svm对应AMD的AMD-V。如果输出是0,那要么CPU不支持,要么主板BIOS里没开启虚拟化选项。后者更常见,尤其是品牌整机,BIOS里默认可能是关闭的。
我遇到过一台机器,CPU明明是支持VT-x的,但/proc/cpuinfo里就是看不到vmx标志。进BIOS翻了一圈,发现虚拟化选项藏在“Security”菜单下的“Virtualization Technology”里,默认Disabled。开启之后保存重启,标志就出来了。
还有一个容易忽略的点:如果你是在虚拟机里再跑KVM,也就是嵌套虚拟化,需要宿主机层面也开启嵌套支持。Intel平台需要给kvm_intel模块加nested=1参数,AMD平台对应kvm_amd模块。这个场景在实验环境里很常见,比如你在云主机里想再开虚拟机测试。
2.3 内核模块加载与确认
确认CPU支持之后,加载KVM模块:
modprobe kvm modprobe kvm_intel # Intel平台 # 或 modprobe kvm_amd # AMD平台加载完成后用lsmod | grep kvm确认。正常应该看到kvm和kvm_intel(或kvm_amd)两个模块。如果kvm_intel加载失败,常见原因是BIOS没开VT-x,或者和某些其他虚拟化软件冲突。
这里有个细节:kvm模块是核心模块,kvm_intel和kvm_amd是平台相关的硬件加速模块。有些发行版会把它们编译进内核而不是作为模块,这种情况下lsmod看不到,但功能是正常的。可以用dmesg | grep kvm看内核日志确认。
提示:如果你在容器环境里操作,比如某些受限的容器,可能无法加载内核模块。KVM需要真正的宿主机权限,容器里通常跑不了。
2.4 用户态工具链的选型
管理KVM虚拟机,工具链有几套选择,我列个表对比一下:
| 工具 | 类型 | 适用场景 | 我的评价 |
|---|---|---|---|
| qemu-system-x86_64 | 命令行 | 快速测试、脚本化 | 参数极多,不适合日常管理 |
| virsh | 命令行 | 服务器环境、自动化 | 功能完整,脚本友好,首选 |
| virt-manager | 图形界面 | 桌面环境、初学者 | 直观易用,适合快速上手 |
| virt-install | 命令行 | 创建虚拟机 | 配合virsh使用,创建流程清晰 |
我的建议是:服务器环境用virsh + virt-install,桌面环境可以装virt-manager辅助。virt-manager底层也是调libvirt,和virsh操作的是同一套东西,两者可以混用。比如你用virt-manager创建了一台虚拟机,用virsh list照样能看到。
安装这些工具,不同发行版包名略有差异。以常见的两个系为例:
# Debian/Ubuntu系 apt install qemu-kvm libvirt-daemon-system libvirt-clients virtinst bridge-utils # RHEL/CentOS系 yum install qemu-kvm libvirt libvirt-client virt-install bridge-utils安装完成后启动libvirtd服务,并设置开机自启:
systemctl enable --now libvirtd systemctl status libvirtd服务起来之后,用virsh list --all测试一下,如果能看到空的虚拟机列表,说明libvirt工作正常。
3. 存储与网络:虚拟机跑起来的两条腿
3.1 存储池规划:别把镜像和磁盘混在一起
虚拟机的存储分两部分:一部分是安装镜像(ISO文件),一部分是虚拟机的磁盘镜像(qcow2或raw格式)。这两类文件建议分开存放,原因后面讲。
libvirt用“存储池”来管理存储资源。存储池可以是一个目录、一个LVM卷组、一个NFS共享等等。最常用的是目录类型的存储池。我一般会规划两个池:
iso-pool:存放安装镜像,路径比如/data/libvirt/isovm-pool:存放虚拟机磁盘,路径比如/data/libvirt/images
创建目录并定义存储池:
mkdir -p /data/libvirt/iso /data/libvirt/images virsh pool-define-as iso-pool dir --target /data/libvirt/iso virsh pool-build iso-pool virsh pool-start iso-pool virsh pool-autostart iso-pool virsh pool-define-as vm-pool dir --target /data/libvirt/images virsh pool-build vm-pool virsh pool-start vm-pool virsh pool-autostart vm-pool用virsh pool-list --all确认两个池都是active状态。
为什么建议分开?因为ISO文件通常很大且只读,虚拟机磁盘是频繁读写的。如果放在同一个目录,备份和迁移时不好区分。另外,如果磁盘空间紧张,你可以单独清理ISO池而不影响运行中的虚拟机。
3.2 磁盘格式选择:qcow2还是raw
创建虚拟机磁盘时,libvirt默认用qcow2格式。qcow2支持稀疏分配、快照、压缩、加密,功能丰富。raw格式则是裸格式,性能略好,但不支持快照,空间是预分配的。
我实测下来的感受是:对于大多数测试和开发场景,qcow2完全够用,性能差异在日常使用中几乎感知不到。只有在高IO压力的生产场景,raw格式的优势才会体现。而且qcow2的稀疏特性很实用——你创建一个100G的磁盘,实际占用可能只有几百K,随着数据写入才逐渐增长。
创建磁盘的命令:
virsh vol-create-as vm-pool test-vm.qcow2 20G --format qcow2这条命令在vm-pool里创建一个20G的qcow2磁盘。注意,20G是虚拟大小,实际占用取决于写入数据量。
注意:qcow2磁盘长期使用后可能出现碎片,影响性能。可以用
qemu-img convert做一次压缩整理,把稀疏块合并。操作前务必关闭虚拟机。
3.3 网络桥接:让虚拟机和宿主机平起平坐
KVM虚拟机的网络模式有好几种,最常用的是NAT模式和桥接模式。
NAT模式下,libvirt默认创建一个virbr0网桥,虚拟机通过它做地址转换访问外网。虚拟机可以访问外部,但外部无法直接访问虚拟机,除非做端口转发。这种模式配置简单,适合个人测试。
桥接模式下,虚拟机直接接入物理网络,和宿主机处于同一网段,拥有独立的IP,外部可以直接访问。这种模式适合需要对外提供服务的场景。
我这次的需求是让虚拟机能被局域网其他机器访问,所以选了桥接模式。配置桥接需要修改宿主机网络配置,不同发行版方式不同。以NetworkManager管理的系统为例,可以用nmcli创建桥接:
nmcli con add type bridge ifname br0 con-name br0 nmcli con add type bridge-slave ifname eth0 master br0 nmcli con modify br0 ipv4.addresses 192.168.1.10/24 nmcli con modify br0 ipv4.gateway 192.168.1.1 nmcli con modify br0 ipv4.dns 192.168.1.1 nmcli con modify br0 ipv4.method manual nmcli con up br0这里把物理网卡eth0桥接到br0上,br0拥有IP地址。虚拟机创建时指定桥接网络,就会接入br0,从而和宿主机在同一网段。
提示:桥接配置有个经典坑——如果你是通过SSH远程操作宿主机,修改网络配置时可能把自己踢下线。建议在本地控制台操作,或者用
nmcli的交互模式,确认配置无误再应用。我吃过这个亏,远程改桥接,网卡一重启SSH就断了,只能跑机房。
3.4 网络模式对比与选择建议
| 模式 | 虚拟机访问外网 | 外部访问虚拟机 | 配置复杂度 | 适用场景 |
|---|---|---|---|---|
| NAT | 可以 | 需端口转发 | 低 | 个人测试、临时环境 |
| 桥接 | 可以 | 可以直接 | 中 | 对外服务、集群实验 |
| 仅主机 | 不可以 | 宿主机可以 | 低 | 隔离测试 |
| 直通 | 取决于物理网络 | 取决于物理网络 | 高 | 高性能网络场景 |
我的经验是:实验环境优先用NAT,省事;需要对外服务时再切桥接。桥接虽然方便,但会占用局域网IP,如果虚拟机数量多,IP管理是个负担。另外,某些企业网络对桥接有限制,交换机端口可能只允许一个MAC地址,这种情况下桥接会失败,需要联系网络管理员调整。
4. 创建第一台虚拟机:从命令行到系统安装
4.1 virt-install参数详解
创建虚拟机用virt-install命令。这个命令的参数很多,但常用的就那么几个。我以创建一台2核4G、20G磁盘的虚拟机为例:
virt-install \ --name test-vm01 \ --memory 4096 \ --vcpus 2 \ --cpu host-passthrough \ --disk vol=vm-pool/test-vm01.qcow2,format=qcow2,bus=virtio \ --cdrom /data/libvirt/iso/install.iso \ --network bridge=br0,model=virtio \ --graphics vnc,listen=0.0.0.0,port=5901 \ --os-variant generic \ --boot cdrom,hd \ --noautoconsole逐条解释关键参数:
--name:虚拟机名称,在libvirt里唯一。--memory:内存大小,单位MB。--vcpus:虚拟CPU数量。--cpu host-passthrough:CPU模式,让虚拟机看到宿主机的真实CPU特性,性能最好。如果要做虚拟机迁移,建议用host-model。--disk:磁盘配置。vol=vm-pool/test-vm01.qcow2指定存储池和卷名,bus=virtio使用virtio总线,性能远好于IDE。--cdrom:安装镜像路径。--network:网络配置,bridge=br0接入桥接,model=virtio使用virtio网卡。--graphics:图形输出方式,VNC监听所有地址,端口5901。--os-variant:操作系统类型,帮助libvirt优化默认配置。可以用osinfo-query os查询支持的列表。--boot:启动顺序,先光驱后硬盘。--noautoconsole:不自动打开控制台,适合脚本化。
4.2 连接VNC完成系统安装
虚拟机启动后,用VNC客户端连接宿主机的5901端口,就能看到安装界面。如果宿主机有防火墙,需要放行对应端口:
firewall-cmd --add-port=5901/tcp --permanent firewall-cmd --reload安装过程和物理机装系统没区别,分区、设置用户、装软件包。唯一需要注意的是,安装完成后重启时,记得把启动顺序改成硬盘优先,或者直接弹出ISO。用virsh可以修改:
virsh change-media test-vm01 sda --eject或者直接编辑虚拟机配置:
virsh edit test-vm01把<boot order='1'/>从cdrom移到disk上。
4.3 安装后的基础优化
系统装好之后,有几件事建议立刻做:
第一,安装virtio驱动。虽然现代Linux内核自带virtio驱动,但Windows系统需要额外安装。Linux系统确认一下lsmod | grep virtio,应该能看到virtio_net、virtio_blk、virtio_pci等模块。
第二,安装qemu-guest-agent。这个代理程序让宿主机能获取虚拟机的内部信息,比如IP地址、文件系统使用情况,还支持优雅关机。安装命令:
# Debian/Ubuntu apt install qemu-guest-agent # RHEL/CentOS yum install qemu-guest-agent systemctl enable --now qemu-guest-agent装好之后,在宿主机用virsh domifaddr test-vm01就能直接看到虚拟机的IP,不用登录进去查。
第三,调整磁盘缓存模式。默认的缓存模式是none,对于qcow2磁盘,我一般改成writeback,写入性能更好,但断电时可能丢数据。如果虚拟机里跑的是数据库,建议保持none或writethrough。这个取舍看场景。
4.4 虚拟机生命周期管理常用命令
日常管理虚拟机,这几个virsh命令用得最多:
virsh list --all # 列出所有虚拟机 virsh start test-vm01 # 启动 virsh shutdown test-vm01 # 优雅关机 virsh destroy test-vm01 # 强制断电 virsh reboot test-vm01 # 重启 virsh suspend test-vm01 # 暂停 virsh resume test-vm01 # 恢复 virsh undefine test-vm01 # 删除虚拟机定义 virsh console test-vm01 # 连接串口控制台virsh console需要虚拟机内部配置了串口终端,否则连上去是黑的。配置方法是在虚拟机里修改内核参数,加上console=ttyS0,并启用getty服务。这个在调试无图形界面的虚拟机时很有用。
5. 性能调优与常见问题排查
5.1 CPU和内存的性能调优
CPU方面,host-passthrough模式性能最好,但牺牲了迁移兼容性。如果虚拟机需要在不同CPU的宿主机之间迁移,用host-model。另外,可以给虚拟机绑定物理CPU核心,减少上下文切换:
virsh vcpupin test-vm01 0 2 virsh vcpupin test-vm01 1 3这条命令把虚拟机的vCPU 0绑定到物理CPU 2,vCPU 1绑定到物理CPU 3。绑定前先用lscpu或virsh nodecpustats了解物理CPU拓扑。
内存方面,KVM支持内存气球(balloon)技术,可以动态调整虚拟机内存。启用方法是在虚拟机配置里加:
<memoryBacking> <memballoon model='virtio'/> </memoryBacking>然后在宿主机用virsh setmem test-vm01 2048动态调整。不过气球技术需要虚拟机内部驱动配合,且调整有下限,不能低于系统运行所需的最小内存。
5.2 磁盘IO性能优化
磁盘性能是虚拟机最容易出现瓶颈的地方。几个优化方向:
第一,使用virtio-blk或virtio-scsi总线。virtio-scsi支持更多设备,且性能更好。配置方法:
<disk type='volume' device='disk'> <driver name='qemu' type='qcow2' cache='writeback' io='native'/> <source pool='vm-pool' volume='test-vm01.qcow2'/> <target dev='sda' bus='scsi'/> </disk>io='native'启用Linux原生异步IO,cache='writeback'启用写缓存。
第二,考虑使用LVM或裸设备作为虚拟机磁盘。qcow2虽然方便,但多了一层文件系统抽象,性能有损耗。如果追求极致性能,可以在LVM卷上直接创建虚拟机磁盘。
第三,定期整理qcow2碎片。用qemu-img convert把磁盘转换一次,可以合并稀疏块、压缩空间:
qemu-img convert -O qcow2 -c test-vm01.qcow2 test-vm01-compact.qcow2-c参数启用压缩。转换完成后替换原文件,注意先关闭虚拟机。
5.3 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方法 |
|---|---|---|---|
| 虚拟机启动失败,提示KVM不可用 | CPU虚拟化未开启 | grep vmx /proc/cpuinfo | 进BIOS开启VT-x/AMD-V |
| 虚拟机网络不通 | 桥接配置错误 | brctl show查看桥接 | 检查物理网卡是否加入桥接 |
| VNC连接黑屏 | 显卡模式不兼容 | 查看虚拟机日志 | 改用cirrus或vga显卡 |
| 磁盘IO极慢 | 缓存模式不当 | virsh dumpxml查看配置 | 调整cache和io参数 |
| 虚拟机无法关机 | 未装guest-agent | virsh domifaddr测试 | 安装qemu-guest-agent |
| 迁移失败 | CPU模式不兼容 | 对比宿主机CPU特性 | 改用host-model或自定义CPU |
5.4 我踩过的几个坑
第一个坑:桥接网络配置后宿主机失联。前面提过,远程改网络配置风险很大。后来我的做法是,先用nmcli创建一个新的桥接连接但不立即启用,确认配置无误后,用nmcli con up br0切换,同时保留一个备用SSH连接。如果切换后失联,备用连接还能救回来。
第二个坑:qcow2磁盘空间只增不减。虚拟机里删了文件,宿主机上的qcow2文件大小不变。这是因为qcow2不会自动回收未使用的块。解决方法是用virt-sparsify工具做稀疏化:
virt-sparsify --compress test-vm01.qcow2 test-vm01-sparse.qcow2这个操作需要虚拟机停机,且耗时较长,但能显著回收空间。
第三个坑:虚拟机时间不同步。KVM虚拟机默认使用UTC时间,如果虚拟机里设置的是本地时间,会出现时间偏移。解决方法是在虚拟机配置里加:
<clock offset='localtime'/>或者在虚拟机内部配置NTP同步。
第四个坑:大页内存未启用导致性能不佳。对于内存密集型应用,启用大页内存能减少TLB miss,提升性能。宿主机上配置:
echo 1024 > /proc/sys/vm/nr_hugepages然后在虚拟机配置里加:
<memoryBacking> <hugepages/> </memoryBacking>注意大页内存需要预分配,且虚拟机内存必须是2M的整数倍。
6. 从单机到实验平台:后续扩展思路
一台宿主机跑几台虚拟机只是起点。如果想把这套东西扩展成一个像样的实验平台,有几个方向可以走。
第一个方向是集中管理。libvirt支持远程连接,在管理机上装virt-manager,通过qemu+ssh://宿主机IP/system连接到多台宿主机,统一管理所有虚拟机。这样不用每台机器都跑图形界面。
第二个方向是模板化。把装好的虚拟机做成模板,后续创建新虚拟机时直接克隆,省去重复安装系统的时间。克隆命令:
virt-clone --original test-vm01 --name test-vm02 --file /data/libvirt/images/test-vm02.qcow2克隆前需要把模板虚拟机里的机器ID、MAC地址等唯一信息清理掉,否则克隆出来的虚拟机会冲突。Linux系统可以用virt-sysprep工具自动清理。
第三个方向是自动化编排。libvirt有Python绑定,可以写脚本批量创建、启动、销毁虚拟机。配合Ansible等配置管理工具,能实现整套实验环境的自动化部署。我试过用Python的libvirt模块写了一个小脚本,根据配置文件批量创建虚拟机,几十台机器的环境几分钟就能拉起来。
第四个方向是结合容器。KVM虚拟机和容器不是对立的,可以混合使用。比如用虚拟机跑需要完整内核的环境,用容器跑轻量级服务,两者通过桥接网络互通。这种混合架构在实际生产中很常见。
最后分享一个我在实际使用中的体会:KVM的稳定性非常高,我有一台宿主机连续运行了大半年没重启,上面的虚拟机也一直稳定工作。但稳定不代表可以不管,定期检查磁盘空间、查看虚拟机日志、更新宿主机内核和QEMU版本,这些日常维护不能省。尤其是QEMU的安全更新,涉及虚拟机逃逸漏洞的补丁要及时打。
另外,如果你打算在生产环境用KVM,建议先在小规模环境充分测试,特别是存储和网络的性能表现。不同硬件组合下的表现差异可能很大,别人的调优参数不一定适合你的机器。多测、多调、多记录,慢慢就能摸清自己这套环境的脾气。