WASI 实战:把 WebAssembly 带进服务器与边缘

如果你前两年接触过 WebAssembly,多半是在浏览器里给前端提速。但 2024 年之后,WASI(WebAssembly System Interface)让同一份 .wasm 字节码跑进了服务器、边缘节点甚至命令行。本文用 wasmtime 和一个最小 Rust 示例,讲清 WASI 到底是什么、它和 Docker 如何共存,以及它在插件系统与服务端轻量 AI 推理里的真实落地。如果你只想在浏览器里用,可以先看我们整理的WebAssembly 浏览器实战

一、WASI 到底是什么

WebAssembly 本身只是一套可移植的字节码指令集,最初为浏览器设计。WASI 则是它的“系统调用接口”——它定义了 wasm 模块如何访问文件、环境变量、时钟、随机数等宿主资源。换句话说,没有 WASI,wasm 只能在浏览器沙箱里跑;有了 WASI,它就能像普通程序一样在操作系统上运行。

当前主流是 wasi_snapshot_preview1(简称 preview1),配套的 wasi-threadswasi-sockets 正在补全;更现代的是Component Model,用 WIT 接口描述让不同语言编译出的组件可以互相调用,是 WASI 走向“语言无关插件生态”的关键。

二、为什么是 WASI:三个无法拒绝的理由

  • 默认零权限的沙箱:模块拿不到任何系统资源,必须显式用 --dir--env 授权,安全边界比进程清晰得多。
  • 冷启动毫秒级:没有容器启动、没有语言运行时初始化,适合边缘函数和高频短任务。
  • 一次编译到处跑:同一份 wasm 在 Linux/macOS/Windows、x86/ARM 上行为一致,跨架构分发不再打包多份二进制。

三、十分钟跑通第一个 WASI 程序

用 Rust 写一个最小例子,编译到 wasm32-wasip1 目标,再用 wasmtime 运行。先确认工具链:

# 安装 wasm 运行时(macOS / Linux)
brew install wasmtime        # 或:curl https://wasmtime.dev/install.sh -sSf | bash

# 准备 Rust 交叉目标
rustup target add wasm32-wasip1

# 新建项目
cargo new hello-wasi && cd hello-wasi

src/main.rs 换成下面这段,它会写一条日志并尝试写文件(写文件需要宿主授权):

// src/main.rs
use std::fs;

fn main() {
    println!("hello from WASI");
    // 只有在宿主显式授权 --dir 时才允许写入
    match fs::write("/tmp/wasi_out.txt", b"wasi works
") {
        Ok(_) => println!("wrote /tmp/wasi_out.txt"),
        Err(e) => println!("write denied: {}", e),
    }
}

编译并运行。注意 --dir=/tmp 把宿主目录显式挂进沙箱,否则写文件会被拒绝:

cargo build --target wasm32-wasip1
wasmtime --dir=/tmp target/wasm32-wasip1/debug/hello-wasi.wasm
# 输出:
# hello from WASI
# wrote /tmp/wasi_out.txt

四、WASI 与 Docker、浏览器 JS 怎么选

维度 WASI / Wasm Docker 容器 浏览器 JS
启动速度 毫秒级 秒级 即时
隔离边界 语言级沙箱 OS 级命名空间 同源策略
分发体积 KB~MB 数十 MB~GB 依赖浏览器
可移植性 一次编译到处跑 依赖镜像与内核 依赖运行时
系统调用 经 WASI 抽象层 直接调用内核 受限 Web API

结论很清晰:WASI 不是要取代谁,而是在“需要接近原生速度、又要求强隔离和极小体积”的细分场景里补位。

五、三个真实落地场景

1. 边缘函数

Cloudflare Workers、Fastly Compute 底层都依赖 wasm/WASI 跑用户代码。把逻辑编译成 wasm,部署到全球边缘节点,冷启动几乎无感——这正是“边缘计算”能做到低延迟的根基。

2. 安全的插件系统

