跳过正文
WebAssembly 在云原生中的应用:从浏览器到 K8s 数据面

WebAssembly 在云原生中的应用:从浏览器到 K8s 数据面

·1221 字·6 分钟·
目录

为什么是 Wasm,不是更多容器
#

先把一个误会解了:这里讨论的 Wasm 和前端那个是同一个字节码,但用法完全不同。浏览器里的 Wasm 是把 C++/Rust 塞进 JS 引擎跑;云原生里的 Wasm 是服务端一个比容器更轻、比进程更安全的执行单元。

容器本来是用来解决进程隔离 + 运行时打包的,Wasm 在字节码层就自带这两个能力——所以它在"容器用着嫌重"的场景里天然合适。

云原生 Wasm 的几个核心特性:

安全边界清晰。 Wasm 模块默认没有任何系统调用能力——它运行在一个沙箱里,访问文件系统、网络、环境变量都必须通过宿主机显式授权的接口。这和容器的安全模型(seccomp + namespace + capabilities)相比,从设计上更难逃逸。

启动极快。 一个 Wasm 模块的冷启动时间在毫秒级甚至亚毫秒级,而一个容器(哪怕是最精简的)冷启动通常在百毫秒到秒级。对 serverless 和边缘计算场景意义重大。

包体积小。 一个完整的 Rust Wasm 业务模块通常 1-5 MB,而一个最小化的 Alpine 容器镜像也有 5-10 MB,实际业务容器镜像动辄 100MB 以上。

跨架构。 同一份 Wasm 字节码在 x86、ARM、RISC-V 上运行,无需重新编译。在多架构 K8s 集群(AWS Graviton 节点混部)里这是真实优势。

WASI:给 Wasm 装上系统调用
#

浏览器里的 Wasm 和宿主机的 JS 引擎通信,靠的是 import/export 的函数。放到服务端,Wasm 模块需要和操作系统打交道(读文件、建 socket),不能靠浏览器 API,于是有了 WASI。

WASI(WebAssembly System Interface)是一套标准化的系统调用接口,定义了 Wasm 模块可以调用哪些宿主机能力。它不是一个实现,而是一组规范:

  • WASI Preview 1(wasi_snapshot_preview1):第一代,已稳定。涵盖文件 I/O、环境变量、随机数、时钟。目前绝大多数 Wasm 工具链(Rust wasm32-wasi、TinyGo、AssemblyScript)默认编译目标。
  • WASI Preview 2(wasi 0.2.0):2024 年初稳定。基于 Component Model 重新设计,引入了 wasi:io、wasi:http、wasi:sockets 等组件接口。Fermyon Spin、wasmtime 1.0+ 已经支持。
  • WASI 0.3:在设计中,重点解决异步 I/O。

对运维工程师来说,记住一点就够:WASI Preview 1 已经可以生产用,Preview 2 是未来方向,编译目标选 wasm32-wasip1wasm32-wasip2 取决于你用的运行时。

containerd + runwasi:K8s 原生运行 Wasm
#

容器运行时接口(CRI)允许 K8s 对接不同的底层运行时。runwasi 是 containerd 的一个 shim,让 containerd 可以直接运行 OCI 格式打包的 Wasm 模块,不需要把 Wasm 塞进一个容器镜像里跑

架构链路:

kubelet → CRI → containerd → containerd-shim-wasmtime-v1 (runwasi) → wasmtime → Wasm 模块

安装 runwasi
#

在 K8s 节点上安装 wasmtime shim(以 Ubuntu 22.04 为例):

# 下载 runwasi 发行版(包含各 runtime 的 shim)
RUNWASI_VERSION=0.5.0
curl -LO https://github.com/containerd/runwasi/releases/download/containerd-shim-wasmtime/v${RUNWASI_VERSION}/containerd-shim-wasmtime-v${RUNWASI_VERSION}-x86_64-linux.tar.gz
tar xzf containerd-shim-wasmtime-*.tar.gz
mv containerd-shim-wasmtime-v1 /usr/local/bin/
chmod +x /usr/local/bin/containerd-shim-wasmtime-v1

