
1. 从一台闲置物理机说起为什么我要折腾 Firecracker 和 KVM手里有一台闲置的物理服务器配置不算差32 核 64G本来想拿来跑点轻量级的任务。最开始的想法很朴素——装个系统开几个虚拟机把资源切一切就完事了。结果真动手才发现传统虚拟化方案在这类场景下有点杀鸡用牛刀启动一个完整虚拟机要等十几秒甚至更久内存开销也大每个实例动辄几百 MB 起步。我需要的其实是那种秒起秒停、密度极高、隔离性又够用的东西。这就是我接触Firecracker的起点。它本质上是一个用 Rust 写的microVM微虚拟机管理器最早是为了支撑函数计算这类需要极快冷启动、极高部署密度的场景而设计的。而它底层依赖的核心技术正是KVM——Linux 内核自带的虚拟化模块。简单说KVM 负责提供硬件级别的虚拟化能力Firecracker 则在这个能力之上把虚拟机做得尽可能小、尽可能快。这篇文章适合谁看如果你满足下面任意一条那接下来的内容应该对你有用想搞清楚 KVM 和 Firecracker 到底是什么关系准备在自己的服务器上跑 microVM 但不知道从哪下手听说过virtio但一直没弄明白它在虚拟化里扮演什么角色或者你只是单纯好奇为什么现在很多云厂商的底层都在往 microVM 这个方向走。我会从原理讲到实操把踩过的坑和验证过的参数都摊开来说尽量让你看完就能自己动手。需要先说明一点Firecracker 不是要取代 KVM它俩是上下层关系。KVM 是内核里的发动机Firecracker 是装在这台发动机上的轻量化车身。理解了这一层后面很多设计选择就顺理成章了。2. KVM 到底做了什么把内核变成一台虚拟化发动机2.1 KVM 的本质是一个内核模块很多人第一次听到 KVM会以为它是一个独立的软件或者一个完整的虚拟化平台。其实不是。KVM 全称是 Kernel-based Virtual Machine它最核心的形态就是 Linux 内核里的一个模块。加载之后内核就多了一项能力可以创建和运行虚拟机。它的工作方式是这样的——用户态的程序比如 QEMU、Firecracker通过/dev/kvm这个字符设备跟内核打交道发出我要创建一台虚拟机我要给这台虚拟机分配内存我要让这台虚拟机的 CPU 跑起来这类指令。KVM 收到指令后借助 CPU 硬件提供的虚拟化扩展Intel 的 VT-x 或者 AMD 的 AMD-V来真正执行。所以 KVM 本身并不模拟硬件它做的是把物理 CPU 的虚拟化能力暴露给用户态程序。这就解释了一个常见疑问为什么装了 KVM 之后还得配一个 QEMU 才能跑虚拟机因为 KVM 只负责 CPU 和内存的虚拟化它不管磁盘、网卡、键盘鼠标这些外设。外设的模拟工作传统上是由 QEMU 来完成的。QEMU 负责造出一台完整的机器KVM 负责让这台机器的 CPU 跑得接近原生速度。两者配合才是一套完整的方案。2.2 硬件虚拟化扩展是性能的分水岭KVM 的性能优势根源在于硬件虚拟化扩展。在没有这些扩展的年代虚拟化靠的是二进制翻译——把虚拟机里的指令一条条翻译成宿主机能执行的指令开销很大。有了 VT-x / AMD-V 之后CPU 本身就支持进入虚拟机模式和退出虚拟机模式虚拟机里的大部分指令可以直接在物理 CPU 上跑只有遇到敏感操作时才退出到宿主机由 KVM 处理。这个退出VM Exit是理解虚拟化性能的关键。每一次 VM Exit 都意味着上下文切换是有成本的。所以虚拟化优化的核心思路之一就是尽量减少 VM Exit 的次数。后面讲 virtio 的时候你会看到virtio 的设计目标之一正是这个。你可以用一条命令确认自己的机器是否支持硬件虚拟化grep -E -c (vmx|svm) /proc/cpuinfo返回值大于 0 就说明支持vmx 是 Intelsvm 是 AMD。如果返回 0那要么是 CPU 不支持要么是 BIOS 里没开启虚拟化选项。我遇到过好几次明明 CPU 支持却跑不起来的情况最后都是 BIOS 里那个开关没打开。2.3 KVM 的能力边界在哪里KVM 很强但它不是万能的。它有几个明确的边界理解这些边界才能理解为什么需要 Firecracker 这样的东西。第一KVM 本身不提供设备模型。你光有 KVM虚拟机是没有磁盘、没有网卡的这些都得靠用户态程序补上。第二KVM 不负责虚拟机的生命周期管理创建、启动、暂停、销毁这些编排逻辑都在用户态。第三KVM 的接口是相对底层的 ioctl 调用直接拿它写程序门槛不低。正因为这些边界用户态才出现了各种VMMVirtual Machine Monitor虚拟机监视器。QEMU 是功能最全的那个几乎能模拟任何设备Firecracker 则是另一个极端——只保留最必要的功能把体积和启动时间压到极致。它们都站在 KVM 的肩膀上只是取舍不同。3. Firecracker 的取舍哲学为什么它能把启动时间压到毫秒级3.1 从功能齐全到只留必要传统 QEMU 的设计目标是什么都能模拟——从古老的 IDE 硬盘到各种奇葩的 PCI 设备它都能给你造出来。这种通用性带来了巨大的代码量和复杂的设备初始化流程代价就是启动慢、内存占用高。一台完整虚拟机启动动辄十几秒内存开销轻松上到几百 MB。Firecracker 的思路完全反过来。它问了一个问题如果我只服务一类场景——比如跑无状态的函数、跑容器沙箱——那我到底需要模拟哪些设备答案是少得可怜一个块设备用来放 rootfs、一个网络设备、一个串口用来输出日志、一个时钟。其他的统统不要。这个取舍带来的效果是惊人的。Firecracker 的代码量只有几万行相比 QEMU 的百万行级别编译出来的二进制很小启动一台 microVM 的时间可以做到 125 毫秒以内内存开销可以压到 5MB 以下。这意味着同一台物理机上可以塞进成百上千个 microVM而且每个都能在眨眼间启动。3.2 microVM 不是小虚拟机那么简单microVM这个词容易让人误解以为只是把普通虚拟机缩小了。其实它的设计理念有本质区别。普通虚拟机的假设是长期运行、功能完整所以启动慢一点、占内存多一点可以接受。microVM 的假设是生命周期极短、数量极多所以每一毫秒的启动时间、每一 MB 的内存都要抠。这种假设直接影响了架构决策Firecracker 去掉了 BIOS/UEFI 的完整启动流程直接用 Linux 内核的PVH或ELF引导方式去掉了 PCI 总线的复杂枚举改用 MMIO内存映射 I/O来暴露设备甚至连设备的数量都写死在代码里不做动态发现。我实测过用 Firecracker 启动一个最小化的 Linux microVM从发出启动命令到串口打印出登录提示稳定在 150 毫秒左右。同样的镜像用 QEMU 启动要 3 到 5 秒。这个差距在需要频繁创建销毁实例的场景里是数量级的区别。3.3 安全隔离microVM 相比容器的优势有人会问既然要轻量为什么不直接用容器容器启动也是毫秒级密度也高。答案在隔离性。容器共享宿主机的内核一个容器里的内核漏洞可能影响到其他容器甚至宿主机。microVM 则不同每个 microVM 有自己独立的内核通过 KVM 的硬件虚拟化边界与宿主机隔离。这个隔离强度接近传统虚拟机但启动速度和资源开销又接近容器。这就是 microVM 的独特定位——它试图同时拿到容器的轻和虚拟机的隔离。Firecracker 在这方面还做了额外的加固它用一个极简的jailer进程把 microVM 关进命名空间和 cgroup 里限制它能看到的文件系统、能用的系统调用、能占的资源。即使 microVM 被攻破攻击者也被困在一个层层设限的笼子里。这套组合拳是它在多租户场景下被广泛采用的重要原因。4. virtio让 microVM 的 I/O 不再成为瓶颈4.1 全虚拟化设备的性能陷阱如果 Firecracker 老老实实模拟一块真实的网卡会怎样虚拟机会以为自己插了一块真网卡每次发数据都要读写网卡的寄存器。这些读写操作会触发 VM Exit陷入到宿主机由 Firecracker 处理处理完再返回虚拟机。一次网络收发可能要触发几十次 VM Exit性能惨不忍睹。这就是全虚拟化设备的通病——因为要模拟真实硬件的寄存器行为导致大量陷入和模拟开销。解决办法是让虚拟机知道自己在虚拟化环境里用一种双方约定好的、更高效的方式通信。这就是virtio的由来。4.2 virtio 的核心共享内存加通知机制virtio 的本质是一套标准化的半虚拟化设备接口。它定义了几种虚拟设备网卡、块设备、控制台等的通信规范核心机制是virtqueue虚拟队列。virtqueue 是一块宿主机和虚拟机都能访问的共享内存区域里面放着一圈描述符每个描述符指向一块数据缓冲区。虚拟机要发数据时把数据放进缓冲区在 virtqueue 里填一个描述符然后通知宿主机。宿主机处理完后再通知虚拟机。整个过程不需要模拟任何硬件寄存器只需要读写共享内存和发送轻量级通知。这个设计把原来几十次 VM Exit 压缩到一两次性能提升是数量级的。而且因为接口是标准化的虚拟机里用通用的 virtio 驱动就行不需要为每种虚拟化平台写专门的驱动。4.3 Firecracker 对 virtio 的裁剪Firecracker 用了 virtio但不是全盘照搬。它只实现了三类 virtio 设备virtio-net网络、virtio-block块存储、virtio-vsock主机与虚拟机间的套接字通信。而且每个设备只支持一个队列去掉了多队列、去掉了复杂的特性协商。这种裁剪是有意为之。多队列能提升吞吐但增加了复杂度和启动开销Firecracker 的目标场景是大量小实例单队列足够用。特性协商能兼容更多设备但 Firecracker 只跟自己配合不需要那么灵活。这种够用就好的思路贯穿整个项目也是它能做到极简的原因。有一点要提醒因为 Firecracker 的 virtio 实现是裁剪过的某些依赖特定 virtio 特性的驱动或配置可能不工作。我在配virtio-net的时候一开始想开多队列提升性能结果发现根本不支持只能退回单队列。这不是 bug是设计选择。5. 动手搭一套从零跑起第一个 microVM5.1 环境检查与依赖准备动手之前先把地基确认好。第一步是确认 KVM 可用ls -l /dev/kvm如果这个设备文件存在且你有读写权限说明 KVM 就绪。如果不存在检查 CPU 虚拟化是否开启、内核模块是否加载lsmod | grep kvm正常情况下应该能看到kvm和kvm_intel或kvm_amd两个模块。没有的话手动加载sudo modprobe kvm sudo modprobe kvm_intel # Intel 平台第二步是准备 Firecracker 二进制。从官方发布页下载对应架构的版本解压后放到/usr/local/bin或者你习惯的目录。我建议同时下载一份对应版本的jailer后面做隔离会用到。第三步是准备内核和 rootfs。Firecracker 需要一个未压缩的 Linux 内核镜像vmlinux格式不是bzImage和一个 ext4 格式的 rootfs。官方提供了一套演示用的镜像拿来练手最省事。自己编译内核的话记得开启CONFIG_VIRTIO_MMIO和对应的 virtio 驱动否则 microVM 起来后看不到任何设备。5.2 配置文件的字段逐个拆解Firecracker 通过一个 JSON 配置文件来描述 microVM 的形态。下面是一份最小可用的配置我逐字段说明{ boot-source: { kernel_image_path: /path/to/vmlinux, boot_args: consolettyS0 rebootk panic1 pcioff }, drives: [ { drive_id: rootfs, path_on_host: /path/to/rootfs.ext4, is_root_device: true, is_read_only: false } ], machine-config: { vcpu_count: 2, mem_size_mib: 512 }, network-interfaces: [ { iface_id: eth0, host_dev_name: tap0, guest_mac: AA:FC:00:00:00:01 } ] }boot_args里的pcioff很关键它告诉内核不要去枚举 PCI 总线因为 Firecracker 用的是 MMIO 设备没有 PCI。consolettyS0让内核日志输出到串口方便你观察启动过程。panic1表示内核 panic 后 1 秒重启调试时有用。machine-config里的vcpu_count和mem_size_mib决定了 microVM 的规格。这里有个经验microVM 的内存不要设太小512MB 是比较稳妥的起点。我试过设 128MB结果某些 rootfs 加载驱动时就 OOM 了。CPU 数量按实际负载来跑轻量任务 1 到 2 个 vCPU 足够。网络部分需要宿主机先创建一个 tap 设备sudo ip tuntap add dev tap0 mode tap sudo ip addr add 172.16.0.1/24 dev tap0 sudo ip link set tap0 up然后在宿主机开启转发和 NATmicroVM 才能访问外网。这一步容易漏漏了的话 microVM 能起来但上不了网。5.3 启动、连接与验证配置写好之后启动 Firecrackerfirecracker --api-sock /tmp/firecracker.sock --config-file vm_config.json如果一切正常你会看到内核启动日志从串口刷出来最后停在登录提示。这时候可以用screen或socat连上串口交互sudo socat -,raw,echo0 unix-connect:/tmp/firecracker.sock不过更常见的做法是通过 Firecracker 的 API 来管理。它启动后会监听一个 Unix socket你可以用 HTTP 请求动态配置和启动 microVM。这种方式更适合自动化——先启动一个空的 Firecracker 进程然后通过 API 依次下发配置、启动实例、查询状态。很多上层编排工具就是这么跟它交互的。验证 microVM 是否正常工作我一般看三件事串口有没有正常输出、网络能不能通、磁盘能不能读写。三个都过了这套环境就算搭稳了。6. 那些文档里不会写的坑6.1 内核格式不对导致启动直接失败这是新手最容易踩的坑。Firecracker 要的是vmlinuxELF 格式的未压缩内核而很多发行版默认给你的是bzImage压缩过的引导镜像。拿bzImage去启动Firecracker 会直接报错退出而且错误信息不一定直观。解决办法是从内核源码编译时选vmlinux或者用工具把bzImage解压出来。我一般直接编译虽然慢一点但最省心。编译时记得把 virtio 相关的驱动编进内核不是编成模块因为 microVM 启动早期就需要这些驱动来挂载 rootfs。6.2 rootfs 权限和格式的隐形要求rootfs 必须是 ext4 格式而且 Firecracker 进程要有读写这个文件的权限。我遇到过权限没问题但就是挂载失败的情况排查半天发现是 rootfs 镜像本身有问题——用dd创建的空文件没有真正格式化。正确的做法是dd if/dev/zero ofrootfs.ext4 bs1M count512 mkfs.ext4 rootfs.ext4然后用 loop 设备挂载上去把系统文件拷进去。这一步如果偷懒用现成的镜像要注意镜像里的/etc/fstab和 init 配置是否适配 microVM 环境很多通用镜像会在这里出问题。6.3 网络不通的排查顺序microVM 网络不通按这个顺序查基本能定位先看宿主机 tap 设备是否 up、IP 是否配好再看宿主机ip_forward是否开启然后看 NAT 规则iptables 或 nftables是否正确最后进 microVM 看网卡是否拿到 IP、路由是否正确。我踩过最隐蔽的一个坑是guest_mac和宿主机上其他设备的 MAC 冲突导致 ARP 表混乱。给每个 microVM 分配唯一 MAC 是个好习惯别图省事用默认值。6.4 资源限制没设好导致宿主机被拖垮microVM 虽然轻但数量多了照样能把宿主机吃干。一定要用 cgroup 限制每个 microVM 的 CPU 和内存上限用jailer把它关进独立的命名空间。我见过没做限制的环境某个 microVM 内存泄漏把整台宿主机拖到 OOM。Firecracker 本身提供了 API 来设置这些限制配合jailer使用效果最好。7. 这套组合适合什么、不适合什么Firecracker 加 KVM 这套方案优势场景很明确需要大量短生命周期实例、对启动速度和密度要求极高、同时又要比容器更强隔离性的场合。函数计算平台、CI/CD 的构建沙箱、多租户的代码执行环境都是它的主场。但它不适合所有场景。如果你需要模拟复杂的硬件、需要 GPU 直通、需要运行对设备有特殊要求的负载那还是得回到 QEMU 这类功能完整的方案。Firecracker 的极简是优点也是限制选它之前先想清楚自己的需求边界。我个人在实际使用中的体会是microVM 这套东西的价值不在于单台性能有多强而在于它改变了实例的成本结构。当启动一台虚拟机的成本低到可以忽略时很多以前不敢想的设计就变得可行了——比如为每个请求起一个独立隔离环境用完即销毁。这种思路上的解放比单纯的性能数字更有意思。