Envoy、Spin、Wasmtime 都支持用 wasm 写插件。相比 Lua 或动态链接库,wasm 插件崩溃不会拖垮宿主进程,且能按租户精确限制资源,是多租户平台的理想扩展点。

3. 服务端轻量 AI 推理

借助 wasi-nn 与 ONNX Runtime 的 wasm 构建,可以把小型模型(embedding、分类、OCR)直接嵌进服务进程,省掉起一个 Python 推理服务的开销。想做更重的私有化大模型,仍然建议走我们写过的Dify + Ollama 私有部署方案,WASI 更适合“模型小、调用频、延迟苛”的链路。

六、WASI 不是来取代 Docker 的

一个常见误会是“WASI 要干掉容器”。现实是两者互补:用 Docker 封装 wasmtime 运行时,再在容器内跑 wasm 插件,既享受镜像分发与编排,又拿到 wasm 的隔离与启动优势。我们之前整理的Docker 实战依然是所有部署的底座。wasm 适合“函数级、短生命周期”的任务,长驻、重 IO 的服务还是交给容器与进程更稳妥。

七、把 wasm 接进 CI

编译目标应当进 CI 卡住,避免本地能跑、流水线挂掉。一个最小 GitHub Actions 步骤:

# .github/workflows/wasm.yml
jobs:
  build-wasm:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: wasm32-wasip1
      - run: cargo build --target wasm32-wasip1 --release
      - run: wasmtime --dir=. target/wasm32-wasip1/release/app.wasm

把这段接进你已有的GitHub Actions 流水线,就能在每次提交时自动验证 wasm 产物可用。

八、边界与坑:文件、网络、线程

  • 网络受限:preview1 默认没有 socket,需要 wasi-sockets(preview2)或宿主自己注入能力,别指望裸 wasm 直接发 HTTP。
  • 线程支持晚:并行需要 wasi-threads 或 Component Model 的异步能力,老项目迁移要评估。
  • 生态仍在演进:WIT、preview2、wasi-nn 接口尚未完全稳定,生产前务必锁定运行时版本,避免上游破坏性变更。

九、运行时选型:wasmtime、wasmer 与 WAMR

真要落地,先选运行时。三者定位不同:wasmtime 是字节跳动与社区维护的参考实现,跟随标准最快,适合做平台底座;wasmer 强调“通用运行时 + 包管理”,一条命令就能跑别人发布的 wasm,适合做工具链;WAMR(WebAssembly Micro Runtime) 由 Intel 主导,体积最小、面向 IoT 与嵌入式,适合资源受限设备。选型时按“要不要嵌入式 / 要不要包生态 / 要不要最新标准”三句话就能快速收敛。

一个实操建议:本地用 wasmtime 跑通后,再决定要不要上 wasmer 的包管理;嵌入式场景直接评估 WAMR 的内存占用曲线。不要一上来就追最前沿——preview1 在绝大多数生产场景已经够用,preview2 与 Component Model 适合在边界清晰后再引入。

运行时 主导方 体积 典型场景
wasmtime 字节跳动/社区 平台底座、Spin
wasmer Wasmer Inc. 命令行工具、包生态
WAMR Intel 极小 IoT、嵌入式

十、现在要不要上 WASI

给你一张决策清单:① 任务是否短生命周期、高并发、低延迟?② 是否需要不可信代码的强隔离?③ 是否愿意接受接口仍在演进的成熟度成本?三条里命中两条,WASI 就值得一试;否则先用容器把业务跑稳。技术选型没有银弹,能解决你当下痛点的那一个,才是对的。

WASI 把 WebAssembly 从“浏览器加速器”变成了“可移植的安全运行时”。它不会取代 Docker,却会在边缘、插件和轻量推理这些缝隙里,悄悄成为下一代基础设施的默认选项。

上一篇 eza + zoxide + bat:用现代命令行工具替换 ls/cd/cat
下一篇 文档即代码实战:用 Git 和 CI 管理技术文档