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