Virtualization is all you need
现代计算机系统是一个庞大的整体,由多个层次构成。每个层次向上层呈现一个接口,将具体的操作过程抽象成功能1。虚拟化就是接管了系统的某一部分,在虚拟环境中实现下层接口的功能,使得上层能够正常运行,从而实现了从实体到虚拟的转变。
虚拟化技术
虚拟化可适用于计算机系统的各个层次。例如,仿真器(如 QEMU)提供指令集虚拟化,CPU 提供硬件虚拟化,容器提供进程虚拟化,Rosetta 和 Wine 等转译层提供库函数虚拟化,而 JVM 提供运行时虚拟化等等。
本文主要讨论的是传统意义上的 “虚拟机”,也就是通过虚拟化技术来运行多个操作系统。
在已有的操作系统上使用虚拟机软件是许多读者可能熟悉的做法。我建议那些时间宝贵的读者选择这个高效且简洁的方式。目前,各类主流操作系统都具备自己的虚拟化 API,将虚拟化作为操作系统的一个内置功能提供。得益于操作系统的帮助,虚拟机软件能够以接近原生的速度使用 CPU 和 I/O 资源。
如今创建虚拟机前所未有的简单:如果你是 Windows 系统用户,可以直接使用自带的 “亲儿子” Hyper-V 软件。如果你使用的是 Linux 发行版,那么 Gnome-boxes 是你的首选。对于 macOS 用户,如果你希望运行 Windows,我不推荐购买 Mac 机器。
![Hyper-V 虚拟机](/_next/image?url=%2Fimages%2Fproxmox%2Fhyperv.png&w=3840&q=75)
Hyper-V 虚拟机
然而,对于一个需要经常同时使用多个操作系统的人来说,使用 “虚拟机软件” 存在一些缺点:
- 宿主操作系统未进行虚拟化的特殊优化。比如,Windows 在怠机状态下便占用了大量资源,限制了虚拟机的性能。
- 宿主操作系统的稳定性达不到要求。一旦资源占用率过高,Windows 和 macOS 大概率会宕机,影响日常使用。
- 宿主操作系统的安全性也达不到要求。由于宿主操作系统的权限高于所有虚拟机,一旦宿主主机受到攻击,所有的虚拟机都将受到影响。而且,一个完整的宿主操作系统的攻击面通常很广,因此风险较大。
为了解决这些问题,我转而使用了另一种虚拟化的方案:裸机虚拟化。
虚拟机监控器的类型
研究计算机系统的工程师习惯把操作系统内核叫做 Supervisor。负责统筹调度虚拟机的 “管理者” 是 Supervisor 的 Supervisor,因此得名 Hypervisor。直译为 “超管”,意译为 “虚拟机监控器”。
Hypervisor 可以是硬件,可以是固件,也可以是软件。功能是将系统资源分离,并创建和管理虚拟机。传统上 Hypervisor 分为两类:
- 第一类虚拟机监控器:可以直接访问硬件。
- 第二类虚拟机监控器:通过底层操作系统间接访问硬件。
我个人认为这种分类方法应该淘汰了。它建立在一个假设上:操作系统本身不提供虚拟化功能。
目前,主流操作系统都具备能直接访问硬件的虚拟化 API,例如 Hyper-V 和 KVM。这使得一类和二类之间的界限很模糊,分类的意义也不明确。所以后文会避开对类别的讨论。
需求与挑战
我想先解释一下,为什么虚拟化对我而言如此重要。
- 首要前提:我有一个性能强大的主机。
- 我是 Arch Linux 的用户,按自己喜好定制使用方式,比如,我使用 i3 管理窗口,并且创建了一系列独特的快捷键。使用 Linux 进行开发是一种享受。
- 我经常玩大型游戏,在 Steam 和 Xbox 的游戏库里积累了不少游戏。
- 我喜爱摄影,偶尔会使用 Lightroom、PhotoShop 等软件调整照片,用 DaVinci Resolve 等创作软件来剪辑视频,并且用 AU 对翻唱歌曲混音。
- 我偶尔使用 stable-diffsion 等轻量或中等级别的 AI 模型来辅助创作。
- 我在本地局域网部署了众多服务,例如 RSS 抓取器、影音服务器、照片服务器等。
我使用单一操作系统的时候遇到过这样的问题:
- 开发和娱乐的冲突。我喜欢在 Linux 系统下进行开发,但是大多数游戏并不适配 Linux。此外, Linux 也缺乏 HDR 和在线视频硬件加速的支持。因此,我需要使用 Windows 来运行游戏和观看视频。
- 游戏和创作的冲突。Nvidia 和 AMD 在 Windows 下都推出了两种显卡驱动:一种针对游戏玩家优化,另一种则针对创作者优化。如果安装了游戏驱动,一些创作软件的功能会受限,反之亦然。
- 版本和环境的冲突。在 Windows 上运行开源 AI 模型往往是一个及其麻烦的过程。我不会 Powershell。在 Windows 上管理运行环境十分繁琐。不同的 AI 模型之间往往需要不同版本的 CUDA,这使得切换环境变得非常困难。
- 体验和稳定的冲突。我日常会使用 Windows Insider Canary Channel 等最新测试版系统。但对于照片服务、影音服务等重要服务,我需要一个极度稳定的环境来运行它们。
裸机虚拟化
对于以上提到的问题,裸机虚拟化(Bare-Metal Virtualization)提供了一种有效的解决方案。裸机虚拟化是指在硬件上直接运行一个轻量级的专用虚拟化层,然后在这个虚拟化层上运行多个完全隔离的操作系统。这样,我们就可以同时运行多个操作系统,而且它们之间不会互相影响。
![裸机虚拟化](/_next/image?url=%2Fimages%2Fproxmox%2Fbmv.png&w=1920&q=75)
裸机虚拟化
裸机虚拟化的好处在于它允许我在随时按需切换操作系统,而无需中断当前的工作。例如,我可以在使用 Linux 进行开发工作的同时,在另一个虚拟机中运行 Windows 来玩游戏或进行创作。由于各个虚拟机之间完全隔离,因此不会有冲突的问题,即使其中一个虚拟机出现问题,也不会影响其他虚拟机的运行。
此外,裸机虚拟化还使我能够灵活地为每个虚拟机配置不同的硬件资源和环境设置,满足各种不同需求。例如,我可以为运行 AI 模型的虚拟机配置更多的显存和 CPU 资源,同时将其 CUDA 版本设置为模型所需的特定版本。
虽然裸机虚拟化听起来可能有些复杂,但实践上并不困难。现在有许多免费且强大的裸机虚拟化解决方案,例如对个人免费授权的 VMware ESXi 和开源的 Proxmox。这些工具提供了用户友好的 web 界面,我们可以轻松地进行虚拟机的创建、配置和管理。
Proxmox
我选择了开源的虚拟化管理平台 ——Proxmox 作为我的裸机虚拟化方案。这个平台的核心构建在一个特制的 Linux Debian 发行版之上。
这就是为什么我不想讨论 Hypervisor 类型的原因,它算 Type 1 还是 Type 2 呢?
Proxmox 的一个显著特色是,它同时支持 KVM 全虚拟化和 LXC 容器虚拟化两种级别的虚拟化技术。KVM 全虚拟化能够运行任意操作系统,而 LXC 容器虚拟化则允许在隔离环境中运行各种 Linux 程序,这为用户提供了更高的性能和更轻量级的资源占用。
此外,Proxmox 提供了一个易于使用的 Web 界面,通过这个界面,用户能够自由配置每个虚拟机的硬件资源,如 CPU、内存和硬盘等。同时,它也支持各种网络配置,包括 NAT、桥接和隔离等,配置结构化虚拟机网络。
![Proxmox VE 8.0 Web 界面](/_next/image?url=%2Fimages%2Fproxmox%2Fproxmox.png&w=3840&q=75)
Proxmox VE 8.0 Web 界面
Proxmox 还内置了许多专为虚拟化环境定制的高级特性,包括但不限于动态内存管理、CPU 过载保护、硬盘超配、ZFS、Ceph、多节点支持、模板克隆和快照等功能。一些特性还可以自动调整虚拟机的资源分配,以确保系统的稳定运行。
I/O 设备
在 Windows 上运行 VMWare Workstation 不需要考虑太多关于 I/O 设备的事情,这是因为 I/O 设备通过宿主操作系统的驱动工作。对于裸机虚拟化方案来说,分配 I/O 设备就很重要了。
为了提高性能并减少延迟,我们要针对不同的设备考虑不同的 I/O 方案。例如,我用 virtIO 虚拟了网卡和硬盘控制器。我通过 IOMMU 实现了 GPU 直通虚拟机。如果想有更快的网络连接,还可以采用支持 SR-IOV 的有线网卡。
借助 virtIO 实现 I/O 半虚拟化
适用于网卡、存储控制器、串行端口
virtIO 是一种标准的、通用的 I/O 虚拟化框架,是一种半虚拟化技术。虚拟机里的操作系统会知道自己正在运行在虚拟环境中,并通过特殊的驱动程序直接与虚拟硬件交互,大幅减少了在虚拟机和宿主机之间切换的次数,且避免了对实际硬件的仿真,从而提高了性能。
借助 IOMMU 实现 I/O 直通
适用于 GPU 和存储控制器
IOMMU 可能是一个比较陌生的词。在老式计算机系统中,I/O 设备(如网卡、显卡、磁盘控制器等)可以通过 DMA 芯片直接访问主内存,这样就存在安全风险。IOMMU 作为 DMA 的拓展,针对 I/O 设备提供了硬件级别的内存隔离和访问控制。它在系统中引入了 IOMMU 表,用于跟踪设备和内存之间的映射关系。
当我们谈论到 GPU 直通或其他设备的直通时,IOMMU 就显得尤为重要。I/O 硬件设备会通过 DMA 把数据写到内存里。在传统虚拟化环境里,宿主机需要截获设备的 DMA 请求,然后修改 DMA 请求的目标内存地址,将其重定向到虚拟机的内存区域。如果系统支持 IOMMU,可以直接修改设备的映射内存地址,减少了宿主机和虚拟机之间的频繁切换。
虚拟化与隔离的关系
MMU(内存管理单元)的主要目的是提供进程间的内存隔离,但它同时也辅助实现了内存虚拟化。IOMMU(输入 / 输出内存管理单元)则负责提供设备间的内存隔离,同时也有助于实现硬件 I/O 虚拟化。隔离是虚拟化的基础,并且硬件级别的隔离为上层虚拟化提供了极大的便利。
借助 SR-IOV 实现硬件 I/O 虚拟化
适用于网卡和存储控制器
SR-IOV,全称为单根 I/O 虚拟化(Single Root I/O Virtualization),是一种硬件辅助的 I/O 共享技术。通过在单个物理设备中创建多个虚拟设备(被称为虚拟函数),每个虚拟机可以直接访问一个虚拟函数,从而实现 I/O 设备的虚拟化。
每个虚拟函数都拥有独立的设备资源,如内存、中断和 DMA 流,使得虚拟机可以直接与物理设备进行交互,绕过了需要宿主机参与的 I/O 堆栈,大大提高了数据处理速度。
我的裸机虚拟化配置
先说说我这台主机的配置。
硬件 | 规格 |
---|---|
CPU | AMD Ryzen 7 5800X,8 核心 16 线程 |
内存 | 48GB DDR4(两张 16GB,两张 8GB) |
GPU1 | 英伟达 GeForce RTX3080 (10G 显存版) |
GPU2 | AMD Radeon RX6950XT |
硬盘 1 | 三星 980 Pro NVMe SSD 2TB |
硬盘 2 | 西部数据 Blue SN550 NVMe SSD 1TB |
硬盘 3 | 希捷 BarraCuda HDD 4TB |
存储
首先解决存储问题。我希望所有系统都运行在 SSD 上,以确保高速运行。一般来说,裸机虚拟化使用的是 ZFS 或 Ceph。对于我的需求来说,这些解决方案过于复杂。好在 Proxmox 还支持通过 LVM(逻辑卷)来管理存储。我把两块 SSD 集成到同一个 LVM 中,并在此 LVM 上创建了一个 ThinPool(瘦池),用于存储虚拟磁盘。
ThinPool 的存储空间是按需分配的,仅在实际写入数据时才会占用物理空间。这种特性使得我们能够进行虚拟磁盘的超额配置,以便预备未来可能的存储需求增长。举例来说,当我在创建安装 Windows 的虚拟机时,由于无法精确预估未来其可能需求的磁盘空间,我选择直接创建了一个容量为 100TB 的虚拟硬盘。在 Windows 看来,这个 100TB 的虚拟硬盘已被格式化为 NTFS 文件系统,且其认为自己实际拥有了 100TB 的可用空间。然而实际上,我所有的 SSD 存储加起来总量仅为 3TB。如果有一天 Windows 用光了这 3TB 的硬盘空间,我可以再连接一块硬盘,将其加入到 LVM 中,从而扩充 ThinPool 的容量。这个过程中,虚拟机内部的 Windows 系统对外部存储空间的变化无感知。
网络
然后解决网络问题。对于有线网络连接,Proxmox 可以直接创建虚拟网络并自动接入路由器。但是,对于无线网络(WiFi)连接,会遇到问题,因为接入点(AP)会拒绝任何未经验证的帧,使得透明转发的网络桥接难以实现2。
我的解决方案是创建一个新的桥接子网,例如 10.10.10.0/24,并将 Proxmox 的 IP 设置为固定的 10.10.10.10,启用 IPv4 转发并启用 MASQUERADE 以实现 NAT 功能。然后,我创建一个 OpenWRT 虚拟机,并手动设置其 IP 地址为 10.10.10.11,网关设为宿主机的 IP 10.10.10.10,并开启 DHCP 服务,自动分配 10.10.10.100-254 范围内的 IP 地址。
在此方案中,宿主机负责桥接所有的虚拟机网络,并且承担了对外请求的 NAT 转发任务。虚拟机之间的互连直接通过桥接实现,不经过转发。OpenWRT 虚拟机只充当 DHCP 服务器的角色,不用作路由器,只负责在虚拟机启动后自动分配 IP 地址。经过测试,虚拟机的网络速度能轻松达到上下行带宽的最大值。
虚拟机
在解决了存储和网络问题之后,我开始按需创建虚拟机。
我创建了以下这些桌面版 Linux 虚拟机:
- 用于开发的 Manjaro 虚拟机。
- 用于运行 AI 模型的 Ubuntu 虚拟机。
- 用于体验新功能的 Kali 虚拟机。
接下来,我创建了三个 Windows 虚拟机:
- 一个专门用于游戏的 Windows 11,其中安装了 AMD Adrenalin Edition 驱动。
- 另一个专门用于游戏的 Windows 11,其中安装了 GeForce Game Ready 驱动。
- 一个用于运行创作软件的 Windows 11,其中安装了 AMD PRO Edition 驱动。
这两个用于游戏的 Windows 都挂载了同一块虚拟磁盘,这样我就不需要把游戏安装两次。
为了正常的外接显示器使用,这些虚拟机都配置了 GPU 直通。如果两个虚拟机直通了相同的 GPU,那么它们就不能同时运行。
我还创建了几个 Ubuntu Server 虚拟机,用于辅助 NAS 运行一些服务。我会在另一篇博客中详细介绍这些服务(最近我计划开始写关于 PT 的内容,这是另一个大话题)。这些服务器可以通过 cloud-init 自动配置用户名、密码、SSH 登录密钥,其操作简便性就像在 AWS 上创建实例一样。
最后,我在三个 LXC 容器中运行了我的 RSS 抓取器、影音服务器、照片服务器。因为它们原本就是设计为在 Docker 中运行的,因此我使用了 “套娃” 的方式:在 LXC 容器包含 Docker 容器。这并无大碍,因为 Linux 的 cgroups 和 namespaces 本身就是树状结构,支持嵌套,并且这种方式不会带来性能损耗。
日常使用
日常使用的过程中,我会同时打开用于开发的 Manjaro 和用于游戏的 Windows 11。在 VM 之间切换的方法很简单。
切换屏幕 这两个系统直通了不同的 GPU,因此我只需要从两个 GPU 分别接一条视频数据线到显示器即可。通过显示器对信号源的切换完成虚拟机屏幕的切换。
切换键鼠 Proxmox 可以直通 USB 设备。直通是指将整个 USB 设备直接连接到虚拟机,让虚拟机独占使用该设备。这样可以获得最佳性能和完全的设备访问权限,但缺点是这个 USB 设备将无法在其他虚拟机或主机之间共享。我使用 KVM(键盘、视频和鼠标)切换器来实现在两个虚拟机之间共享键盘和鼠标。
![KVM 切换器](/_next/image?url=%2Fimages%2Fproxmox%2Fkvm_switch.jpg&w=3840&q=75)
KVM 切换器
KVM 切换器通常用于在多台计算机之间切换键盘、视频和鼠标,它有多个 USB 接口用于连接键盘和鼠标,并且有两个 USB 输出端口用于连接两台计算机。将 KVM 切换器的两个输出端口都连接到主机,就可以通过 USB 端口直通的方式将它们绑定到不同的虚拟机上。
把人恶心坏了的英伟达和 vGPU
文章的最后我要提一嘴 vGPU 的事情。Nvidia 在 Windows 通过 Hyper-V 支持了 GPU 虚拟化,比如 WSL2 里运行的 Ubuntu 是可以通过 vGPU 进行图形计算的,只是性能损失比较严重。
在 Linux 里,Nvidia 也支持容器级的 GPU 虚拟化。只需要安装 Nvidia-docker,就可以在不同容器里使用 GPU。如果你是 AI 开发者,可以通过 Nvidia-docker 来管理不同的 CUDA 版本,Torch 版本等等,非常方便。
不过,只有非常昂贵的企业级 GPU 支持真正的 vGPU。我说的是基于 SR-IOV 的虚拟化 I/O 方案。其实,GitHub 项目 vgpu_unlock 已经证实英伟达的低端消费级显卡(例如 RTX2060)都自带 vGPU 硬件支持,只是英伟达驱动主动屏蔽了这一功能。其目的就是为了销售自己的 “高端” 商用显卡。
vGPU 的商业价值
我曾经在某大厂开发算力平台,提供分布式训练服务等等。如果平台能售卖弹性任务,就能大大降低空置率,完成资源的最大化利用。例如一个 4 卡训练任务发现目前有 8 张空闲卡,就可以自动扩充成 8 卡任务。据说单这个功能就省了几个亿。售卖粒度更细的弹性任务能从闲置资源里榨出更多效益,尤其是推理任务。
作为消费者,这几年看着英伟达一路作死,从 vGPU 上锁,到 “泄漏” 消费级显卡的哈希计算锁绕过补丁,到 RTX4060 的反向升级。每每看到这种为了商业利益恶心普通消费者的做法,我都想拿出 Linus 的经典表情包。
![Linus: NVIDIA, FUCK YOU](/_next/image?url=%2Fimages%2Fproxmox%2Ffuckyou_nvidia.jpeg&w=1080&q=75)
Linus: NVIDIA, FUCK YOU
不过,英伟达还是顺利赶上了矿潮和 AIGC 两波时代大事件,它对消费者的不屑一顾换来了今天 AI 时代的巨头垄断地位。证明硬件厂商完全可以靠软件和商业模式赚大钱。
有的厂商曾经是搅局者,是创业者,是领航员。如今看看,不过是屠龙者终成恶龙的故事一再上演罢了。
Footnotes
© LICENSED UNDER CC BY-NC-SA 4.0