Namespaces, assemblies, projects and directories, the solution, binaries, images, systemd units, config paths, environment variables, protocol method names, the media type and the raw chunk magic all move to MoShiro/moshiro. Existing data and deployments are not compatible and must be recreated. Also fix the architecture tests after the solution layout change and make the update tests independent of their order.
8.3 KiB
8.3 KiB
Fleet:通用节点框架
实现状态:
Fleet.Update、Fleet.Server.Releases、fleet-updater、fleet-release还只是设计;容器节点的更新目前由宿主机上的moshiro-update脚本和 MoShiro 的ReleaseService完成(见 deployment.md)。其余组件已实现。
名称暂定。Fleet 与业务无关,MoShiro 是它的第一个使用者。
1. 定位
凡是“很多节点连到一个面板”的产品,都要解决同一批问题:节点怎么接入、怎么证明身份、怎么保持连接、怎么可靠上报、怎么接收配置、怎么更新自己。Fleet 把这些做成通用库和工具,产品只需要写业务。
┌──────────────────────────── 产品层(各产品自己写) ─────────────────────────────┐
│ MoShiro: Protocol(Kind/任务) · Measurement(积木) · Agent · Probe · Backend │
│ 其他产品: YourApp.Node · YourApp.Server │
└──────────────────────────────────┬─────────────────────────────────────────────┘
│ 只依赖 Fleet;Fleet 不认识任何产品
┌───────────────────────────────── Fleet ───────────────────────────────────────┐
│ 节点侧(只依赖 BCL,支持 AOT) 服务端侧(ASP.NET Core) │
│ Fleet.Core Result/退避/有界队列 Fleet.Server.Enrollment 注册与审批 │
│ Fleet.Identity 密钥/签名/证书/存储 Fleet.Server.Link wss 网关/在线表 │
│ Fleet.Rpc JSON-RPC/路由/二进制帧 Fleet.Server.Releases 通道与灰度(可选)│
│ Fleet.Link 会话/认证/重连/能力/ Fleet.Server.Sqlite 存储实现(可选)│
│ 发件箱/状态同步/点对点 IFleetStore 存储抽象 │
│ Fleet.Hosting 模块宿主/配置/日志/服务外壳/健康信标 │
│ Fleet.Update 清单/验签/安装/切换/回滚 │
│ 工具:fleet-enroll(注册) fleet-updater(更新) fleet-release(CI 签名) │
└──────────────────────────────────────────────────────────────────────────────┘
2. 设计原则
- 与业务无关:Fleet 中不出现任何产品概念。产品通过扩展点接入:元数据类型、RPC 方法、发件箱消息类型、期望状态文档类型。
scripts/check-deps强制规定backend/fleet/**不能引用MoShiro.*。 - 安全默认值:
- Fleet 永远不提供 exec、shell、文件读写、远程下发代码这类方法;
- 节点只响应本地注册过的方法,本地策略
allow_methods还可以进一步收窄; - 更新必须通过离线密钥签名验证,面板即使被攻破也推不出恶意二进制。
- 权限分离:注册(
fleet-enroll,一次性运行)、常驻业务进程、更新(fleet-updater)是三个独立的进程,使用不同的系统用户,各自只拥有最小权限。 - NativeAOT 优先:节点侧只依赖 BCL;泛型扩展点通过产品传入的
JsonTypeInfo<T>实现序列化,不使用反射。 - 只抽取真正用到的:每项能力都必须同时被 MoShiro 和
samples/EchoNode使用,防止过度设计。
3. 组件
| 组件 | 侧 | 职责 | 详细 |
|---|---|---|---|
Fleet.Core |
两侧 | Result<T>、错误码基类、退避、有界队列、ULID/UUIDv7 |
— |
Fleet.Identity |
两侧 | ECDSA P-256 身份密钥、自签名证书、SPKI 指纹、带用途前缀的签名串、KeyStore(文件权限 0600) |
link.md §2 |
Fleet.Rpc |
两侧 | JSON-RPC 2.0 信封、方法路由、请求与通知、二进制帧、错误映射;与传输方式无关 | link.md §4 |
Fleet.Link |
节点 | 连接服务端(wss)或其他节点(TLS);认证、心跳、重连、能力协商、可靠发件箱、期望状态同步 | link.md |
Fleet.Enrollment.Client |
节点 | 加入令牌注册流程(供 fleet-enroll 使用) |
link.md §3 |
Fleet.Hosting |
节点 | 模块宿主、配置(源生成 JSON)、logfmt 日志、服务外壳(systemd/procd/Windows)、健康信标 | link.md §8 |
Fleet.Update |
节点 | 清单格式、验签、下载校验、多版本目录与原子切换、服务适配器、健康确认、回滚 | update.md |
Fleet.Server.Enrollment |
服务端 | 加入令牌、注册端点、审批与接管 | link.md §3 |
Fleet.Server.Link |
服务端 | wss 端点、签名认证、连接注册表、在线事件、状态下发、收件箱 ACK | link.md |
Fleet.Server.Releases |
服务端(可选) | 托管清单、管理通道、灰度发布、汇总各节点版本 | update.md §6 |
Fleet.Server.Sqlite |
服务端(可选) | IFleetStore 的 SQLite 实现(节点、令牌、公钥、状态版本) |
— |
fleet-enroll |
工具 | 节点注册:生成密钥、调用注册端点、写入配置 | link.md §3 |
fleet-updater |
工具 | 节点更新守护进程,可以同时管理多个产品 | update.md |
fleet-release |
工具 | 在 CI 或离线环境中生成并签名发布清单 | update.md §3 |
4. 目录结构
fleet/ 以后可以原样拆成独立仓库并发布 NuGet 包
├─ Directory.Build.props 导入仓库根目录的 props
├─ src/
│ ├─ Fleet.Core/ Fleet.Identity/ Fleet.Rpc/ Fleet.Link/ Fleet.Hosting/ Fleet.Update/
│ ├─ Fleet.Enrollment.Client/
│ └─ Fleet.Server.Enrollment/ Fleet.Server.Link/ Fleet.Server.Releases/ Fleet.Server.Sqlite/
├─ tools/ fleet-enroll/ fleet-updater/ fleet-release/
├─ samples/ EchoNode/ EchoServer/ 最小示例:不依赖 MoShiro 的第二个使用者
├─ tests/
└─ docs → 仓库 docs/fleet/
5. 演进策略
- 现阶段(已确认,暂不独立):放在 MoShiro 仓库的
backend/fleet/目录中,通过项目引用使用,一处修改两边同时编译。 - 拆分条件:公开 API 连续两个里程碑没有破坏性变更,并且
samples能覆盖全部能力。满足后拆成独立仓库,按 semver 发布 NuGet 包,MoShiro 改为包引用。 - 公开 API 审查:
backend/fleet/src/*的 public 类型使用PublicAPI.Shipped.txt/PublicAPI.Unshipped.txt跟踪(使用Microsoft.CodeAnalysis.PublicApiAnalyzers,只在构建期生效),任何变更都会在 PR 中显示出来。
6. 产品接入一览
节点侧:
var identity = NodeIdentity.Load(config.IdentityDir); // 由 fleet-enroll 生成
var link = FleetLink.ToServer(config.Server, identity, MyRpcContext.Default)
.WithCapabilities(["myapp.v1"])
.WithState<MyDesired>(MyRpcContext.Default.MyDesired, reconciler) // 期望状态同步(可选)
.WithOutbox<MyReport>(MyReportCodec.Instance, maxBytes: 16 << 20) // 可靠上报(可选)
.On<DoThingParams, DoThingResult>("myapp.do_thing", HandleAsync); // 业务方法
var host = new ModuleHost(log).Add(link).Add(myModules);
await ServiceShell.RunAsync(host, HealthBeacon.From(config)); // 同时提供更新所需的健康确认
服务端:
builder.Services.AddFleetServer(o => o.UseStore<SqliteFleetStore>())
.AddState<MyDesired>(desiredProvider) // 为每个节点计算期望状态
.AddInbox<MyReport>(reportHandler) // 持久化之后再 ACK
.OnNodeEvent(presenceHandler); // 上线、下线、版本变化
app.MapFleetEnrollment("/fleet/enroll");
app.MapFleetLink("/fleet/rpc");
app.MapFleetReleases("/fleet/releases"); // 可选