Hyperlight WASM:快速、安全且无需操作系统

[图片]

去年秋天,Azure Core Upstream 团队推出了 Hyperlight:一个开源的 Rust 库,你可以使用它来执行基于虚拟机监控器的保护的小型嵌入式函数。然后,我们展示了如何极速运行 Rust 函数,以及如何使用 C 来运行 JavaScript。2025 年 2 月,Cloud Native Computing Foundation (CNCF) 投票将 Hyperlight 纳入其 Sandbox 计划。

我们现在发布了 Hyperlight WASM:一个 Hyperlight 虚拟机 (VM) “微型访客”,可以运行用多种编程语言编写的 wasm 组件工作负载。 如果你想直接深入了解,可以访问 GitHub 上的 hyperlight-wasm 代码库。在本文的剩余部分,我们将介绍 Hyperlight WASM 的基本工作原理,然后逐步演练如何构建一个 Rust 示例。

加入 Hyperlight 社区

性能与兼容性

传统的虚拟机需要做大量工作才能运行程序。它们不仅需要加载整个操作系统,还需要启动操作系统所依赖的虚拟设备。Hyperlight 速度很快,因为它没有做这些工作;它暴露给 VM 访客的只是一个线性内存切片和一个 CPU。没有虚拟设备,没有操作系统。

但这种速度是以 兼容性 为代价的。你的当前生产应用程序很可能需要运行在 x86-64 架构(硬件)上的 Linux 操作系统,而不是一个裸线性内存切片。 并且兼容性不仅限于操作系统; 它可以从三个不同的层面进行讨论:

在操作系统层面上实现兼容性,可以让你抽象出客户可能想要使用的语言/运行时,因为常见的操作系统几乎被所有语言、库和应用程序程序普遍支持——但这需要提供一个特定的、相对较重的执行环境。另一方面,非常轻量级的服务可以并且确实通过更改单个语言的标准库来支持其平台,从而抽象出其非完整操作系统执行环境的细节,这大大提高了性能——但代价是需要使用特定的语言来实现运行在该环境中的程序。

WASI 和 WebAssembly 组件模型在这两种相互对立的抽象之间提供了一个中间地带:通过实现这些接口,可以允许任何轻量级执行环境运行用(几乎)任何语言编写的程序。 Hyperlight WASM 正是利用了这一点,让你可以在几乎任何执行环境中实现一小部分高级、高性能的抽象,并提供一个非常快速、受硬件保护,但仍然广泛兼容的执行环境。

介绍 Hyperlight WASM 访客

兼容性如何?好吧,通过使用 WebAssembly 运行时——wasmtime——构建 Hyperlight,任何编程语言都可以在受保护的 Hyperlight 微型 VM 中执行,而无需任何关于 Hyperlight 的预先知识。对于程序作者而言,他们只是为 wasm32-wasip2 目标进行编译。这意味着他们可以使用 wasmtimeJco 等运行时在本地运行他们的程序,或者使用 Nginx UnitSpinWasmCloud 或现在的 Hyperlight WASM 在服务器上运行它们。如果做得好,开发者在开发时无需考虑他们的代码将在哪个运行时上运行。这种程度的开发者灵活性只有通过标准才能实现。

在 Hyperlight WASM 访客中执行工作负载不仅适用于 C、Go 和 Rust 等编译语言,而且适用于 Python、JavaScript 和 C# 等解释型语言。这里的诀窍与容器非常相似,是将语言运行时也作为镜像的一部分包含进来。 例如,对于 JavaScript,StarlingMonkey JS Runtime 旨在在 WebAssembly 中原生运行。

编程语言、运行时、应用程序平台和云提供商都开始为 WebAssembly 提供丰富的开箱即用体验。如果我们做对了,你将永远不需要考虑你的应用程序是否正在 Azure 的 Hyperlight 微型 VM 中运行。 你可能永远不会知道你的工作负载正在 Hyperlight 微型 VM 中执行。这是一件好事。

更多安全层,更少的总层数