# 重启 containerd
systemctl restart containerd

配置 containerd 使用 wasm shim(/etc/containerd/config.toml):

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.wasmtime]
  runtime_type = "io.containerd.wasmtime.v1"

创建 RuntimeClass
#

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: wasmtime
handler: wasmtime

部署 Wasm 工作负载
#

Wasm 模块需要打包成 OCI 镜像(只是用 OCI 格式存储,不是真的容器):

# 用 Rust 编译一个简单的 Wasm HTTP 服务
# Cargo.toml 里 target = "wasm32-wasip1"
cargo build --target wasm32-wasip1 --release

# 打包成 OCI 镜像
# wasm-to-oci 或直接用 oci-spec-rs
docker buildx build --platform wasi/wasm \
  -t registry.example.com/my-wasm-app:latest \
  --push .
# Pod 使用 wasmtime RuntimeClass
apiVersion: v1
kind: Pod
metadata:
  name: wasm-hello
spec:
  runtimeClassName: wasmtime
  containers:
  - name: app
    image: registry.example.com/my-wasm-app:latest
    # Wasm 模块不需要 CMD,wasmtime 直接执行 _start 导出函数

当前成熟度:runwasi + containerd 的方案在 2024 年已经相对稳定,但仍有几个生产限制:不支持 GPU、不支持特权模式、调试工具链比容器稀缺。适合无状态、计算密集、安全要求高的场景。

SpinKube:Wasm 微服务的更完整方案
#

runwasi 解决的是"K8s 能不能运行 Wasm",SpinKube 解决的是"Wasm 微服务如何在 K8s 上管理生命周期"。

SpinKube = Fermyon Spin(Wasm 微服务框架)+ containerd-shim-spin(运行时)+ SpinApp CRD(K8s 自定义资源)+ Spin Operator(控制器)。

安装 SpinKube
#

# 安装 cert-manager(依赖)
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml

# 安装 SpinKube operator
helm install spin-operator \
  --namespace spin-operator \
  --create-namespace \
  --version 0.3.0 \
  --wait \
  oci://ghcr.io/spinkube/charts/spin-operator

# 安装 RuntimeClass 和 SpinAppExecutor
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.3.0/spin-operator.runtime-class.yaml
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.3.0/spin-operator.spin-app-executor.yaml

编写 Spin 应用
#

// src/lib.rs - 一个 Spin HTTP handler
use spin_sdk::http::{IntoResponse, Request, Response};
use spin_sdk::http_component;

#[http_component]
fn handle_request(req: Request) -> anyhow::Result<impl IntoResponse> {
    println!("收到请求: {:?}", req.headers());
    Ok(Response::builder()
        .status(200)
        .header("Content-Type", "application/json")
        .body(r#"{"status":"ok","runtime":"spin-wasm"}"#)
        .build())
}
# spin.toml
spin_manifest_version = 2

[application]
name = "my-service"
version = "0.1.0"

[[trigger.http]]
route = "/..."
component = "my-service"

[component.my-service]
source = "target/wasm32-wasip1/release/my_service.wasm"
[component.my-service.build]
command = "cargo build --target wasm32-wasip1 --release"
watch = ["src/**/*.rs", "Cargo.toml"]

部署 SpinApp
#

apiVersion: core.spinoperator.dev/v1alpha1
kind: SpinApp
metadata:
  name: my-service
  namespace: production
spec:
  image: "registry.example.com/my-service:v1.0.0"
  replicas: 3
  executor: containerd-shim-spin
  resources:
    limits:
      cpu: 100m
      memory: 64Mi  # Wasm 应用内存占用极低
    requests:
      cpu: 10m
      memory: 16Mi

SpinKube 的 HPA 和普通 Deployment 一样配置,不需要特殊处理。冷启动时间 < 5ms,非常适合用 KEDA 做基于消息队列的弹性伸缩。

Envoy/Istio Wasm 插件扩展
#

这是目前云原生 Wasm 落地最成熟的场景之一,生产可用。

Envoy 支持 Wasm 插件作为 HTTP filter,可以在请求/响应链路上插入自定义逻辑,替代 Lua filter 或 ext_proc 的部分场景。

为什么要用 Wasm 替代 Lua
#

Lua filter 的问题:

  • Lua 运行在 Envoy 的 Lua JIT 里,没有严格的资源隔离
  • Lua 代码一旦崩溃可能影响整个 Envoy 进程
  • 没有类型系统,大型 Lua 脚本维护成本高
  • 调试困难,没有好用的本地测试框架

Wasm 插件的优势:

  • 每个插件运行在独立沙箱,崩溃不影响 Envoy 主进程
  • 可以用 Rust/Go/AssemblyScript 编写,类型安全
  • 本地用 envoy + Wasm 插件直接测试,和生产行为一致

用 Rust 写一个 Envoy Wasm 插件
#

# Cargo.toml
[package]
name = "rate-limit-header"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
proxy-wasm = "0.2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
// src/lib.rs - 给所有响应注入限流头
use proxy_wasm::traits::*;
use proxy_wasm::types::*;

proxy_wasm::main! {{
    proxy_wasm::set_log_level(LogLevel::Trace);
    proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> {
        Box::new(RateLimitRoot)
    });
}}

