ad7d8cdd72
- describe resident Runtime boundaries and sandbox guarantees - specify component archive and host capability contracts - document Console ownership and persistence behavior - explain versioned WIT Registry, lock files, and path replace - provide startup, packaging, publication, and dependency commands
320 lines
11 KiB
Markdown
320 lines
11 KiB
Markdown
# Wasmeld Runtime 架构
|
||
|
||
**状态:** Draft
|
||
|
||
**日期:** 2026-07-22
|
||
|
||
**范围:** 仅实现 Wasmeld Runtime
|
||
|
||
## 1. 范围
|
||
|
||
本阶段只解决一件事:在一个 Rust 宿主进程中,安全地加载并常驻运行内部 Rust 编译的 WebAssembly Component。
|
||
|
||
需要具备:
|
||
|
||
- Rust Component 的构建、注册、加载、调用、停止与重载。
|
||
- 同一实例跨多次调用保留内存。
|
||
- 同一实例不发生并发重入。
|
||
- Wasmtime 资源限制与最小 sandbox。
|
||
- 统一且不含业务语义的 Component 调用契约。
|
||
|
||
当前不做 HTTP、客户、鉴权、数据库、网络、消息队列、业务状态持久化、分布式部署或发布治理。
|
||
|
||
## 2. 核心结构
|
||
|
||
~~~text
|
||
Rust Component source
|
||
|
|
||
| cargo build --target wasm32-wasip2 --release
|
||
v
|
||
component.wasm + manifest.toml
|
||
|
|
||
v
|
||
Component Registry
|
||
|
|
||
v
|
||
Wasmeld Runtime (one Rust process)
|
||
|
|
||
+-- Engine
|
||
+-- compiled Component cache
|
||
+-- Service Manager
|
||
|
|
||
+-- ServiceActor
|
||
+-- bounded mailbox
|
||
+-- Store<HostState>
|
||
+-- restricted Linker
|
||
+-- Component Instance
|
||
~~~
|
||
|
||
Runtime 是一个 Rust 库或单独进程,不提供网络协议。对内只暴露:
|
||
|
||
~~~text
|
||
register(manifest, artifact) -> ServiceKey
|
||
start(service_key, init_config) -> ActorHandle
|
||
invoke(actor_handle, bytes) -> bytes or RuntimeError
|
||
stop(actor_handle)
|
||
reload(next_manifest, next_artifact, init_config) -> ActorHandle
|
||
~~~
|
||
|
||
调用方未来可以是 HTTP Adapter、CLI 或测试程序;Runtime 本身不关心调用来源。
|
||
|
||
## 3. 核心对象
|
||
|
||
| 对象 | 责任 |
|
||
| --- | --- |
|
||
| Component Artifact | 一个不可变的 component.wasm 与最小 manifest。 |
|
||
| Service Revision | Artifact 的 id + revision。 |
|
||
| Engine | 进程级共享的 Wasmtime 编译和执行引擎。 |
|
||
| Component Cache | 缓存编译后的 Component,不保存实例内存。 |
|
||
| Service Manager | 管理 Service Revision 与 ActorHandle 的映射。 |
|
||
| ServiceActor | 独占 Store、Instance、HostState 与有界邮箱。 |
|
||
| ActorHandle | 向 Actor 发送调用,不能接触 Store 或 Instance。 |
|
||
| HostState | Store 专有的运行时状态,不包含业务数据。 |
|
||
|
||
V1 采用:
|
||
|
||
~~~text
|
||
one Service Revision -> one ServiceActor -> one Store + one Instance
|
||
~~~
|
||
|
||
多个 Actor、分片和水平扩展属于后续能力。
|
||
|
||
## 4. 制品与注册
|
||
|
||
内部服务构建为 Component:
|
||
|
||
~~~bash
|
||
cargo build --target wasm32-wasip2 --release
|
||
~~~
|
||
|
||
最小制品:
|
||
|
||
~~~text
|
||
component.wasm
|
||
manifest.toml
|
||
~~~
|
||
|
||
manifest 只包含:
|
||
|
||
~~~text
|
||
id
|
||
revision
|
||
component path
|
||
expected WIT world
|
||
memory limit
|
||
fuel per call
|
||
deadline
|
||
mailbox capacity
|
||
input/output byte limits
|
||
~~~
|
||
|
||
注册规则:
|
||
|
||
- ServiceKey 由 id 与 revision 构成,不允许覆盖。
|
||
- Runtime 校验 manifest 的 WIT world 格式与资源策略;实例化时由 typed binding 校验基础导出契约。
|
||
- V1 只接受本地受信任制品;Registry、签名、SBOM 与远程下载后置。
|
||
- 已编译 Component 可缓存,但 Actor 的内存只能存在于 Store + Instance 中。
|
||
|
||
## 5. 最小 WIT 契约
|
||
|
||
V1 不定义任何业务协议。输入输出都是有长度上限的 opaque bytes。
|
||
|
||
~~~wit
|
||
package wasmeld:service@0.1.0;
|
||
|
||
world service-component {
|
||
variant service-error {
|
||
init-failed(string),
|
||
call-failed(string),
|
||
}
|
||
|
||
export init: func(config: list<u8>) -> result<_, service-error>;
|
||
export invoke: func(input: list<u8>) -> result<list<u8>, service-error>;
|
||
}
|
||
~~~
|
||
|
||
规则:
|
||
|
||
- init 仅在一个新 Instance 上调用一次。
|
||
- invoke 是唯一的普通调用入口。
|
||
- Component 返回 service-error 后,Actor 可继续使用。
|
||
- trap、资源超限或中断后,整个 Store + Instance 必须丢弃,不能继续调用。
|
||
- 基础 world 不定义宿主能力 import。组件专属 World 通过独立 interface 按需 import
|
||
平台能力,详见 `wasmeld-capabilities.md`。普通 Rust `wasm32-wasip2` 产物会隐式导入标准 WASI P2
|
||
接口,因此 Runtime 提供受限的 WASI 兼容层,而不是把它们暴露为业务 ABI。
|
||
- Runtime 在注册时只允许以下兼容 import:`wasi:cli/{environment,exit,stdin,stdout,stderr}`、
|
||
`wasi:clocks/wall-clock`、`wasi:filesystem/{preopens,types}`、`wasi:io/{error,streams}`。
|
||
`wasi:io/poll`、WASI monotonic clock、随机数、socket、HTTP 和未知平台 import 都会被拒绝。
|
||
- V1 提供独立的 `wasmeld:clock/monotonic-clock@0.1.0` 平台能力。只有显式导入
|
||
该 interface 的组件会获得对应 Linker 实现。
|
||
- health、shutdown、日志、文件、网络和标准 I/O 都不属于基础 ABI。wall clock 仅作为
|
||
Rust/WASI 兼容层的一部分存在,不用于传递业务能力;随机数不在 V1 白名单内。
|
||
|
||
## 6. 常驻 Actor
|
||
|
||
~~~text
|
||
Caller
|
||
-> ActorHandle
|
||
-> bounded Sender<Invocation>
|
||
-> dedicated Wasm worker
|
||
-> Store + Instance + HostState
|
||
~~~
|
||
|
||
一个 Actor 的 worker 独占 Store。不能使用 Arc<Mutex<Store>> 共享 Store,也不能让同一 Instance 重入。
|
||
|
||
处理流程:
|
||
|
||
1. ActorHandle 提交 Invocation。
|
||
2. Actor 从 FIFO mailbox 取出一条消息。
|
||
3. Actor 在同一 Store + Instance 上调用 service.invoke。
|
||
4. Actor 通过 oneshot response 返回结果。
|
||
5. 下一条消息才可进入该 Instance。
|
||
|
||
这保证 Rust heap、Wasm linear memory 和全局变量在多次调用间常驻,同时保证状态修改串行。
|
||
|
||
实现约束:
|
||
|
||
- Wasm 调用放在专用 worker 线程或线程池,不能阻塞 Tokio 的通用 worker。
|
||
- V1 不使用 Wasmtime async 或 Wasm threads。为兼容 Rust 的 `wasm32-wasip2` 组件,
|
||
链接同步、受限的 WASI P2 白名单实现。
|
||
- mailbox 满时立即返回 RuntimeOverloaded。
|
||
- 调用 deadline 到期且尚未进入 Instance 时直接取消。
|
||
- Component 不得在 invoke 之外启动后台循环。
|
||
|
||
Actor 内存是易失的。Runtime 重启、Actor fault、stop 或 reload 后,下一实例重新执行 init,不恢复旧内存。
|
||
|
||
## 7. 生命周期
|
||
|
||
~~~text
|
||
Registered -> Starting -> Ready -> Draining -> Stopped
|
||
|
|
||
+-> Failed
|
||
~~~
|
||
|
||
- register:保存 Artifact 与 manifest。
|
||
- start:取得已编译 Component,创建 Store、受限 WASI Linker 与 Instance,然后调用 init。
|
||
- ready:接收 invoke。
|
||
- draining:拒绝新调用,等待当前调用完成。
|
||
- stopped:释放 Store 和 Instance。
|
||
- failed:trap、fuel 耗尽、deadline 或 Runtime 错误后立即释放 Store 和 Instance。
|
||
|
||
故障后:
|
||
|
||
~~~text
|
||
fault
|
||
-> mark Actor failed
|
||
-> reject queued calls
|
||
-> drop Store + Instance
|
||
-> optionally create a new Actor
|
||
~~~
|
||
|
||
自动重建可配置,但 V1 不应隐藏故障或声称保留了旧内存。
|
||
|
||
reload 的最小流程:
|
||
|
||
1. 注册新的 Service Revision。
|
||
2. start 新 Actor,确保 init 成功。
|
||
3. 调用方切换到新的 ActorHandle。
|
||
4. 调用方显式 stop 旧 Actor 并释放。
|
||
|
||
V1 不做请求级流量切换或状态迁移。
|
||
|
||
## 8. Sandbox 与资源限制
|
||
|
||
V1 Linker 注册受限 WASI P2 兼容实现,并按 Component 的真实 imports 注册已批准的
|
||
平台能力。每个 Actor 都有独立
|
||
`WasiCtx`,并且显式禁止 TCP、UDP、DNS 和所有 socket 地址。默认配置还保证无环境变量、
|
||
参数、工作目录或预打开目录,stdin 关闭,stdout/stderr 丢弃。因此 Component 默认没有:
|
||
|
||
- 文件系统、环境变量、命令行参数或标准 I/O 继承。
|
||
- 网络、DNS、socket 或原始系统调用。
|
||
- 数据库、密钥、配置文件、宿主目录或宿主进程能力。
|
||
|
||
每个 Actor 必须显式设置:
|
||
|
||
| 限制 | 目的 |
|
||
| --- | --- |
|
||
| linear memory | 阻止无限内存增长。 |
|
||
| tables / instances / memories | 限制 Wasm 对象数量。 |
|
||
| fuel per invocation | 限制确定性的计算工作量。 |
|
||
| epoch deadline | 限制 wall-clock 执行时间。 |
|
||
| input/output bytes | 限制单次数据传输。 |
|
||
| mailbox capacity | 实现背压,避免无限排队。 |
|
||
|
||
fuel 和 epoch 只约束 Wasm 执行。未来一旦加入 Host I/O,必须单独定义 timeout、取消和连接资源策略。
|
||
|
||
Runtime 的 epoch ticker 上限为 10 ms,且服务 deadline 不能小于该 tick,以避免配置使
|
||
wall-clock deadline 失效。ActorHandle 持有 ticker 的生命周期引用,因此 Runtime 对象释放后,
|
||
仍被调用方持有的 ActorHandle 不会失去 epoch 中断能力。
|
||
|
||
Wasm Store 隔离 Component 内存和可见 import,但不是完整 OS 隔离。V1 以第一方代码为前提;进程、Pod、gVisor 或 Kata 隔离在实际需要时再加入。
|
||
|
||
## 9. 最小代码结构
|
||
|
||
~~~text
|
||
wit/
|
||
service/
|
||
package.wit
|
||
clock/
|
||
package.wit
|
||
|
||
crates/
|
||
wasmeld-runtime/
|
||
bindings.rs
|
||
error.rs
|
||
manifest.rs
|
||
runtime.rs
|
||
lib.rs
|
||
|
||
components/
|
||
echo/
|
||
counter/
|
||
fault/
|
||
spin/
|
||
clock-probe/
|
||
|
||
tests/
|
||
runtime_components.rs
|
||
~~~
|
||
|
||
## 10. 验收测试
|
||
|
||
测试 Component:
|
||
|
||
- echo:原样返回输入,验证基础加载与调用。
|
||
- counter:init 创建计数状态,每次 invoke 递增,验证内存常驻。
|
||
- fault:显式 trap,验证旧 Store 会被丢弃且 Actor 可重建。
|
||
- spin:不可被优化掉的 CPU 循环,验证 fuel 与 epoch 中断。
|
||
- clock-probe:显式导入平台 monotonic-clock,验证能力按需 Link。
|
||
- wasi-clock-probe:通过 Rust 标准库导入 WASI monotonic clock,验证注册时拒绝越权。
|
||
|
||
必须验证:
|
||
|
||
1. Component 只有在导出匹配预期 WIT world、且其 import 可由受限 WASI Linker 满足时才可启动。
|
||
2. 同一 Actor 调用 counter 两次能观察到递增状态。
|
||
3. stop、fault 或 Runtime 重启后,counter 从 init 重新开始。
|
||
4. 多个并发 invoke 会严格经同一 Actor 串行执行。
|
||
5. 无限循环会被 fuel 或 epoch deadline 终止。
|
||
6. trap 后旧 Store 被丢弃,重建 Actor 后可再次调用。
|
||
7. mailbox 满时立即出现 overload,不无限等待。
|
||
8. memory.grow 超过限制会导致 Actor fault。
|
||
9. reload 能启动新 revision 的 Actor;旧 Actor 由调用方显式 stop。
|
||
|
||
## 11. 后续扩展触发条件
|
||
|
||
| 明确需求 | 再引入的能力 |
|
||
| --- | --- |
|
||
| 需要网络入口 | HTTP/gRPC Adapter 与 API Gateway。 |
|
||
| 需要调用宿主资源 | 独立 Host Capability 与 WIT import。 |
|
||
| 需要跨重启保留状态 | 外部状态、checkpoint 与恢复协议。 |
|
||
| 需要多个实例 | Actor 池、分片和放置策略。 |
|
||
| 需要生产发布治理 | 制品签名、Registry、灰度与回滚。 |
|
||
| 需要执行非第一方代码 | 额外进程或 OS sandbox。 |
|
||
|
||
## 12. 参考资料
|
||
|
||
- [WebAssembly Component Model](https://component-model.bytecodealliance.org/)
|
||
- [Rust Components with wasm32-wasip2](https://component-model.bytecodealliance.org/language-support/building-a-simple-component/rust.html)
|
||
- [Wasmtime](https://docs.wasmtime.dev/)
|
||
- [Wasmtime interruption with fuel and epochs](https://docs.wasmtime.dev/examples-interrupting-wasm.html)
|