Firecracker 如何配置 vhost-user 块设备并对接外部存储后端
【免费下载链接】firecrackerSecure and fast microVMs for serverless computing.项目地址: https://gitcode.com/GitHub_Trending/fi/firecracker
如果你的块设备 IO 需要在 Virtio 队列处理同层实现自定义逻辑——例如通过网络取回块数据、或实现精细的 readahead 算法——可以用 Firecracker 的 vhost-user 块设备:它把 Virtio 队列的处理委托给宿主机上的另一个用户态进程(backend),Firecracker 只作为 vhost-user 前端参与控制面。本文给出从启动 backend、通过 API 添加设备,到在 guest 内验证设备、并在启动后更新设备配置的完整操作路径。
注意:按 发布策略,vhost-user 块设备目前处于developer preview,文档明确建议不要在生产环境使用,且该功能可能随时变化。
前后端分工与交互时机
Vhost-user 是一个用户态协议。在这个架构中,Firecracker(前端)负责:
- 通过 Unix domain socket(UDS)连接 backend;
- 与 backend 和 guest 做 feature negotiation;
- 处理 guest 发起的设备配置请求;
- 把 guest 内存和 Virtio 队列信息(包括 guest 内存区域的 fd 和队列通知的 fd)共享给 backend。
backend 负责实际处理 guest 的 IO 请求。UDS socket 只用于控制面,不参与数据面。
有三个时间点前后端会发生交互:
- 设备初始化:创建 vhost-user 设备时,Firecracker 连接对应的 UDS socket,与 backend 协商 Virtio/Vhost feature 并获取设备配置;
- 设备激活:guest 驱动完成设备设置后,Firecracker 把内存表和 Virtio 队列信息共享给 backend;
- 配置更新:对 vhost-user 设备发送
PATCH请求时,Firecracker 重新向 backend 请求设备配置,使新配置对 guest 可见。
拓扑上,每个 vhost-user 设备连接自己独立的 UDS socket,无法多设备共享一个 socket(协议层无法区分不同设备的消息)。一个 backend 进程可以服务多个设备,也可以每个设备对应独立 backend。
Firecracker 只实现了 vhost-user 前端。开源 backend 参考实现包括 Qemu、Cloud Hypervisor、crosvm 和 SPDK 中的 vhost-user-blk,也可以自行实现;文档示例以 Qemu 的vhost-user-blk为准。
准备条件
- 一台满足 Firecracker 要求的 KVM 宿主机(x86_64 或 aarch64 Linux,参见 kernel 支持策略);
- Firecracker 二进制,并以后台方式留出 API socket。getting-started 中的简化启动方式(不使用 jailer):
API_SOCKET="/tmp/firecracker.socket" sudo rm -f $API_SOCKET sudo ./firecracker --api-sock "${API_SOCKET}"- 一个 vhost-user backend 二进制和它服务的块文件(backing file)。测试框架中 Qemu backend 的启动命令形如
vhost-user-blk --socket-path <socket> --blk-file <file>,见 utils_drive.py。
一个前提细节:UDS socket 由 backend 侧创建并监听,Firecracker 是连接方。因此要先启动 backend,再向 Firecracker 发PUT /drives,socket字段填 backend 实际监听的 socket 路径。
第一步:启动 vhost-user backend
文档给出的示例配置(Qemu backend):
vhost-user-blk --socket-path=${backend_socket} --blk-file=${drive_path}${backend_socket}:UDS socket 路径,替换成 backend 实际创建监听的路径;${drive_path}:backend 服务的块文件路径,替换为你准备的后端镜像/磁盘文件。
如果需要只读设备,只读属性配置在 backend 一侧而不是 Firecracker 一侧:测试框架对 Qemu backend 的只读场景就是在命令行追加--read-only(见 utils_drive.py)。只要 backend 通告VIRTIO_BLK_F_ROfeature,Firecracker 就会接受,设备表现为只读。
第二步:向 Firecracker 添加 vhost-user 块设备
在 microVM 的其他资源(boot-source、rootfs、网络等按 getting-started 配置完成)之后、启动实例之前,发送PUT /drives请求。文档示例:
curl --unix-socket ${fc_socket} -i \ -X PUT "http://localhost/drives/scratch" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ -d "{ \"drive_id\": \"scratch\", \"socket\": \"${backend_socket}\", \"is_root_device\": false }"其中${fc_socket}是第一步中--api-sock传入的 API socket 路径(示例为/tmp/firecracker.socket),${backend_socket}与第一步中 backend 监听的 socket 路径一致。socket字段的定义("Path to the socket of vhost-user-block backend")可在 firecracker.yaml 中查到。
与 virtio 块设备不同,vhost-user 驱动没有path_on_host、is_read_only、io_engine这些字段,请求体只需要drive_id、socket、is_root_device。
配置完成后按标准流程发送InstanceStart启动 microVM(getting-started 中的curl -X PUT "http://localhost/actions"请求)。
启动后验证设备
集成测试 test_drive_vhost_user.py 的做法是在 guest 内通过blockdev检查设备:
# 设备列表:确认 /dev/vdX 存在及 ro/rw 属性 blockdev --report # 指定设备的大小(字节):与 backend 侧块文件大小比对 blockdev --getsize64 /dev/vdb测试中作为 root device 的 vhost-user 驱动对应/dev/vda;blockdev --report首列显示ro或rw,用于确认只读/读写属性是否与 backend 配置一致。blockdev --getsize64的输出应与 backend 所服务块文件的大小相等(测试用stat获取文件字节数后与 guest 内查询值断言一致)。
启动后更新设备配置(PATCH)
vhost-user 场景下,Firecracker 不直接接触 backing file,backend 对文件的改动不会自动被 Firecracker 感知。vhost-user 协议中 backend 主动通知前端的VHOST_USER_BACKEND_CONFIG_CHANGE_MSG机制Firecracker 不支持,取而代之的是PATCH /drivesAPI:它只需要drive_id一个必填属性(其他可选属性对 vhost-user 无意义):
curl --unix-socket ${fc_socket} -i \ -X PATCH "http://localhost/drives/scratch" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ -d "{ \"drive_id\": \"scratch\" }"收到该请求后,Firecracker 会从 backend 重新获取设备配置并向 guest 发送配置变更通知。集成测试test_config_change验证过的流程是:backend 侧完成 resize → 对同一drive_id发PATCH→ guest 内blockdev --getsize64报告新大小。
已知限制与工程注意事项
快照不支持。配置了 vhost-user 设备的 microVM 不能做快照,尝试创建快照会失败;集成测试断言的报错为Devices without snapshot support are present: vhost-user-block(id: rootfs)。
重复 PUT 会重建连接。对已存在的drive_id再次发送PUT /drives,Firecracker 会关闭到 backend 的现有连接并建立新连接,可能需要重启 backend。
性能代价。为了让 backend 能处理 virtio 请求,guest 内存必须以共享内存映射(memfd_create+MAP_SHARED)提供。文档的测试观察到共享映射的 page fault 比私有映射最长慢约 24%,建议在自己的 workload 上先做性能剖析再决定是否使用 vhost-user。
没有宿主机 page cache。与直接读写宿主文件的 virtio 块设备不同,vhost-user 路径下 backing file 不经过 Firecracker,宿主机 page cache 的缓存和 readahead 收益不在此路径中,需要的话要在 backend 内实现自有缓存。
限流职责转移。virtio 块设备内置的 rate limiting 对 vhost-user 不生效,Firecracker 不参与请求处理,限流是 backend 的责任;也可以用 cgroups 限制 guest 的宿主 CPU 消耗来间接限制 IO。
安全边界。文档列出的要点:
- guest 内存的 memfd fd 会出现在 Firecracker 进程的 procfs 中(形如
lrwx------ ... 32 -> /memfd:guest_mem (deleted)),能访问该 fd 的宿主进程可观察到 guest 运行行为;在设备全部激活前 memfd 不会被关闭,因此需要把 Firecracker 的 procfs 访问限制在可信进程内; - 推荐用 jailer 运行 Firecracker,但 jailer 目前只能运行 Firecracker 二进制本身,backend 需要另外放进 jailer 或施加其他隔离措施,因为 guest 有可能触发 backend 代码中的缺陷;
- backend 应实现等待 Firecracker 连接的超时,避免 Firecracker 提前退出导致资源耗尽;backend 崩溃时可向 Firecracker 发送
SIGBUS之类的信号使其一并退出,避免遗留孤儿进程; - 使用 jailer 时注意 file size 资源限制不能小于最大的 guest 内存区域(memfd 被视为文件)。
相关文档
- vhost-user 块设备:架构、backend 列表、安全考虑与示例配置
- 启动后更新块设备:PATCH /drives 对 virtio 与 vhost-user 驱动的不同语义
- 块设备 IO 引擎:
Sync/Async引擎与 vhost-user 的对比位置 - Getting Started:Firecracker 启动与 API 使用基础
- test_drive_vhost_user.py:boot、设备排序、resize、快照失败的验证用例
【免费下载链接】firecrackerSecure and fast microVMs for serverless computing.项目地址: https://gitcode.com/GitHub_Trending/fi/firecracker
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考