15.vm_lighter_than_container
Introduction
-
背景
- 容器(Docker/LXC)在工业界广泛应用: Google, AWS, Azure, GCP 都依赖容器;
- 容器优势:
- 快速实例化(毫秒~百毫秒)
- 高密度运行(单机可数千~上万实例)
- 内存占用小, 镜像体积小
- 问题
- 安全性不足:
- 容器暴露 400+ syscalls, 内核攻击面巨大;
- 隔离性不如 VM, 常需要"容器运行在 VM 内"来补强;
- 资源滥用(fork bomb, 文件描述符耗尽)可导致宿主机 DoS;
- 安全性不足:
-
目标
- 结合容器的轻量性与 VM 的强隔离;
- 需求:
- 毫秒级实例化
- 高密度运行
- 小镜像/低内存占用
- 快速挂起/恢复
- 保持 VM 的硬件隔离安全性
-
贡献
- 分析 Xen 性能瓶颈, 重构控制面:
- 移除 XenStore (noxs), 消除中心化瓶颈;
- 新工具链 chaos/libchaos + split toolstack;
- 提出 Tinyx 工具, 生成极小 Linux VM; 支持 unikernel;
- 实现 LightVM
- 分析 Xen 性能瓶颈, 重构控制面:
Tinyx 与 OverlayFS
- Tinyx 目标: 构建最小化 Linux VM rootfs;
- 原理:
- OverlayFS =
lowerdir(Debian minimal) +upperdir(空目录); - 在合成目录中正常安装
apt-get包, 写入 upperdir; - 卸载 (unmount) 保留 upperdir 的安装结果;
- 将 upperdir 覆盖到 BusyBox rootfs 得到最小 rootfs;
- OverlayFS =
- 效果: 自动化, 轻量化, 无需完整 Debian 系统;
Noxs 与 Chaos Toolstack
-
传统 Xen 网络启动流程 (图 7a):
- 工具栈写 XenStore 注册新 VM;
- 后端驱动监听 XenStore, 返回 event channel & grant ref;
- 前端驱动启动后去 XenStore 拉取信息;
- 前后端共享内存, 事件通道传递数据;
- 缺点: 大量 XenStore 交互 (30+ 条记录), 性能瓶颈;
-
LightVM noxs/chaos 流程 (图 7b):
- chaos 工具链发 ioctl 请求后端创建设备;
- 后端返回 event channel & grant ref;
- chaos 通过 hypercall 将信息写入 VM 的 device page;
- 前端驱动启动时, 直接映射 device page, 获取通信信息;
- 前后端通过事件通道通知, 绕过 XenStore;
-
优点: 去中心化, 低延迟, 可扩展;
设备页映射 (Device Page Mapping)
- 概念: hypervisor 为每个 VM 分配一张 只读设备页;
- 过程:
- 前端启动时 hypercall 请求地址, 将设备页映射到虚拟地址空间;
- 前端驱动读取其中的 backend-id, event channel, grant ref;
- 据此完成 ring buffer 映射和 event channel 绑定;
- 意义: 设备初始化无需 XenStore, 直接 guesthypervisor 通信;
Sysctl 伪设备
- 用途: 处理 电源管理类操作(suspend/resume/shutdown/migration);
- 架构:
- sysctlback 在 Dom0
- sysctlfront 在 VM
- 双方通过 共享设备页 + event channel 通信;
- 在迁移中:
- chaos 对 sysctlback 发 ioctl 标记 suspend 触发事件;
- sysctlfront 收到 guest 保存状态并解绑设备页/event channel;
- VM 进入可迁移状态;
TCP Connection 在迁移中的作用
- 流程:
- chaos 在源端与目标宿主机的迁移守护进程建立 TCP 连接;
- 首先传输 guest 配置信息 目标端预建 domain & devices;
- 通过 sysctl 触发 guest suspend;
- 使用 libxc 将 VM 内存+CPU 状态通过 TCP 管道传送至目标端;
- 目标端 resume VM;
- 目的:
- 简单, 通用(任何两台主机都可通信);
- 提供可靠传输, 保证迁移状态正确;
- 测试指标: 迁移总时延 = save + transfer + restore;
Checkpointing 与 Migration
-
Checkpoint (Save/Restore):
- Save = VM 冻结 + 状态写出;
- Restore = 状态加载 + VM 继续执行;
- LightVM: Save ~30ms, Restore ~20ms
- 标准 Xen: Save ~128ms, Restore ~550ms
- 用途: 单机快速暂停/恢复 VM;
-
Migration:
- 跨机器: Save + 网络传输 + Restore;
- LightVM: ~60ms, 不随 VM 数量增加而变慢;
- 标准 Xen: 数百毫秒~秒级;
- 用途: 负载均衡, 故障恢复, 能耗优化, 边缘计算, serverless;
VM Migration 的应用场景
- 负载均衡: 迁移 VM 到空闲主机, 避免过载;
- 故障恢复 & 维护: 宿主机要重启/打补丁时, 迁移 VM 保证服务不中断;
- 节能: 低峰时迁移 VM 到少数主机, 其余关机省电;
- 靠近用户: 边缘计算/5G MEC 中, 把 VM 迁移到更靠近用户的节点, 降低延迟;
- 高可用: 宿主机预测性故障 提前迁移 VM;
- Serverless/JIT 服务: 快速迁移/实例化 VM 以支持函数级弹性;
Requirements
LightVM 设计目标是满足容器的关键特征:
Fast Instantiation(快速实例化)
- 容器: 100–200ms 内即可运行服务;
- 传统 VM: 1–2s 启动, 过慢;
- LightVM: 2.3ms 接近进程级别;
- 应用: serverless, 按需网络功能, 防火墙;
High Instance Density(高密度实例)
- 容器: 单机数千以上;
- 传统 VM: 几十~几百;
- LightVM: 数千到上万 VM, 资源消耗接近容器;
Pause/Unpause(快速挂起/恢复)
- 容器: 毫秒级冻结/解冻, 便于资源复用;
- VM(传统): 挂起/恢复通常要数百毫秒以上;
- LightVM: 30ms 挂起, 25ms 恢复;
Small Image & Memory(小镜像 & 小内存)
- 传统 VM 镜像数百 MB~GB, 启动慢;
- 容器镜像数 MB~几十 MB;
- LightVM: 镜像 480KB–数十 MB, 运行时内存 3.6MB;
- 工具: Tinyx + unikernel;
Lightweight VMs
背景与动机
- 第一步优化目标: 减少 VM 的 镜像大小 和 内存占用;
- 观察: 大多数容器和 VM 只运行 单一应用;
- 如果 VM 仅包含应用所需的最小功能, 可以显著降低内存消耗;
- 本文探索两条路径:
- Unikernels: 应用与极简 OS 链接 专用 VM, 体积仅几 MB;
- Tinyx: 自动化工具构建最小化 Linux 发行版 几十 MB 镜像, 约 30MB RAM 即可运行;
3.1 Unikernels
- 定义: 将目标应用与极简 OS(如 MiniOS)直接链接, 生成专用 VM;
- 特点:
- 没有用户/内核分离, 没有多进程, 极度精简;
- 适合运行单一任务应用;
- 实例:
- ClickOS: 运行 Click modular router 配置;
- Mirage: 将 OCaml 应用打包为 VM;
- Mini-OS: Xen 自带的玩具操作系统, 功能有限, 但可快速构建 unikernel;
- 示例: Daytime unikernel
- 基于 Mini-OS + lwIP, 仅需 50 行代码实现返回时间的 TCP 服务器;
- 镜像大小: 480KB (未压缩);
- 内存占用: 3.6MB(需要轻微修改 Xen toolstack 绕过 4MB 最小限制);
- 更多应用:
- TLS 终止代理;
- Minipython(基于 Micropython 的 unikernel, 用于 AWS Lambda 类似服务);
- 这些应用镜像约 1MB, 运行内存 ~8MB;
- 局限:
- 需要把应用移植到 Mini-OS, 开发成本高;
- 对依赖 Linux syscall API 的现有应用, 移植困难;
3.2 Tinyx
- 定位: 在性能与通用性之间的折中:
- 比 unikernel 通用(无需重写应用);
- 比完整 Linux VM 更轻量;
- 输入:
- 应用(例如 nginx);
- 目标平台(例如 Xen);
- 输出:
- 一个最小化的 Linux 发行版 rootfs;
- 一个优化过的 Linux 内核;
文件系统构建
- 包含:
- 应用本体
- 应用依赖
- BusyBox(提供基础工具)
- 依赖解析方法:
objdump提取应用所需库;- Debian 包管理器 解析依赖;
- 优化:
- 黑名单: 剔除运行时不必要的安装类依赖(如 dpkg);
- 白名单: 用户可强制保留某些包;
- OverlayFS 构建机制:
- 在 Debian minimal debootstrap 上挂载一个空 OverlayFS;
- 在挂载目录中安装依赖(行为与正常 Debian 相同);
- 卸载 OverlayFS upperdir 保留完整, 已配置好的文件;
- 删除缓存, dpkg/apt 文件, 多余目录;
- 将结果叠加在 BusyBox rootfs 下, 形成最终 Tinyx 发行版;
- 在 BusyBox init 中加入 glue 代码启动应用;
内核构建
- 基线配置:
tinyconfig; - 根据目标平台加入选项(如 Xen/KVM);
- 默认禁用:
- 内核模块支持;
- 对虚拟化无用的驱动(如裸机硬件驱动);
- 可选优化: 逐一禁用用户指定的内核选项 重编译 启动测试 不影响运行则保留禁用;
- 效果:
- Tinyx 内核体积比 Debian kernel 小一半;
- 运行内存占用: 1.6MB (Tinyx) vs 8MB (Debian);
Virtualization Today
背景
Xen 是一种在生产环境广泛使用的 type-1 hypervisor(如 Amazon EC2);
- Xen 的 hypervisor 本身仅有约 11.4K LoC, 可信计算基较小, 隔离性强;
- 对比: KVM 基于 Linux 内核, 可信计算基更大;
- 本节以 Xen 为研究对象, 分析现有虚拟化技术在轻量 VM 场景下的性能表现;
4.1 Short Xen Primer
- Xen hypervisor 只负责最基本的资源管理(CPU, 内存等);
- Xen 启动后会自动创建 Dom0:
- Dom0 一般运行 Linux, 包含工具栈 (xl, libxl, libxc) 用于 VM 创建/迁移/关闭;
- Dom0 还运行 XenStore: 一个中心化注册表, 记录 VM 与设备信息, 提供 watch/callback 机制;
- Dom0 还托管物理设备驱动, 以及软件交换机(如 Open vSwitch);
- Xen 采用 split driver 模型:
- 后端驱动 (netback) 在 Dom0;
- 前端驱动 (netfront) 在来宾 VM;
- 两者通过共享内存和 event channels(软件中断) 通信;
4.2 Overhead Investigation
实验环境: Intel Xeon E5-1630 v3 (3.7GHz) + 128GiB DDR4, Xen 与 Linux 版本 4.8;
测试方法
- 顺序启动 1000 个 VM, 测量:
- 创建时间 (create time): toolstack 准备 VM 的耗时;
- 启动时间 (boot time): VM 内核启动到可用的耗时;
- 三类 VM:
- Debian minimal VM(1.1GB);
- Tinyx VM(9.5MB);
- Daytime unikernel(480KB);
- 另外对比: Docker 容器, Linux 进程;
- 镜像存放在 RAM disk, 避免 I/O 干扰;
实验结果
- Debian VM: 创建 ~500ms, 启动 ~1.5s;
- Tinyx VM: 创建 ~360ms, 启动 ~180ms;
- Unikernel: 创建 ~80ms, 启动 ~3ms;
- Docker 容器: 启动 ~200ms;
- Linux 进程 (fork/exec): 启动 ~3.5ms(均值), 90 分位数 ~9ms;
随着 VM 数量增加, 创建时间显著上升:
- 创建第 1000 个 VM: Debian 需 42s, Tinyx 需 10s, Unikernel 需 700ms;
- 进程/容器的创建时间几乎不随数量增加而变化;
- 结论: 随着 VM 变得轻量, VM 实例化 (instantiation) 成为主要瓶颈;
开销分解
通过对 xl 和 libxl 插桩, 发现创建 VM 的开销主要分布在:
- 解析配置文件;
- hypervisor 交互(分配内存, 创建 vCPU);
- XenStore 写入(例如 VM 名称, 设备信息);
- 虚拟设备的创建与配置;
- 内核镜像解析与加载;
- 工具栈内部状态维护;
结果(Figure 5):
- 主要瓶颈: XenStore 交互 + 虚拟设备创建;
- 其他部分(解析 config, hypervisor 交互等)可以忽略不计;
XenStore 成本原因
- 协议开销大: 每次读写都需要多次消息往返和软件中断;
- 写入 VM 名称时需与所有已有 VM 比较 O(n) 成本;
- 设备初始化需要写多个记录, 并保证事务原子性;随着 VM 增多, 事务冲突频繁, 导致重试;
- XenStore 会记录所有访问到 20 个日志文件, 定期 log rotation 导致性能尖峰;
LightVM
目标与总体设计
- 目标: 让 VM 启动时间接近进程启动时间(毫秒级), 而不仅是优化现有 Xen 代码路径;
- 痛点: XenStore 的集中式, 类文件系统 API 太慢, VM 创建/启动期间需要几十次软件中断与特权域切换; 与之对比,
fork只需一次用户态内核态跨越; - 方案: 重构 Xen 控制面, 形成 LightVM 架构(见 Figure 6):
- noxs: 取消 VM 创建/启动时对 XenStore 的依赖, 前后端驱动通过共享内存直接交换信息, 减少中断与域切换;
- chaos/libchaos: 精简的工具栈, 替换
xl/libxl; - split toolstack: 将创建流程拆为 prepare 与 execute 两阶段, 显著减少"命令触发时"的工作量;
- xendevd: 二进制守护进程, 替换慢速的 bash 热插拔脚本, 快速把虚拟接口加入软件交换机或完成块设备设置;
- libxc: 仍用于保存/恢复/迁移的数据面传输;
5.1 Noxs(No XenStore)与 Chaos 工具栈
- 标准 Xen(Figure 7a)中:
- 工具栈向 XenStore 写入"需要网卡"的条目; 后端(netback)监听目录, 分配 event channel 与 grant reference 并回写到 XenStore; 来宾启动后由前端(netfront)到 XenStore 取回参数完成初始化;
- 实际创建流程会交互 30+ 个 XenStore 项目, VM 数量越多越慢;
- 核心洞见: hypervisor 已经掌握大多数必要信息(如 VM id), 可把其扩展为"集中存储"的实现载体, 规避 XenStore;
- LightVM(Figure 7b)做法:
- 用 libchaos/chaos 替换
libxl/xl, 彻底移除libxs/XenStore 依赖; - 为每个 VM 分配一页"设备页 (device page)": 记录该 VM 的设备信息(block/net 的 backend-id, event channel, grant ref);
- 新增 hypercall: Dom0 可写该页(受控), 来宾以只读方式映射;
- 创建流程:
chaos create先通过 noxs 内核模块 ioctl 向后端请求创建设备, 后端返回通信细节(event channel, grant ref等);- 工具栈调用 hypercall 将这些细节写入该 VM 的设备页;
- 来宾启动时通过 hypercall 向 hypervisor 要到设备页地址并 映射到自身地址空间(Linux 与 Mini-OS 均做了前端修改);
- 前端用设备页里的信息完成 grant 映射 与 event channel 绑定, 随后前后端互换状态(如网卡 MAC)并通过事件通道做通知(取代 XenStore watches);
- 无 XenStore 的迁移: 新增 sysctl 伪设备(front/back + 设备页 + 事件通道)承载电源类操作:
chaos与目标机 迁移守护进程建立 TCP 连接, 先发送客体配置让对端预创建 domain 与设备;- 源端通过对 sysctlback 发 ioctl 触发 suspend(在共享页打上"suspend"理由并触发事件通道);
- 来宾 保存内部状态并解绑 noxs 相关资源; 随后用 libxc 传输来宾数据到远端;
- 用 libchaos/chaos 替换
5.2 Split Toolstack(准备/执行分离)
- 观察: 大量创建时执行的代码 对所有 VM 通用或对一类配置通用, 没必要在每次"创建命令"时现算;
- 做法(Figure 8):
- 用 libchaos 替换标准工具栈, 并拆为两阶段:
- Prepare(后台守护进程 chaos daemon 周期执行): 预做与 VM 通用的工作(如让 hypervisor 生成 ID, 准备管理信息, 分配 CPU 资源等), 生成一批 VM shells 放入池中, 数量可配置, 始终保持可用;
- Execute(收到创建命令时):
chaos解析配置, 向守护进程要一个匹配需求的 shell, 在其上完成剩余的 VM 专属步骤(装载内核镜像, 设备收尾初始化), 然后 boot;
- 用 libchaos 替换标准工具栈, 并拆为两阶段:
- 效果: 将大量与"具体 VM 无关"的慢路径前移, 极大缩短创建命令的关键路径;
5.3 替换热插拔脚本: xendevd
- 标准 Xen 中, 虚拟设备创建常需用户态脚本(
xl直接调 bash 或udevd触发同样脚本), 单次脚本启动与执行要数十毫秒, 放大启动时延; - 方案: 实现 xendevd 二进制守护进程, 直接监听后端的 udev 事件并执行 预定义的快速设置, 无需 fork/bash, 显著减小设备接入软件交换机/块设备准备的时间;
组合效果(Figure 9)
- 对 daytime unikernel, 1,000 个实例创建时间对比:
xl(标准 Xen): 最慢;chaos [XS](仅换工具栈, 仍用 XenStore) 更快;chaos [XS+split](再加拆分工具栈) 继续下降;chaos [NoXS](去 XenStore) 明显下降;- LightVM(NoXS + split + xendevd 全开) 最快, 且随已运行 VM 数量增长更平缓;
- 直观结论: 去 XenStore, 拆分工具栈, 替换热插拔脚本 三者叠加, 才能把 VM 创建时延压低到毫秒级并具备良好扩展性;
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