通过将 Hyperlight 与 WebAssembly 相结合,我们所执行的重大魔法是:通过减少总体工作量,我们实现了比传统 VM 更好的安全性和性能。 当传统的虚拟机管理器 (VMM) 创建一个新的虚拟机时,他们首先需要创建新的虚拟设备,然后加载内核,然后加载操作系统,只有这样才能启动应用程序。这个过程最少需要大约 125 毫秒。

[图片:图表显示,对于应用程序的每个实例,传统的虚拟机监控器都有四层繁重的工作:VM 访客、访客内核、操作系统发行版和应用程序二进制文件。]

使用 Hyperlight 和 Hyperlight WASM,我们最终做的事情远少于传统的 VM。 当 Hyperlight VMM 创建一个新的 VM 时,它所需要做的就是创建一个新的内存切片并加载 VM 访客,VM 访客转而加载 wasm 工作负载。 这在今天大约需要 1-2 毫秒,并且正在努力将这个数字在未来降到 1 毫秒以下。

[图片:图表显示,与传统的微服务方法相比,对于应用程序的每个实例,hyperlight 减少了三层繁重的工作。]

这种架构不仅有利于启动时间,快速启动时间还会影响你安排应用程序的方式。 如果启动一个工作负载大约需要一毫秒,你就可以负担得起没有空闲实例。 如果你选择准备一个预热池,内存占用将大大减少。 它还允许你在更便宜的硬件上完成更多工作,这些硬件更靠近用户。 这就是我们即将推出的 Azure Front DoorEdge Actions 服务的逻辑,该服务由 Hyperlight 提供支持,并且很快将进入私有预览版。

将 Hyperlight 与 wasm 结合使用不仅有利于性能; 它也有利于安全性。 在底层,Hyperlight WASM 访客使用一流的 wasmtime 运行时,该运行时被编译成一个 Rust no_std 模块作为 Hyperlight 访客。 Wasmtime 通过软件定义的运行时沙箱为 wasm 工作负载提供强大的隔离边界。 虽然潜在的攻击者很难突破 wasm 的沙箱,但在 Hyperlight WASM 访客上,即使他们设法做到了,他们也将面临逃脱 VM 的挑战。 一层沙箱是好的。 但有两层会更好。

UDP 回显示例

好了,让我们看看如何从 Rust 中使用 Hyperlight WASM。 在我们的示例中,我们将运行一个简单的用户数据报协议 (UDP) 回显服务器,该服务器使用 wasi:sockets 接口的一部分。 对我们来说幸运的是,我们不必自己编写该程序:我们可以下载一个预编译的 wasm 程序(如果你想自己编译它,源代码可在此处获得)并运行它。 让我们首先这样做。 假设你有一个正常运行的 Rust 安装,首先安装 wkg 工具以从 GitHub Artifacts 下载 wasm 二进制文件,然后使用它来获取 wasm 二进制文件的副本:

cargo install wkg
wkg oci pull ghcr.io/hyperlight-dev/wasm-udp-echo-sample/udp-echo-server:latest -o echo.wasm

现在我们有了 Wasm 二进制文件,我们可以开始连接 Hyperlight 主机了。 目前这有点复杂,因为我们尚未发布 cargo 包。 因此,让我们首先 git 克隆我们的示例代码库:

git clone https://github.com/hyperlight-dev/hyperlight-wasm-sockets-example
mv echo.wasm hyperlight-wasm-sockets-example
cd hyperlight-wasm-sockets-example

如果你查看代码库中的代码,你会注意到它的结构类似于常规的 Rust 项目。 没错,它的结构确实是常规的 Rust 项目。 它包括几个文件,其中大多数文件用于实现回显示例使用的样板接口。 在项目的根目录中,有一个名为 hyperlight.wit 的文件。 该文件是用 WebAssembly Interface Types (WIT) 语言编写的文本表示形式,它指定了主机正在向 wasm 模块提供和期望从 wasm 模块获取的精确接口。 我们需要将此文件处理为二进制 wasm 表示形式,该表示形式将在构建 hyperlight-wasm 时使用:

wasm-tools component wit hyperlight.wit -w -o hyperlight-world.wasm
export HYPERLIGHT_WASM_WORLD=$(readlink -f hyperlight-world.wasm)

现在,让我们看看使用 hyperlight-wasm 运行组件的代码。为了获得可用于轻松实现 wasm 模块的导入/调用的绑定的代码,我们可以使用 **hyperlight_component_macro::host_bindgen**

// bindings.rs
extern crate alloc;
hyperlight_component_macro::host_bindgen!();

这将生成一组特性,这些特性表示沙箱内部件导入和导出的 WebAssembly 组件模型实例。 我们定义一个状态结构,用于跟踪我们需要实现导入的所有内容(但在此示例应用程序中未使用):

// state.rs
pub struct MyState {}
impl MyState {
  pub fn new() -> Self { MyState {} }
}

然后,我们需要指定此状态表示形式可用于实现组件使用的接口:

// main.rs
use bindings::*;
impl root::component::RootImports for MyState {
  type Udp = MyState;
  fn udp(&mut self) -> &mut Self { self }
  // ... one for each imported instance
}

wasi:sockets udp 接口的实现非常简单,因为它实际上没有任何与资源无关的函数:

// udp.rs
impl wasi::sockets::Udp<
  ErrorCode,
  IpSocketAddress,
  (),
  MyPollable>
for MyState {}

但是,由于它导出一个 **UdpSocket** 资源,我们需要实现该资源,指定支持它的主机类型以及如何在主机上实现其方法:

impl wasi::sockets::udp::UdpSocket<
  wasi::sockets::network::ErrorCode,
  MyDatagramStream,
  wasi::sockets::network::IpSocketAddress,
  (),
  MyDatagramStream,
  MyPollable
  >
for MyState {
   type T = MySocket;
  fn start_bind(
    &mut self,
    self_: BorrowedResourceGuard<MySocket>,
    _network: BorrowedResourceGuard<()>,
    local_address: wasi::sockets::network::IpSocketAddress
  ) -> Result<(), wasi::sockets::network::ErrorCode> {
    *(*self_).os.lock().unwrap() = Some(Arc::new(
      std::net::UdpSocket::bind(local_address)
        .map_err(|_| wasi::sockets::network::ErrorCode::Unknown)?));
    Ok(())
  }
  fn stream(
    &mut self,
    self_: BorrowedResourceGuard<Self::T>,
    _remote_address: Option<IpSocketAddress>
  ) -> Result<(MyDatagramStream, MyDatagramStream), ErrorCode> {
    let sock = (*self_).os.lock().unwrap();
    let sock = sock.as_ref().unwrap();
    Ok((MyDatagramStream { socket: sock.clone() },
      MyDatagramStream { socket: sock.clone() }))
  }
  fn finish_bind(
    &mut self,
    _self: BorrowedResourceGuard<Self::T>
  ) -> std::result::Result<(), ErrorCode> {
    Ok(())
  }
  fn r#subscribe(
    &mut self,
    _self: BorrowedResourceGuard<Self::T>
  ) -> MyPollable {
    MyPollable::AlwaysReady
  }
}

完成这些后,我们就可以运行 Hyperlight 并将其指向我们的 Wasm 二进制文件了。这只需要一些样板代码。在下面的代码中,我们首先创建一个新的 Hyperlight 沙箱,其中有足够的内存来运行 Wasm 运行时。然后,我们注册刚刚编写的绑定。最后,我们加载我们的 Wasm 组件并运行它。

fn main() {
  let state = State::new();
  // Setup the sandbox with enough resources to run a Wasm runtime
  let mut sb: hyperlight_wasm::ProtoWasmSandbox =
    hyperlight_wasm::SandboxBuilder::new()
      .with_guest_input_buffer_size(5_000_000)
      .with_guest_heap_size(10_000_000)
      .with_guest_panic_context_buffer_size(10_000_000)
      .with_guest_stack_size(10_000_000)
      .with_guest_function_call_max_execution_time_millis(0)
      .build()
      .unwrap();
  // Register the host functions
  let rt = crate::bindings::register_host_functions(&mut sb, state);
  // Load our Wasm Component and run it
  let sb = sb.load_runtime().unwrap();
  let sb = sb.load_module("echo.wasm").unwrap();
  let mut wrapped = bindings::RootSandbox { sb, rt };
  let run_inst = root::component::RootExports::run(&mut wrapped);
  let _ = run_inst.run();
}

