Linux KVM 虚拟机配置教程
坑边闲话:KVM (Kernel-based Virtual Machine) 是一个基于 Linux 内核的开源虚拟化技术,它在 2006年 由 Avi Kivity 和他的团队开发,并于 2007年 合并到 Linux 内核中,成为主流虚拟化解决方案之一。KVM 使用较为方便,生态非常良好,本文介绍如何在 Linux 上配置并使用 KVM 虚拟机,同时会介绍 Intel VT-D、AMD-V、IOMMU 等硬件功能。
1. 初始化 Linux 系统·
在开始之前,读者需要有一个支持裸机安装的 Linux 系统,本文以 Debian 12 为例。
强烈建议使用最小化安装程序,因为有些新手非常依赖图形化界面配置虚拟机,导致迟迟无法深入理解技术细节,学到最后发现只是学了一个类似 VMware Workstation Pro 的软件。GUI 是用户友好型发明,但是对于学习技术、了解细节、拓展思维并不友好。
1.1 调整 Console 界面字体大小·
最小化安装的 Linux 没有桌面环境,只有一个 console tty,因此建议执行以下命令,将界面字体设置为 Termius
,并按照自己的喜好和视力设置一个合适的字体大小。
1 | sudo dpkg-reconfigure console-setup |
2560x1600 分辨率的 16 英寸显示器,建议选择 Termius 最大号字体。
1.2 确定 CPU 是否支持硬件虚拟化·
1 | lscpu | grep Virtualization |
如果输出不为 0,则可认为该平台支持硬件虚拟化。
1.3 安装 KVM 组件·
如果根据笔者的这篇文章进行了安装,则无需安装下列任何软件,因为安装 cocipit-machines
时,已经把与 KVM 虚拟机相关的组件都安装好了。否则,请根据下列内容执行。
1 | sudo apt install bridge-utils |
如果要使用 Linux 图形化桌面,可以再安装一组图形化工具以方便日常查看,实现类似 VMware Workstation 的效果。命令如下:
1 | sudo apt install virt-manager virt-viewer |
2. 网络虚拟化·
一个完整的虚拟机创建流程应该是:
- 准备系统
- 配置网络
- 准备镜像和磁盘
- 定义并创建虚拟机
- 启动并连接虚拟机
网络是虚拟机与外部环境通信的最重要的途径,因此本文需要详细介绍虚拟机架构的网络模型。
值得指出的是,尽管 CPU, GPU, SSD 等存储、计算模块均有虚拟化相关知识,但是它们的虚拟化一般只是为了优化效能、增加便利性,普通用户在一般场景下无需深入了解。然而,网络的虚拟化是必须要搞清楚弄明白的,因为网络虚拟化的拓扑和技术模型直接决定了虚拟机的可用性及效率。
2.1 虚拟交换机模型·
虚拟交换机是对现实中的物理交换机的模拟,简单说来就是使用软件实现一个交换模型,每台虚拟机分配虚拟网卡,虚拟网卡与虚拟交换机建立通信。
虚拟交换机的核心职责是:
- 连接虚拟机:将同一台物理主机上的多个虚拟机的虚拟网卡(vNIC)连接在一起。
- 转发数据:像物理交换机一样,它工作在数据链路层(L2),根据 MAC 地址表来决定数据帧(Frame)应该被转发到哪个虚拟机的端口。
- 连接外部网络:它还需要一个“上行链路”(Uplink)来连接到物理网络,从而让虚拟机能够与外部世界通信。
通过下列命令可以创建虚拟网桥设备:
1 | sudo nmcli connection modify br0 ipv4.method manual ipv4.addresses 10.4.1.21/16 ipv4.gateway 10.4.1.10 |
2.2 PCI 网卡直通·
上一节描述的纯软件交换方式非常优雅,但是性能一直很成问题,而且依赖软件交换就不可避免地造成 CPU 资源浪费,一旦流量大起来,CPU 的利用率也会飙升。
一个可行的方案是 PCIe 设备直通。如果主机的物理网卡数量足够,我们完全可以把某个网卡通过 PCIe 直通的方式,让某个虚拟机能完全控制该网卡。在主机看来,这个网卡就消失了,而在虚拟机看来,自己多了一个真实的网卡。该技术有以下优势:
- 性能最好,网络通信的资源占用率较低;
- 兼容性最好,由于使用的是真实的网络设备,因此一般没有任何兼容性问题。
同时,该技术也有以下缺陷:
- 基于 IOMMU/VT-d 技术,某些老 CPU 可能不支持该技术;
- 虚拟机持有某个真实网卡之后,某些虚拟化平台会锁定虚拟机的内存,导致主机的内存资源迅速耗光。
- 使用了真实设备之后,虚拟机将与该节点绑定,无法实现自动迁移。
2.3 SR-IOV 技术·
PCIe 直通的方式在网卡有限的情况下会很难扩展,因为一个主机开七八台虚拟机是很常见的,但是主机却一般没有这么多网卡。
网络无非就是交换数据,因此多个虚拟机共享一张硬件网卡是很正常的,而且网卡天然就是基于分组传输的设备,来自多个虚拟机的数据包可以同时在一个网卡的队列里排队,彼此几乎互不干扰。然而,一个网卡的 PCI 地址只有一个,无法被多个虚拟机同时使用,否则会造成内存寻址的错乱。为此工程师发明了 SR-IOV(Single Root I/O Virtualization)技术。
SR-IOV 是一个概念,厂商有自己的具体实现。通俗来说,SR-IOV 将一个物理设备(Physical Function, PF)虚拟成多个虚拟功能(Virtual Function, VF). 每个 VF 都可以被直通给一个虚拟机。宿主机管理 PF,而虚拟机直通 VF。这样既保留了直通的高性能,又解决了物理网卡数量有限的问题,是目前高性能虚拟化网络的主流方案。
虽然 SR-IOV 看上去是个很不错的技术,但是在使用过程中笔者也发现一些问题。理论上所有隶属于同一张网卡 Physical Function 的 VF 应该能在网卡芯片内部进行数据交换,然而某些网卡在不接入物理交换机的情况下,片上的交换功能也被关闭了,造成虚拟机之间无法通信。此外,还有一些老交换机没有片上交换功能。
最后,启用 SR-IOV 通常需要在 BIOS 中开启相关选项,并在宿主机上加载驱动并配置需要创建的 VF 数量。具体说要要完成以下操作:
- 在 UEFI 的网卡配置界面(或系统内的配置界面)开启网卡的 SR-IOV 功能,并配置合适数量的 VF.
- 开启主板的 PCI 直通功能;
- 开启 Intel VT-d 或者 AMD 的 IOMMU 功能;
- 在操作系统内部开启 SR-IOV 支持;
- 在虚拟机内部安装 SR-IOV 专用的网卡驱动。
2.4 VirtIO 网卡·
纯软件实现的虚拟化在架构上比较和谐,但是效率很低。基于 PCI 直通或 SR-IOV 的硬件虚拟化效率极高,但是要求硬件平台具有相关特性,而且还部分地限制了虚拟化的灵活性。因此我们不禁在想,有没有一种更灵活更高效的方式实现虚拟机的外设通信呢?
通过观察软件虚拟化的性能制约因素我们发现,在传统的 I/O 中数据通常需要在内核态和用户态之间产生多次拷贝。比如,guest 用户态到 guest 内核、host 内核到 host 用户态至少有两次拷贝。2007 年,Rusty Russell 在 IBM 从事虚拟化开发时 VirtIO 首次提出了 VirtIO 的概念。这个概念较好地解决了频繁的内存拷贝问题。
既然虚拟机作为用户态进程存在,所以理论上通过主机用户态和内核态共享内存的方式实现内存零拷贝。VirtIO 就是在这种思想启发下的产物。它规定了 guest 和 host 通信时使用的数据结构和通信协议,一般只需要传递控制信号,尽量少甚至无需数据拷贝,由此可以大大提高网卡、SSD 等设备的 I/O 效率。
VirtIO 的核心数据结构是 vring, 它由以下三个具体的数据结构组成。
结构名 | 作用方向 | 由谁写入 | 由谁读取 | 主要用途 |
---|---|---|---|---|
Descriptor Table | buffer 元信息表 | guest driver | host device | 存放数据 buffer 的地址、长度、标志位等信息 |
Available Ring | 告知设备可用项 | guest driver | host device | 表示 guest 已准备好、等待处理的描述符 |
Used Ring | 通知驱动处理完 | host device | guest driver | 表示设备处理完毕的数据 buffer |
不同于全软件虚拟化(简称全虚拟化),VirtIO 需要对虚拟机作一定的修改,否则无法实现高效的零拷贝通信。因此,这种需要对虚拟机内核作修改的外设通信方式也被称为半虚拟化。一般我们认为半虚拟化的性能远高于全软件虚拟化,比较接近 PCI 设备直通等硬件辅助虚拟化。
3. 存储设备虚拟化·
3.1 基于文件的虚拟磁盘·
在描述基于 SR-IOV 技术的硬件辅助虚拟化时我们曾提到,网卡天生就很适合多虚拟机共享。这算是某种基于网络数据包 packet 的「量子化」。而块设备是另一种特殊的设备,它让虚拟化存储变得更加灵活。
一般我们认为块设备是一个连续的块数组。以硬盘为例,它被抽象为一个巨大的扇区数组,单个扇区为 512/4K 字节。如果在这个硬盘上创建现代文件系统,就可以进一步得到文件概念。文件是由元数据索引的一个字节数组,与硬盘的扇区相比,文件的元素粒度更细。抽象地看,文件和硬盘是很类似的,它们都支持按照某种最小单元进行寻址。所以我们可以把文件模拟为一个虚拟机的磁盘。常见的 qcow2、vmdk、vhdx 等格式就是虚拟磁盘文件格式,它们本质上是文件,但是可以被 hypervisor 解释为一个虚拟机硬盘。
如果我们要创建一个 512GB 的虚拟磁盘文件,最简单的方法是直接在文件系统上申请一个容量为 512GB 的空间,然后像对待普通硬盘一样对这个文件进行读改写。但是这么做很不灵活,而且在写入过程中发生突然断电会有可能导致数据损毁。qcow2
是 QEMU 使用的一种特殊文件格式,它支持 Copy-on-Write 特性,即修改后的数据块只会被写入到文件末尾的新位置,然后更新元数据(块映射表)指向这个新位置。此外,qcow2
的 CoW 机制使得虚拟磁盘文件可以按需增长,一个 512GB 的虚拟磁盘,初始时在宿主机上可能只占用几 MB 的空间,该特性一般被称为精简置备。最后,VHDX 和 VMDK 等主流商业格式也通过类似的技术实现了精简置备和快照等高级功能。
3.2 基于卷管理器的块设备·
使用 LVM、ZFS 等工具可以在物理磁盘组的基础之上创建出 LV 和 ZVol 等逻辑块设备。这种块设备也有自己的路径(一般是 /dev
目录下的某个块设备路径),而且可以像文件一样被虚拟机作为硬盘使用。这种存储的优势是性能比较高,LVM 和 ZFS 已经被广泛验证过,其存储的可靠性和性能均非常优秀。缺点是存储迁移会比较麻烦,不如文件类型来得快捷方便。
3.3 控制器直通·
跟 PCI 网卡直通类似,我们也可以把 NVMe 控制器或 SAS/SATA 控制器直通给虚拟机,让虚拟机直接调用物理硬盘。
3.4 raw 设备映射·
在某些特殊情况下,我们希望虚拟机可以直接利用到某个具体的物理硬盘,但是因为条件限制我们无法使用 PCI 直通。
前面我们提到,硬盘是一个大的块设备。如果我们创建一个与物理硬盘容量、规格一致的指针文件并分配给虚拟机,然后 hypervisor 主动拦截虚拟机对该块设备的所有 I/O 并在真实的硬盘上重放一遍,就能实现类似硬盘直通的效果。这种硬盘分配方式一般被称为裸设备映射或 raw 设备映射,在 VMware 语境里被称为 RDM 直通。
- 通过这种方式,我们可以保证硬盘在接入真实机器时仍然能被正确读出来,无需面对物理机不能识别
qcow2
虚拟磁盘文件的问题。 - 此外,指针文件的体积很小,里面只记录了对应的物理磁盘地址。
- 而且经过实测,该方案性能也比较能令人接受。
raw 设备映射的 I/O 模型也可以利用 VirtIO 思想进行优化。传统的 raw 设备映射需要在 host 内核和 guest 内核之间拷贝一次,通过 VirtIO 可以实现零拷贝,从而大大提升存储 I/O 效率。
不过,raw 设备映射也有以下缺点:
- 丧失了虚拟化的部分灵活性:使用 RDM 的虚拟机与特定的物理硬件绑定,这使得虚拟机的迁移(如 VMware 的 vMotion)变得复杂或不可能。
- 快照功能受限:对 RDM 磁盘进行 hypervisor 级别的快照通常是不支持的,因为它直接操作物理设备,绕过了虚拟磁盘文件的抽象层。
4. 使用 virsh 创建并管理虚拟机·
首先启动 libvirtd
守护进程:
1 | sudo systemctl enable --now libvirtd |
可以通过以下命令检查 KVM 是否已经安装并正常工作:
1 | sudo virsh list --all |
4.1 使用 virsh
创建并管理虚拟机·
virsh
是一个强大的命令行工具,用于管理虚拟机。你可以通过它来创建、启动、停止、查看虚拟机等。
本节后续将按照该逻辑链条展开介绍。
4.1.1 定义虚拟机·
define
是最重要的 libvirt 命令,它读取 XML 格式的虚拟机描述文件,生成 libvirt 虚拟机对象。不执行该定义过程,后续所有操作均无法进行!
1 | sudo virsh define --file vm.xml |
后期如果修改了 vm.xml
, 则需要重新定义。
1 | sudo virsh undefine $VM_NAME |
注意,这里的 VM_NAME
是虚拟机 XML 描述文件里的一个标识符,与 XML 文件名没有关系。
4.1.2 启动虚拟机·
1 | sudo virsh start $VM_NAME |
4.1.3 停止虚拟机·
1 | sudo virsh shutdown $VM_NAME |
如果用户急切地要杀死当前虚拟机,可以使用 destroy 命令:
1 | sudo virsh destroy $VM_NAME |
4.1.4 查看虚拟机的状态·
1 | sudo virsh dominfo $VM_NAME |
5. 深入理解 XML 定义文件·
libvirt 项目始于 2005 年左右,当时 XML 已经是业界非常成熟、广泛应用的结构化数据描述格式。而更加人性化的 JSON 和 YAML 要么还没出现,要么还不怎么流行,因此 libvirt 就这样选择了用对用户不太友好的 XML 格式描述虚拟机配置。不过好在现在有了 ChatGPT,我们可以让大模型帮我们生成、修改配置。从这种角度看,大模型的出现是对原教旨主义者重大利好。
XML 格式非常复杂,但是其中的各个模块却很简洁,几乎可以认为是自解释的。
1 | <disk type='block' device='disk'> |
笔者对 libvirt 虚拟机描述文件的结构进行如下总结:
domain
: 虚拟机定义的根节点,type 指定后端虚拟化技术name
虚拟机名称uuid
虚拟机唯一标识(可选)memory
分配内存大小vcpu
CPU 核数os
type
类型arch
架构(如 x86_64, aarch64 等)machine
机器类型(如 pc-i440fx-2.9, q35 等)
boot
dev=‘cdrom’ 第一启动设备boot
dev=‘hd’ 第二启动设备(可选)
features
CPU 特性支持acpi
apic
pae
cpu
CPU 配置clock
时钟设置on_poweroff
电源事件行为on_reboot
重启事件行为on_crash
崩溃事件行为devices
设备定义模块emulator
QEMU 路径disk
磁盘设备type
磁盘类型(file, block, network 等)device
设备类型(disk, cdrom 等)driver
磁盘驱动source
磁盘源文件或设备target
目标设备名称和总线类型
interface
网络接口mac
地址source
网络源model
网络模型类型(如 virtio, e1000 等)
input
输入设备graphics
图形显示配置listen
image
一般情况下用户无需亲自设定每个字段,直接从网上找官方 demo 然后修改一下设备类型和文件地址即可。
总结·
本文详细描述了 Linux 平台的虚拟化技术,从后端虚拟化引擎到前端控制工具都有所介绍。虚拟化是运维练手的绝佳项目,它可以锻炼运维人员对 Linux 网络、存储的掌控能力。笔者在这个过程中深深体会到了 ZFS 块设备的灵活与强大,学到了许多知识。