ci/woodpecker/push/main-amd64-build Pipeline was successful
Namespaces, assemblies, solution, binaries, images, systemd units, config paths, environment variables, protocol methods and the Linux user are all renamed. Breaking: existing databases, node identities and installed units must be recreated.
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"); // 可选