struct RateLimitRoot;
impl Context for RateLimitRoot {}
impl RootContext for RateLimitRoot {
    fn create_http_context(&self, _: u32) -> Option<Box<dyn HttpContext>> {
        Some(Box::new(RateLimitFilter))
    }
    fn get_type(&self) -> Option<ContextType> {
        Some(ContextType::HttpContext)
    }
}

struct RateLimitFilter;
impl Context for RateLimitFilter {}
impl HttpContext for RateLimitFilter {
    fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action {
        self.set_http_response_header("X-Ratelimit-Limit", Some("1000"));
        self.set_http_response_header("X-Powered-By", Some("envoy-wasm"));
        Action::Continue
    }
}
# 编译
cargo build --target wasm32-unknown-unknown --release
# 产物:target/wasm32-unknown-unknown/release/rate_limit_header.wasm

在 Istio 中部署 Wasm 插件
#

Istio 1.12+ 支持通过 WasmPlugin CRD 分发 Wasm 插件,不需要手动把 wasm 文件放到每个节点:

apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: rate-limit-header
  namespace: my-app
spec:
  selector:
    matchLabels:
      app: backend  # 只注入到 backend Pod 的 sidecar
  url: oci://registry.example.com/wasm/rate-limit-header:v1.0.0
  phase: STATS  # 在 stats filter 之后执行
  pluginConfig:
    max_requests: 1000

Istio 会自动把 Wasm 模块分发到匹配的 Envoy sidecar(或 Ambient Mode 的 Waypoint Proxy)。插件配置通过 pluginConfig 以 JSON 传入,插件代码用 get_configuration() 读取。

性能对比
#

根据 Envoy 官方测试数据(2024):

Filter 类型额外延迟 P50额外延迟 P99内存(per worker)
Native C++~0.01ms~0.05ms基准
Lua filter~0.1ms~0.5ms+2MB
Wasm filter~0.05ms~0.2ms+5MB(wasm 运行时)
ext_proc~0.5ms+~2ms+取决于外部进程

Wasm 比 Lua 快,比 ext_proc 快得多,只比 native C++ 慢一点但安全隔离更好。

Wasm 做 OPA 策略扩展
#

Open Policy Agent(OPA)是云原生策略引擎,但 Rego 语言对很多团队有学习成本,而且某些复杂策略(调用外部 API、复杂数据转换)用 Rego 写很痛苦。

OPA 从 0.35 版本开始支持 Wasm 编译后的策略:把 Rego 策略编译成 Wasm,嵌入到应用进程里执行,或者用 Wasm 直接写策略逻辑。

OPA + Wasm 的两种用法
#

方式一:把 Rego 编译成 Wasm(减少网络调用)