最后,在我们可以运行示例 Wasm 二进制文件之前,我们需要提前将它编译成 wasmtime 可以加载的格式,而无需在运行时生成代码:

cargo install --git https://github.com/hyperlight-dev/hyperlight-wasm --branch hyperlight-component-macro hyperlight-wasm-aot
hyperlight-wasm-aot compile --component echo.wasm echo.bin

现在我们准备好启动应用程序并运行它了。 你可以通过在一个终端中运行 cargo run 来执行此操作,它将启动一个侦听端口 8080 的服务器: **cargo run**

在另一个终端中,我们现在可以发送用户数据报协议 (UDP) 数据包。 你可以使用 netcat 实用程序来执行此操作,如下所示(对于 Windows,你可以在此处找到 netcat 替代方案):

**nc -u 127.0.0.1 8080**

现在你可以自由输入了! 你发送给服务器的任何内容都会回显回来。 这就是 Hyperlight 的 Wasm 访客的基础知识。

接下来是什么?

到目前为止,我们主要讨论了在 Hyperlight 上使用 WASI 以实现操作系统和 VM 之间的可移植性。但这并不是终点:由于 WebAssembly 指令集和组件模型抽象二进制接口 (ABI) 不与任何一个机器架构相关联,因此 wasm 应用程序也可以在不同的指令集之间移植。 你很快就可以期望 Hyperlight WASM 也可以在 Arm64 处理器上运行。 重建你的主机就可以了,你永远不需要重新编译在其中运行的 wasm 应用程序。

你可能还注意到,虽然我们可以在访客中支持 WASI API,但 VMM 主机没有提供其自己的 WASI 接口的默认实现,因此你必须自己实现它们。 虽然许多应用程序会欣赏这种灵活性,包括像 Microsoft 这样使用 Hyperlight 创建产品的云供应商,但这确实意味着入门和尝试可能需要一些时间。 出于这个原因,我们计划使用 WASI 的一些接口的默认绑定来扩展 Hyperlight-Wasm。 这样,如果你只想沙箱化一个 HTTP 服务器或一个侦听套接字的服务,你就不需要做太多其他事情才能入门。

参与 Hyperlight

Hyperlight 已由 Microsoft 捐赠给 CNCF 的 Sandbox 计划。 我们的目标是提高快速、高效和安全的云原生计算的标准。 Hyperlight WASM 访客是我们 Hyperlight 项目的最新补充,可以方便地编写在 Hyperlight 上运行的程序。

作为根据 Apache 2.0 许可获得许可的开源项目,Hyperlight 强调了 Microsoft 致力于在技术社区内促进创新和协作。 我们欢迎开发人员、解决方案架构师和 IT 专业人员来帮助构建和增强 Hyperlight。 要开始使用 Hyperlight,请访问 Hyperlight 的代码库 并告诉我们你的想法。

访问 Github 上的 Hyperlight 代码库。

[头像:Yosh Wuyts]

Yosh Wuyts

高级开发者倡导者

Yosh 是 Microsoft 位于丹麦哥本哈根的云原生倡导者。 他们是 Rust Async WG 的成员,也是 Bytecode Alliance 认可的贡献者。 他们也是一位自豪的猫爸爸和热情的厨师。

查看此作者的更多文章

[头像:Lucy Menon]

Lucy Menon

微软软件工程师和研究员

Lucy Menon 是 Microsoft 的一名软件工程师和研究员,致力于 Hyperlight 并形式化 WebAssembly 组件模型。

查看此作者的更多文章

相关文章

Microsoft 开源

开源使 Microsoft 产品和服务能够为我们的客户带来选择、技术和社区。

浏览项目

在社交媒体上与我们联系