b5aa008729
Describe ResidentSupervisor ownership, TCP policy and configuration, bounded stream backpressure, timer scheduling, restart behavior, and deterministic Host-resource shutdown order.
205 lines
9.3 KiB
Markdown
205 lines
9.3 KiB
Markdown
# Wasmeld Console
|
||
|
||
`wasmeld-console` 是 `wasmeld-runtime` 的本地 HTTP 管理适配器。它负责:
|
||
|
||
- 接收 `.wasmpkg`,校验并保存 `component.wasm` 与运行时 manifest
|
||
- 发布、查询和下载不可变的二进制 WIT Package
|
||
- 启动、停止和重启内嵌的 `wasmeld-runtime`
|
||
- 启动、停止和重启常驻 Actor
|
||
- 管理服务 ID 到活动版本的 Deployment
|
||
- 通过独立 Gateway 转发原始二进制调用
|
||
- 为导入 `wasmeld:kv/store@0.1.0` 的 Component 提供持久化 KV
|
||
- 暴露调用计数、状态和最近事件
|
||
- 在 Release 可执行文件中嵌入 Solid + Tailwind v4 管理面
|
||
|
||
Wasm 制品保存在本地文件系统。服务 manifest、调用计数和最近 256 条事件通过
|
||
[Toasty](https://github.com/tokio-rs/toasty) 写入本地 libSQL 数据库,默认路径是
|
||
`var/wasmeld/console.db`。Deployment 也存储在同一数据库中。
|
||
Host KV 使用 `(service_id, key)` 复合主键存储在该数据库中:同一服务的 Revision
|
||
共享数据,不同服务互相隔离,Runtime 或 Console 重启不会清空 KV。
|
||
|
||
调用计数、错误数、延迟和调用事件通过容量受限的异步队列批量写入数据库,Gateway
|
||
响应不等待 libSQL。队列不可用或进程异常退出时,尚未写入的遥测可能丢失,但不会把
|
||
已经成功的 Component 调用改写成失败。注册、Deployment 和生命周期变更仍在响应前
|
||
完成持久化。
|
||
|
||
`wasmeld-console` 启动时会在同一进程内创建 `wasmeld-runtime`、加载 Wasmtime Engine,
|
||
并重新注册已保存的 Component。Runtime 停止后 Console HTTP 和数据库仍然在线;
|
||
再次启动 Runtime 会重新注册 Component。未部署的版本恢复为 `stopped`;Deployment
|
||
指向的版本会用空初始化配置创建新的 Actor。Actor 的 Store 和线性内存不做快照。
|
||
编译后的本地机器码默认缓存在 `var/wasmeld/component-cache`,相同 Component 在
|
||
Console 或 Runtime 重启后可由 Wasmtime 直接复用;缓存不包含 Actor 内存。
|
||
|
||
## 常驻 Host Driver
|
||
|
||
导出 `wasmeld:resident/actor@0.1.0` 的 Component 启动时,Console 会为它创建独立的
|
||
`ResidentSupervisor`。Supervisor 在专用阻塞线程上独占 `ResidentSession`,避免同步
|
||
Actor mailbox 阻塞 Tokio executor。Timer、TCP Listener 和每条 TCP Stream 由 Tokio
|
||
task 等待外部事件,再通过容量受限的 channel 汇聚到同一个 Supervisor。
|
||
|
||
Host 资源属于平台配置,不由 Component 创建,也不写入 `.wasmpkg`。当前可通过
|
||
`ConsoleConfig::resident_services` 为稳定服务 ID 配置 TCP Listener 和 Timer:
|
||
|
||
```rust
|
||
use std::collections::BTreeMap;
|
||
use wasmeld_console::{
|
||
ConsoleConfig, NetworkScope, ResidentPolicy, ResidentServiceConfig,
|
||
ResidentTcpListenerConfig, ResidentTimerConfig,
|
||
};
|
||
|
||
let mut resident_services = BTreeMap::new();
|
||
resident_services.insert(
|
||
"scheduler".to_owned(),
|
||
ResidentServiceConfig {
|
||
policy: ResidentPolicy {
|
||
tcp_listen: NetworkScope::Loopback,
|
||
..ResidentPolicy::default()
|
||
},
|
||
tcp_listeners: vec![ResidentTcpListenerConfig {
|
||
name: "internal-api".to_owned(),
|
||
bind: "127.0.0.1:9000".parse().unwrap(),
|
||
}],
|
||
timers: vec![ResidentTimerConfig {
|
||
name: "heartbeat".to_owned(),
|
||
initial_delay_ms: 1_000,
|
||
interval_ms: Some(30_000),
|
||
}],
|
||
..ResidentServiceConfig::default()
|
||
},
|
||
);
|
||
let config = ConsoleConfig {
|
||
resident_services,
|
||
..ConsoleConfig::default()
|
||
};
|
||
```
|
||
|
||
`ResidentPolicy` 默认拒绝全部网络地址;配置 Listener 时还必须显式选择 `Loopback`
|
||
或 `Any`。Listener 在 Actor 启动过程中完成策略校验和 bind,任何失败都会回滚刚启动的
|
||
Actor,避免出现“Actor 正在运行但端口未监听”的半启动状态。Listener、Timer 和其它
|
||
顶层资源名称在同一服务内必须唯一,每个 Revision 都会获得独立的 Host handle 和不复用
|
||
资源 ID。
|
||
|
||
TCP Driver 每次最多读取 `limits.resident.max_stream_chunk_bytes`,不会把 socket handle
|
||
交给 Wasm。TCP 字节在 Supervisor 队列满时等待并把背压传递到内核接收缓冲区,不能像
|
||
Timer occurrence 一样丢弃。Component 可通过 WIT 返回 `write-stream`、`close-stream`、
|
||
`pause-stream` 和 `resume-stream`;每条连接的写入/控制队列也是有界的,慢客户端填满
|
||
队列时只关闭该连接,不阻塞同一 Actor 的其它连接。
|
||
|
||
Timer task 只负责等待,队列满时丢弃该次 occurrence,而不是创建无界积压。Component
|
||
返回 `arm-timer` 会替换 Host 初始日程,`cancel-timer` 会取消它;generation 使取消前
|
||
已经排队的迟到事件失效。
|
||
|
||
停止顺序固定为:停止接收新 Driver 事件、停止 accept、投递 `shutdown`、关闭 Stream
|
||
和 Timer task、先释放子 Stream 再释放 Listener、最后停止 Actor。服务重启和 Runtime
|
||
Deployment 恢复都会重新 bind 并创建新 Session,不会复用旧 Revision 的 socket、Timer
|
||
或 Component 内存。UDP 和 Unix Driver 尚未接入,后续继续复用相同的 Supervisor
|
||
序列化边界。
|
||
|
||
## 启动
|
||
|
||
`wasmeld-console` 需要 Rust 1.95 或更高版本;Wasm 组件继续使用项目约定的 Rust 1.90
|
||
工具链构建。
|
||
|
||
```bash
|
||
cargo +stable run -p wasmeld-console
|
||
```
|
||
|
||
Debug 构建只启动 API,不构建或提供 Web 静态文件。前端开发服务器需要单独启动:
|
||
|
||
```bash
|
||
cd crates/wasmeld-console/web
|
||
npm ci
|
||
npm run dev
|
||
```
|
||
|
||
Release 构建会自动使用 `package-lock.json` 构建并嵌入管理面:
|
||
|
||
```bash
|
||
cargo +stable build --release -p wasmeld-console
|
||
./target/release/wasmeld-console
|
||
```
|
||
|
||
发布构建要求 Node.js 22.13+ 和 npm。可用 `NPM` 指定 npm 可执行文件;设置
|
||
`WASMELD_BUILD_WEB=1` 可以在非 Release Cargo 构建中验证嵌入流程。前端结构和 PWA
|
||
边界见 [`web/README.md`](web/README.md)。
|
||
|
||
进程默认启动两个独立 Listener:
|
||
|
||
- 管理 API:`127.0.0.1:8080`
|
||
- Gateway:`0.0.0.0:8081`
|
||
|
||
管理 Router 不会挂载到 Gateway Listener,Gateway 也不暴露注册、Runtime 生命周期和
|
||
WIT Registry 路由。
|
||
|
||
可用环境变量:
|
||
|
||
- `WASMELD_ADDR`
|
||
- `WASMELD_GATEWAY_ADDR`
|
||
- `WASMELD_ARTIFACT_DIR`
|
||
- `WASMELD_DATABASE_PATH`
|
||
- `WASMELD_WIT_REGISTRY_DIR`
|
||
- `WASMELD_ALLOWED_ORIGINS`,多个 Origin 使用逗号分隔
|
||
- `RUST_LOG`
|
||
|
||
## API
|
||
|
||
| 方法 | 路径 | 用途 |
|
||
| --- | --- | --- |
|
||
| `GET` | `/healthz` | 健康检查 |
|
||
| `GET` | `/api/v1/runtime` | Runtime 状态 |
|
||
| `POST` | `/api/v1/runtime/start` | 启动 Runtime 并重新注册受管 Component |
|
||
| `POST` | `/api/v1/runtime/stop` | 停止 Runtime 并释放所有 Actor |
|
||
| `POST` | `/api/v1/runtime/restart` | 重建 Runtime,并重新创建活动 Deployment 的 Actor |
|
||
| `GET` | `/api/v1/services` | 服务版本及精确 Host Capability 列表 |
|
||
| `POST` | `/api/v1/services` | multipart 上传单个 `package` 字段(`.wasmpkg`) |
|
||
| `DELETE` | `/api/v1/services/{id}/{revision}` | 注销非活动 Revision 并删除制品 |
|
||
| `POST` | `/api/v1/services/{id}/{revision}/start` | 启动 Actor |
|
||
| `POST` | `/api/v1/services/{id}/{revision}/stop` | 停止 Actor |
|
||
| `POST` | `/api/v1/services/{id}/{revision}/restart` | 重启 Actor |
|
||
| `POST` | `/api/v1/services/{id}/{revision}/invoke` | Base64 二进制调用 |
|
||
| `GET` | `/api/v1/deployments` | 活动 Deployment 列表 |
|
||
| `GET` | `/api/v1/deployments/{id}` | 查询服务的活动版本 |
|
||
| `POST` | `/api/v1/deployments/{id}/activate` | 启动目标版本并原子切换活动版本 |
|
||
| `GET` | `/api/v1/events` | 最近 256 条持久化事件 |
|
||
| `GET` | `/api/v1/wit/packages` | WIT 包版本列表 |
|
||
| `POST` | `/api/v1/wit/packages` | multipart 发布单个二进制 WIT `package` 字段 |
|
||
| `GET` | `/api/v1/wit/packages/{namespace}/{name}/{version}` | WIT 包元数据 |
|
||
| `GET` | `/api/v1/wit/packages/{namespace}/{name}/{version}/content` | 下载 WIT 包 |
|
||
|
||
活动 Deployment 不能直接注销。开发工具会先完成新 Revision 的编译、注册和切换,
|
||
再注销上一份开发 Revision,因此失败的构建不会影响当前可调用版本。
|
||
|
||
激活已注册版本:
|
||
|
||
```bash
|
||
curl -X POST http://127.0.0.1:8080/api/v1/deployments/echo/activate \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"revision":"0.1.0"}'
|
||
```
|
||
|
||
Gateway 只提供 `GET /healthz` 和
|
||
`POST /v1/services/{id}/invoke`。调用请求与成功响应的
|
||
`Content-Type` 都是 `application/octet-stream`:
|
||
|
||
```bash
|
||
curl http://127.0.0.1:8081/v1/services/echo/invoke \
|
||
-H 'Content-Type: application/octet-stream' \
|
||
--data-binary 'hello'
|
||
```
|
||
|
||
成功响应通过 `X-Wasmeld-Revision` 返回实际路由的版本。Gateway 错误统一返回 JSON;
|
||
未部署为 `404`,Actor 不可用为 `503`,过载为 `429`,执行超时为 `504`。
|
||
|
||
服务响应的 `capabilities` 来自 Component 二进制中的 WIT imports,例如
|
||
`wasmeld:clock/monotonic-clock@0.1.0`。Console 不接受手工能力声明,Runtime 只会链接
|
||
注册表中存在且版本完全匹配的接口。
|
||
|
||
## Host KV
|
||
|
||
`wasmeld:kv/store@0.1.0` 提供 `get`、`set` 和 `delete`。Key 必须是非空 UTF-8,
|
||
最大 256 B;Value 最大 64 KiB。数据库命令通过有界专用工作线程执行,Host 等待时间
|
||
不会超过服务的 `deadline_ms`。v0.1.0 不提供 TTL、遍历、事务或 CAS。
|
||
|
||
组件包格式、开发循环、WIT 依赖、Registry 和本地 `replace` 说明见
|
||
[`wasmeld-package` CLI 文档](../wasmeld-package/README.md)。
|