# 把 Rego policy 编译成 wasm bundle
opa build -t wasm -e 'authz/allow' policy.rego

# 产物是一个 bundle.tar.gz,里面包含 policy.wasm
# 在应用里用 @open-policy-agent/opa-wasm(JS)或 wasmtime(Rust/Go)执行

这种方式的好处:策略在进程内执行,不需要 sidecar OPA 进程,延迟从 1-5ms 降到 0.1ms 以内。

方式二:用 Rust/Go 编写自定义策略函数(Rego built-in)

OPA 支持通过 Wasm 扩展内置函数:

// 注册一个 custom_jwt_verify built-in
// 让 Rego 可以调用:custom_jwt_verify(token, pubkey)
use opa_wasm_sdk::*;

#[no_mangle]
pub extern "C" fn custom_jwt_verify(token_ptr: i32, key_ptr: i32) -> i32 {
    // 自定义 JWT 验证逻辑(比如支持特殊算法)
    // 返回 1 表示有效,0 表示无效
    todo!()
}

实际上这种方式目前工具链还不成熟,大多数团队用的是方式一(Rego → Wasm 编译)。

在 K8s 准入控制中的应用
#

# 用 OPA Gatekeeper + Wasm bundle 替代纯 Rego
# 把复杂的 Go 逻辑编译成 Wasm,在 Gatekeeper 中调用
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: customjwtpolicy
spec:
  crd:
    spec:
      names:
        kind: CustomJwtPolicy
  targets:
  - target: admission.k8s.gatekeeper.sh
    rego: |
      package customjwtpolicy
      # 调用 Wasm 编译的内置函数
      violation[{"msg": msg}] {
        token := input.review.object.metadata.annotations["auth-token"]
        not custom_jwt_verify(token, data.pubkey)
        msg := "invalid JWT token"
      }

Wasm vs 容器:数据对比
#

维度容器(Alpine base)Wasm(wasmtime)备注
冷启动时间200ms - 2s1 - 10msWasm 优势显著
最小镜像大小5MB(Alpine)0.5 - 5MB依赖语言和框架
内存占用(hello world)10-30MB1-5MBWasm 更轻
CPU overhead几乎为零5-15%(JIT 解释)成熟 AOT 编译后接近零
安全隔离namespace + seccomp沙箱 + WASI 白名单Wasm 更严格
生态系统极其成熟快速成长但仍有差距容器胜
调试工具完善基础可用容器胜
状态管理完整(volumes、PVC)有限(WASI fs)容器胜
GPU 支持支持不支持容器胜

冷启动时间数据来源:CNCF Wasm 工作组 2024 年基准测试,测试环境 AWS c6g.xlarge(Graviton 3)。

AI Agent 沙箱:Wasm 的新战场
#

这是 2024-2025 年 Wasm 在云原生领域最值得关注的新场景:用 Wasm 给 AI Agent 的代码执行能力提供安全隔离

AI Agent 执行用户或 LLM 生成的代码,安全风险极高:

  • 恶意代码尝试读取 /etc/passwd、访问云 metadata 接口
  • 代码死循环或内存溢出
  • 横向移动,访问集群内其他服务

传统方案是给每个代码执行任务起一个容器(gVisor 加固),但冷启动 200ms+ 在交互式 Agent 场景下用户体验很差。

Wasm 的方案:

// 用 wasmtime 嵌入式运行时执行用户代码
use wasmtime::*;

fn execute_user_code(wasm_bytes: &[u8], input: &str) -> Result<String> {
    let engine = Engine::default();
    let mut store = Store::new(&engine, ());

    // 限制资源:最多执行 1 亿条指令,最多 64MB 内存
    let mut limits = StoreLimitsBuilder::new()
        .fuel(100_000_000)
        .memory_size(64 * 1024 * 1024)
        .build();
    store.limiter(|_| &mut limits);
    store.add_fuel(100_000_000)?;

    // 不暴露任何 WASI 接口,完全沙箱
    let module = Module::new(&engine, wasm_bytes)?;
    let instance = Instance::new(&mut store, &module, &[])?;

    let run = instance.get_typed_func::<(), i32>(&mut store, "run")?;
    let result = run.call(&mut store, ())?;

    Ok(format!("exit code: {}", result))
}

