文章/mirage-unikernel-study-notes
7 分钟阅读
Mirage the Unikernels 学习
梳理 Mirage unikernel 的构建方式、优势和运行架构。
概述
Unikernels:一种通过用高级源代码编写的应用程序来部署云服务的新方法。Unikernels 在编译时将应用特化为独立内核,并在部署到云平台时密封,防止修改。
优点:
- 显著降低了镜像大小
- 提高性能
- 提高安全性 原型系统 Mirage 通过虚拟机扩展实现不可变,将静态类型安全与单地址空间布局相结合, Mirage 将 OCaml 代码编译成在云上运行的单内核系统,在不产生显著性能损失的情况下将代码大小减小了一个数量级。 之前的 Library OS 在实际部署中由于硬件兼容性问题而不切实际,现代的虚拟化平台帮助克服了这个问题。
引言
- 操作系统虚拟化提供一个平台,客户租用资源托管虚拟机
- 每个虚拟机都表现为一台独立的计算机,启动标准的操作系统内核并运行未修改的应用程序进程:每个虚拟机通常专门用于特定的角色,例如数据库、Web 服务器。通过从模板克隆虚拟机,进行横向扩展。
- Unikernels 在编译时去除功能,将最终的虚拟机镜像视为单一用途应用(而不是通用的操作系统)。整个软件栈包括系统库、语言运行时和应用程序被编译成一个可直接在标准虚拟机管理程序上运行的单一可启动虚拟机镜像
- 牺牲源代码级别的向后兼容性可以提高性能,同时显著提高面向外部的云服务的安全性;通过标准网络协议(如 TCP/IP)保持与外部系统的兼容性,而不是尝试支持 POSIX 或其他常规标准
- Mirage DNS 服务器的性能优于 BIND 9(高出 45%)和高性能 NSD 服务器;同时使用非常小的虚拟机镜像:我们的单内核设备镜像仅为 200KB,而 BIND 超过 400MB。
架构
- 虚拟化是云计算的支持技术,通过 Xen 等虚拟机管理程序广泛部署
- 数据中心设备通常由在 Xen 上引导的 Linux 或 Windows 虚拟机组成:
- 一个承载主要应用程序(例如 MySQL、Apache)的客户机操作系统内核
- 其他服务(例如 cron、NTP)并行运行
- 通常连接带有配置文件和数据的外部存储设备
- 虚拟机管理程序提供了一种可以动态扩展的虚拟硬件抽象:
- 既可以通过添加内存和虚拟 CPU 进行纵向扩展
- 也可以通过生成更多虚拟机进行横向扩展
配置和部署
- Unikernels 允许应用程序开发人员使用简单的库调用来配置动态参数;或者使用构建系统工具来配置静态参数。
紧凑和优化
- 通过正常的链接机制生成高度紧凑的二进制文件
- 在特定编译中未使用的功能不会被包含
- 较小的二进制文件大小便于部署
单镜像应用
- 兼容性需求,导致最简单的虚拟机也包含大量代码,增加了攻击面
- Unikernels 在编译时,就消除不必要的功能;模块依赖可以很容易的进行静态验证,确保只包含需要的服务
- 虚拟机不能进行克隆,因为其中包含了一些静态配置。可以通过动态配置指令,如 DHCP 而不是静态 IP 来避免这一点
类型安全
- 要抵御远程攻击,促使人们使用类型安全的语言
- 一个重要的决策是是否在同一个 unikernel 中支持多种语言
- 支持多种语言的一个可以提高与现有代码的向后兼容性,但代价是:
- 增加单镜像系统的复杂性
- 需要处理多种语言运行时之间的互操作性
- 支持多种语言的一个可以提高与现有代码的向后兼容性,但代价是:
- 另一种选择是只用一种语言编写系统组件,并且尽可能的优化该工具链
- 非 OCaml 代码可以封装在单独的虚拟机中通过消息传递进行通信
- 设备内的访问控制不再需要用户空间进程,而是依靠语言的类型安全来实施限制
- 虚拟地址空间可以简化为单地址空间模型
密封性、虚拟机降权
- Unikernels 是单镜像、单地址空间,对虚拟机的接口依赖较少
- 编译时未出现在单内核操作系统中的任何代码都永远不会被运行,完全防止了代码注入攻击。实现:
- 建立一组页表,其中没有一页既是可写的又是可执行的
- 然后进行 seal hypercall,防止页表被修改
- 直到终止,访问策略会一直持续
- 相比之下,在传统操作系统中实现 Write XOR Execute 策略,需要对内核、库、运行时等进行大量修改。
- 这种方法确实意味着正在运行的虚拟机无法扩展其堆,而是必须在启动时预先分配其所需的所有内存。对于云基础架构来说,这是合理的,因为内存容量已经在运行前就已经确定
运行时的地址空间随机化
- Seal 可以防止攻击者引入攻击代码,但它无法阻止攻击者执行已经存在的代码
- 这可以通过运行时地址空间随机化来防范
选择 OCaml 的原因
- 具有灵活的编程模型,支持函数式、命令式和面向对象编程,其简洁性减少了代码行数(LoC),而代码行数通常被认为与攻击面相关
- 简单而高性能的运行时
- 静态类型的实现消除了编译时的类型信息,同时保留了类型安全的所有好处
- 需要大量的工程努力来重建系统组件,特别是存储和网络栈
- 每个 Mirage 单内核在 Xen 上运行,使用一个虚拟 CPU;通过 Unikernels 之间的通信实现多核
- 确实探索了在 C 中应用 unikernel 技术,将应用程序代码与 Xen MiniOS 链接起来,但是这样构建的 NSD DNS 比 Mirage DNS 性能差得多,并且仍然需要很大工程努力,也没有得到类型安全的好处
PVBoot 库
- 初始化一个具有虚拟 CPU 和 Xen event channel 的虚拟机,跳转到入口函数
- 不支持多进程,不支持抢占式多任务
- 为语言运行时分配了一个单一的 64 位地址空间
- 提供两种页分配器:slab 和 extent 分配器
- Slab 用于支持运行时中的 C 代码
- Extent 分配器保留一个连续的虚拟内存区域,以 2MB 为单个块,进行操作,允许映射 x86_64 hugepages
- 内存区域被静态分配,例如 GC 堆、I/O 数据等
domainpoll函数,在一组 event channels 间阻塞。支持超时- PVBoot 为异步、事件驱动的虚拟机提供了最低限度的支持,该虚拟机在 I/O 可用或超时之前一直处于休眠状态
语言运行时
- Mirage 在经过专门修改的语言运行时上执行 OCaml 代码,主要在两个关键领域进行了修改:内存管理和并发
- Mirage 单内核的内存布局,分为三个区域:
- .text 和 .data
- 外部 I/O 页面
- OCaml 堆
- 应用程序的主线程在启动后立即启动,当它返回时,虚拟机关闭
- Caml 垃圾回收器将堆分为两个区域:一个用于短生命周期值的快速小堆,以及一个在每次小堆收集时将长生命周期值提升到其中的大型主堆
- 小堆有一个单一的 2MB 范围,以 4KB 块增长
- 主堆拥有虚拟内存的剩余部分,使用 extent 分配器以 2MB hugepage 增长
- Mirage 单内核在运行时避免 ASR,而倾向于更专门的安全模型(2.3),保证连续的虚拟地址空间,简化了运行时内存管理。
讨论
邮箱用于身份识别和回复通知,并由 Waline 存储。