这个方案的好处:

  • 冷启动 < 5ms(wasmtime 加载预编译的 Wasm 模块)
  • 内存限制精确可控
  • CPU 时间通过 fuel 机制精确计量和限制
  • 无法访问文件系统、网络,除非宿主机显式允许

实际上 Cloudflare Workers、Fastly Compute@Edge 已经把这套方案运行在生产上,处理数十亿次请求。在私有 K8s 集群里复刻这套方案的工具链已经基本成熟。

在 K8s 里部署 AI 代码执行沙箱:

# 用 SpinKube 部署代码执行沙箱服务
apiVersion: core.spinoperator.dev/v1alpha1
kind: SpinApp
metadata:
  name: code-executor
  namespace: ai-agents
spec:
  image: "registry.example.com/code-executor:v1.0.0"
  replicas: 5
  executor: containerd-shim-spin
  resources:
    limits:
      cpu: 500m
      memory: 512Mi  # Wasm 沙箱内的内存限制由代码控制,这里是节点级别限制

当前成熟度评估
#

给出一个直接的落地建议:

现在就可以上生产的场景:

  1. Envoy/Istio Wasm 插件:替代 Lua filter,适合自定义认证、请求改写、遥测注入。工具链成熟,Istio WasmPlugin CRD 简化了分发。
  2. OPA Rego → Wasm 编译:减少策略评估延迟,适合高频准入控制。只需要 opa build -t wasm 一条命令。
  3. 边缘/CDN 计算(如果用 Cloudflare Workers 或 Fastly):完全成熟。

2025 年可以试验,2026 年考虑生产的场景:

  1. SpinKube 微服务:适合无状态、短请求、安全要求高的服务。框架在快速成熟,但运维工具链(日志、调试、traceing 集成)还在追赶。
  2. AI Agent 代码执行沙箱:技术可行,但需要投入工程时间搭建语言编译流水线(把用户提交的 Python/JS 转成 Wasm)。

还太早,观望为主:

  1. runwasi 替代通用容器:大多数业务服务仍然需要依赖 glibc、数据库驱动等复杂库,迁移成本高,WASI 的 posix 兼容性还有缺口。
  2. 有状态服务:Wasm 的持久化存储方案还没有标准化,不适合数据库、消息队列这类工作负载。

一个判断标准: 如果你的工作负载是无状态 + 短生命周期 + 安全敏感 + 启动时间关键,Wasm 值得认真考虑。如果有其中一条不满足,容器仍然是更稳妥的选择。

Wasm 在云原生的定位不是取代容器,而是在容器不够轻、不够快、不够安全的地方填补空白。这个空白比五年前大了很多,工具链也成熟了很多,但距离大规模替代容器还有相当长的路要走。

Wenzhuo Huang
作者
Wenzhuo Huang
搞运维的工程师,写代码的运维人。专注 Kubernetes、AWS、GitOps 与基础设施可靠性。这个博客既是我的技术笔记本,也是我踩过的坑的受害者档案。

相关文章

Istio Ambient Mode 无 Sidecar 服务网格实践

·1464 字·7 分钟
Sidecar 模式已经陪我们走了六七年,但它的问题也越来越难以忽视。Ambient Mode 不是缝缝补补,而是从架构层面重新设计了服务网格的数据面。本文从实际运维视角深入拆解 ztunnel + Waypoint 两层架构,并给出从 Sidecar 迁移到 Ambient 的完整路径。

AWS EKS 生产实践:网络、安全与多集群管理

·792 字·4 分钟
管理多套 EKS 集群两年下来,踩了不少坑。本文系统整理网络选型、IAM 权限、节点管理、集群升级、安全加固和成本控制这六个核心话题,每个话题都有具体配置示例和实际遇到的问题。