docs: merge the Fleet docs into backend/fleet/README.md, add TODO.md, unify the Backend compose, fix stale references [CI SKIP]

This commit is contained in:
justme committed 2026-10-07 03:36:34 +08:00
1 parent a286a453f4
commit c974a7dddb
24 files changed
+218 -525

No files matched your search

+33 -116
View File
@@ -4,7 +4,7 @@
1. **三个独立项目加一个前端**:Probe、Agent、Backend 各自拥有解决方案,可以单独构建、测试和发布;Web 是独立的 Vite 项目(`web/`)。
2. **通用能力只写一次**:TCP/UDP/TLS/ICMP 采集积木(类似 iperf3 的能力,但只记录原始数据)放在 `MoShiro.Measurement` 中,Probe 和 Agent 共用;采集方法(Kind)由积木组合声明而成。
3. **节点机制通用化**:注册、身份、连接、RPC、可靠上报、状态同步、宿主、更新,由与业务无关的 **Fleet** 框架提供(见 [fleet/overview.md](fleet/overview.md))。MoShiro 只写业务,其他产品也可以复用 Fleet。
3. **节点机制通用化**:注册、身份、连接、RPC、可靠上报、状态同步、宿主,由与业务无关的 **Fleet** 框架提供(见 [../backend/fleet/README.md](../backend/fleet/README.md))。MoShiro 只写业务,其他产品也可以复用 Fleet。
4. **改一处,整条链路受益**:协议、原始数据格式、测试方案、指标、错误码、版本号、编译设置、代码风格都只有**一个真源**。改动之后,下游要么自动更新,要么在编译期或 CI 中直接报错。
5. **模块化**:Probe/Agent 由可插拔的模块组成;Backend 按层分成多个项目,层之间的依赖在编译期强制。
6. **Probe/Agent 轻量**:运行时只依赖 BCL 和仓库内的库,不引用任何 NuGet 包。
@@ -13,24 +13,23 @@
```text
moshiro/
├─ README.md .gitignore
├─ docs/ docs/fleet/
├─ scripts/ # publish / package / smoke / stability 等脚本(check-deps 等检查也放这里)
├─ README.md TODO.md AGENTS.md .gitignore
├─ docs/
├─ scripts/ # publish / package / smoke / stability 等脚本
├─ packaging/ # install.sh、systemd 单元、OpenWrt 文件
├─ web/ # 前端:Vite + React + TypeScript(自写),只依赖 backend/openapi/v1.json
│
├─ global.json Directory.Packages.props MoShiro.slnx # SDK、NuGet 版本和全部 .NET 项目入口
├─ .editorconfig BannedSymbols.txt # 风格与禁用 API(构建期强制)
├─ BannedSymbols.Node.txt # 仅用于常驻节点进程:纯采集边界
│
├─ ci/MoShiro.Tar/ # CI 归档工具,也加入根解决方案
└─ backend/ # 服务和节点 C# 代码(构建产物在 backend/artifacts/)
├─ Directory.Build.props MoShiro.slnx # 后端编译设置和后端目录解决方案
├─ .editorconfig BannedSymbols.txt # 风格与禁用 API(构建期强制)
├─ BannedSymbols.Node.txt # 仅用于常驻节点进程:纯采集边界
├─ fleet/ # 通用节点框架(不认识 MoShiro,以后可以拆成独立仓库)
│ ├─ src/ Fleet.Core Fleet.Identity Fleet.Rpc Fleet.Link Fleet.Hosting Fleet.Update
│ │ Fleet.Enrollment.Client Fleet.Server.{Enrollment,Link,Releases,Sqlite}
│ ├─ tools/ fleet-enroll fleet-updater fleet-release
│ ├─ samples/ EchoNode EchoServer
│ ├─ src/ Fleet.Core Fleet.Identity Fleet.Rpc Fleet.Link Fleet.Hosting
│ │ Fleet.Enrollment.Client Fleet.Server.{Abstractions,Enrollment,Link}
│ ├─ tools/ fleet-enroll
│ └─ tests/
├─ shared/ # MoShiro 自己的共享库
│ ├─ MoShiro.Protocol/ # 契约真源:Kind/Plan 目录、任务集、moshiro.* 方法、UDP 包、RawChunk、错误码
@@ -50,7 +49,7 @@ moshiro/
└─ MoShiro.Backend.slnx
```
节点机器上的发布物是三个独立进程,使用不同的系统用户:`moshiro-agent`(或 `moshiro-probe`)、`fleet-enroll`(一次性运行)、`fleet-updater`(常驻或定时运行)。
节点机器上的发布物是两个独立进程,注册和采集分开:`fleet-enroll`(一次性运行)和 `moshiro-agent`(或 `moshiro-probe`,常驻)。容器节点的更新由宿主机上的 `moshiro-update` 完成,见 [deployment.md](deployment.md)。
## 3. 依赖关系
@@ -81,7 +80,6 @@ Probe, Agent ──> Measurement ──> MoShiro.Protocol ──> Fleet.Core /
Probe, Agent ──> Fleet.Link / Fleet.Hosting / Fleet.Identity
Backend.* ──> MoShiro.Protocol + Fleet.Server.* (不引用 Measurement)
fleet-enroll ──> Fleet.Enrollment.Client ──> Fleet.Identity / Core
fleet-updater ──> Fleet.Update ──> Fleet.Core (不引用 Link,也不读取节点身份)
Web ──> openapi/v1.json
```
@@ -91,42 +89,30 @@ Web ──> openapi/v1.json
| `Fleet.Server.*` | ASP.NET Core、`Microsoft.Data.Sqlite`(仅 Sqlite 项目)、节点侧 Fleet 库 | 任何 `MoShiro.*` |
| `MoShiro.Protocol` | Fleet.Core、Fleet.Rpc | 任何 I/O、套接字、日志 |
| `MoShiro.Measurement` | MoShiro.Protocol、Fleet.Core | NuGet;日志(只通过计数接收器上报);读取配置;统计或解释逻辑 |
| Probe / Agent | 上述库,以及 Fleet.Link / Hosting / Identity | NuGet 运行时包;彼此互相引用;Fleet.Update;Fleet.Enrollment.Client |
| Probe / Agent | 上述库,以及 Fleet.Link / Hosting / Identity | NuGet 运行时包;彼此互相引用;Fleet.Enrollment.Client |
| Backend.* | MoShiro.Protocol、Fleet.Server.*,以及第 5.2 节列出的包 | Measurement、EF Core 等依赖反射的库 |
分析器类包(`PrivateAssets="all"`)不受限制。`scripts/check-deps` 在 CI 中检查上述规则。
分析器类包(`PrivateAssets="all"`)不受限制。`ArchitectureTests` 在构建时检查上述规则。
## 4. 共享库
### 4.1 Fleet(通用节点框架)
组件、扩展点与接入方式见 [fleet/overview.md](fleet/overview.md),连接细节见 [fleet/link.md](fleet/link.md),更新见 [fleet/update.md](fleet/update.md)。
> **实现状态**:`Fleet.Core`、`Fleet.Identity`、`Fleet.Rpc`、`Fleet.Link`、`Fleet.Hosting`、`Fleet.Enrollment.Client`、`Fleet.Server.*`(Abstractions、Enrollment、Link)和 `fleet-enroll` 已实现。`Fleet.Update`、`Fleet.Server.Releases`、`fleet-updater`、`fleet-release` 还只是设计(纯二进制方式的更新);容器节点的更新是宿主机上的 `moshiro-update` 脚本和面板的 `ReleaseService`,见 [deployment.md](deployment.md)。原来的 `MoShiro.Common` 和 `MoShiro.NodeHost` 已经并入 Fleet:
| 原位置 | 现位置 |
| --- | --- |
| `MoShiro.Common`(Result、缓冲区、VarInt、有界队列、重放窗口、退避、ID、令牌哈希) | `Fleet.Core` |
| `MoShiro.NodeHost`(模块宿主、日志、配置、ConfigStore、服务外壳) | `Fleet.Hosting` |
| 节点密钥与签名 | `Fleet.Identity` |
| JSON-RPC 信封与路由 | `Fleet.Rpc` |
| Agent 注册 | `Fleet.Enrollment.Client` + `fleet-enroll` |
组件、扩展点和协议细节见 [../backend/fleet/README.md](../backend/fleet/README.md)。
### 4.2 MoShiro.Protocol(MoShiro 的契约真源)
```text
MoShiro.Protocol/
├─ ProtocolVersion ProtocolLimits MoShiroErrorCode(在 Fleet 错误码基础上扩展)
├─ ProtocolLimits MoShiroErrors(在 Fleet 错误码基础上扩展)
├─ Ids/ AgentId(= Fleet NodeId)、ProbeId、TaskId、TestId、ChunkId、StreamId
├─ Catalog/
│ ├─ Kinds/ KindSpec 声明(积木组合 + ParamSpec + 记录列 + 预算)
│ ├─ Plans/ 多阶段测试方案
│ └─ ParamSpec 参数定义(类型、默认值、上下限),节点与 Backend 共用同一套校验
├─ Tasks/ TaskSet(Fleet 期望状态的文档类型)、调和结果
├─ Rpc/ moshiro.* 方法名、params/result、二进制通道号(从 16 开始)
├─ Wire/ UDP 包、数据连接首帧
├─ Raw/ 自描述 RawChunk:头部、列类型、列编解码、Brotli、crc32c
└─ Json/ AgentRpcContext、ProbeRpcContext(按信道拆分,便于裁剪)
└─ Raw/ 自描述 RawChunk:头部、列类型、列编解码、Brotli、crc32c
```
累加器、直方图等统计类型**不在 Protocol 中**,只属于 `Backend.Analysis`。
@@ -198,7 +184,7 @@ AgentApp Probe(ProbeApp)
- **Api 不能引用 Storage**(编译期保证);Api 和 Gateway 只调用 Application。
- Application 返回 `Result<T>`。**错误码到 HTTP 状态的映射只在 Api 层做一次**(`ErrorCodeHttpMap`);到 JSON-RPC 错误的映射由 Fleet.Rpc 统一完成。
- Api 层按调用方分目录:`Public/`、`Admin/`,公共部分放在 `Common/`(分页、ETag、认证过滤器、ProblemFactory、`EndpointConventions`)。每类资源一个端点文件,新增资源时在 `ApiModule.Map` 中加一行。
- 注册端点、签名认证、连接注册表、状态推送、收件箱 ACK、发布通道都来自 Fleet(`MapFleetEnrollment`、`MapFleetLink`、`MapFleetReleases`)。MoShiro 只提供:
- 注册端点、签名认证、连接注册表、状态推送、收件箱 ACK 都来自 Fleet(`MapFleetEnrollment`、`MapFleetLink`)。MoShiro 只提供:
- `IFleetStore` 的实现;
- `TaskSet` 的状态提供者;
- `RawChunk` 的收件箱;
@@ -211,86 +197,18 @@ Backend.Analysis 只依赖 MoShiro.Protocol 和 Fleet.Core,没有 I/O,可以
Backend 允许的包:ASP.NET Core 共享框架、`Microsoft.Data.Sqlite`、`Microsoft.AspNetCore.OpenApi`、`Microsoft.Extensions.ApiDescription.Server`(仅构建期使用)。
## 6. “改一处,整条链路受益”的机制
## 6. 编译设置
| 改动 | 真源 | 链路上的效果 |
| --- | --- | --- |
| 采集密度(频率、包大小、采样间隔) | 任务参数(API) | 经 `fleet.state.sync` 下发后立即生效,无需发布 |
| 新增采集方法(现有积木可以组合) | `Protocol/Catalog/Kinds` 中加 1 条 | 节点执行、Recorder 自动生成列、Backend 校验和目录、Analysis 按列名读取 |
| 新增采集方法 | 在 `Kinds` 里加声明(含流的 `Driver`/`Key`),在 `Measurement/Blocks` 里实现它的拨号方和监听方并登记到 `BlockCatalog` | Probe 和 Agent 同时获得,合并不需要写代码 |
| 原始数据格式 | `Protocol/Raw`(自描述) | 新增列不需要升级版本;四处使用同一份编解码器 |
| 新增统计指标 | `Analysis/Metrics` 中加 1 行 `MetricDef` | 存储、降采样、API、SSE、目录全部自动处理;回填 30 天历史 |
| 服务评分 | `Analysis/Models` | 查询时计算,立即对全部历史生效 |
| 新增 RPC 能力 | `MoShiro.Protocol/Rpc` + 组装处加 1 行 | 通过 Fleet 的 capabilities 协商,旧节点不受影响 |
| 注册、认证、重连、发件箱、状态同步 | `backend/fleet/` | MoShiro 和所有其他产品同时受益 |
| 更新机制 | `Fleet.Update` + `fleet-updater` | 所有产品共用;产品只提供发布清单 |
| REST DTO 与端点 | `Backend.Api` | 生成 OpenAPI,CI 检查 diff,前端的 TS 类型随之更新 |
| 错误码 | Fleet 错误码 + `MoShiroErrorCode` | REST、JSON-RPC、OpenAPI、TS 全部一致 |
| 版本、编译设置、风格、禁用 API | `backend/` 下的 props、`.editorconfig`、`BannedSymbols*.txt` | 所有项目 |
- 所有 .NET 项目共用 `backend/Directory.Build.props`(目标框架、可空引用、警告当错误、最新分析器、确定性构建、禁用 API 清单 `BannedSymbols.txt`、版本号)和根目录的 `Directory.Packages.props`(NuGet 版本、SQLite 源码版本),SDK 版本在根目录的 `global.json`。
- `backend/probe/`、`backend/agent/` 另有 `Directory.Build.props`:NativeAOT、体积优先、关闭 EventSource、调试支持和 Server/Concurrent GC 等,具体开关以文件为准;保留 StackTraceSupport,线上崩溃需要可读的调用栈。
- `BannedSymbols.Node.txt` 适用于常驻节点进程(Probe、Agent,以及 Fleet 的节点侧运行库),主要条目:
- `System.Diagnostics.Process`;
- `System.Reflection.Assembly.Load*`;
- `System.Runtime.InteropServices.NativeLibrary.Load*`;
- `System.IO.File.Write*`、`File.Create`、`FileStream` 构造函数(只有 `Fleet.Hosting.ConfigStore` 可以通过 `#pragma` 豁免);
- `System.Net.Http.HttpClient`(没有豁免:注册在 `fleet-enroll` 里,它是独立的一次性工具,允许 HTTP 和写入身份目录)。
## 7. 编译设置
`backend/Directory.Build.props`(所有项目):
```xml
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisLevel>latest-recommended</AnalysisLevel>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<IsAotCompatible>true</IsAotCompatible>
<InvariantGlobalization>true</InvariantGlobalization>
<UseArtifactsOutput>true</UseArtifactsOutput>
<Deterministic>true</Deterministic>
<Version>0.1.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" PrivateAssets="all" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)BannedSymbols.txt" />
</ItemGroup>
</Project>
```
`backend/fleet/src/*` 另外启用 `Microsoft.CodeAnalysis.PublicApiAnalyzers`(`PublicAPI.Shipped.txt` / `PublicAPI.Unshipped.txt`),公开 API 的任何变更都会在 PR 中显示出来。
`backend/probe/`、`backend/agent/` 下的 `Directory.Build.props`(先导入根 props,再追加轻量设置):
```xml
<PropertyGroup>
<PublishAot>true</PublishAot>
<OptimizationPreference>Size</OptimizationPreference>
<UseSystemResourceKeys>true</UseSystemResourceKeys>
<EventSourceSupport>false</EventSourceSupport>
<DebuggerSupport>false</DebuggerSupport>
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
<ServerGarbageCollection>false</ServerGarbageCollection>
<ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>
<!-- 保留 StackTraceSupport:线上崩溃需要可读的调用栈 -->
</PropertyGroup>
<ItemGroup>
<!-- 纯采集边界:编译期禁止 -->
<AdditionalFiles Include="$(MSBuildThisFileDirectory)../BannedSymbols.Node.txt" />
</ItemGroup>
```
`BannedSymbols.Node.txt` 适用于常驻节点进程(Probe、Agent,以及 Fleet 的节点侧运行库),主要条目:
- `System.Diagnostics.Process`;
- `System.Reflection.Assembly.Load*`;
- `System.Runtime.InteropServices.NativeLibrary.Load*`;
- `System.IO.File.Write*`、`File.Create`、`FileStream` 构造函数(只有 `Fleet.Hosting.ConfigStore` 可以通过 `#pragma` 豁免);
- `System.Net.Http.HttpClient`(**不再有豁免**:注册已经移到 `fleet-enroll`)。
`fleet-enroll` 和 `fleet-updater` 是独立的工具,使用各自的禁用清单:
- `fleet-updater` 允许写安装目录、调用服务管理接口,但禁止读取节点身份目录;
- `fleet-enroll` 允许 HTTP,允许写入身份目录。
## 8. 轻量预算
## 7. 轻量预算
M0 阶段先测出基线,之后作为 CI 的回归门槛(超出 10% 即失败):
@@ -299,7 +217,6 @@ M0 阶段先测出基线,之后作为 CI 的回归门槛(超出 10% 即失
| Probe | ≤ 8 MB(Linux/OpenWrt,静态链接了 .NET 的加密、压缩、网络库;Windows 实测 5.6 MB) | ≤ 32 MB(Linux 实测约 21 MB) | 同时连接 15 个 Agent 并运行默认模板时 CPU < 2%;`hy2_ladder` 100 Mbps 时 CPU < 50%(单核) |
| Agent | ≤ 10 MB(Linux 实测 8.4 MB) | ≤ 48 MB(Linux 实测约 23 MB) | 运行默认模板时 ≤ 64 MB;同时跑一个 200 Mbps 测试时 CPU < 60%(1 vCPU) |
| fleet-enroll | ≤ 4 MB | — | 一次性运行 |
| fleet-updater | ≤ 4 MB | ≤ 16 MB | 空闲时每小时轮询一次 |
| Backend | 不设硬性限制 | ≤ 150 MB | — |
为满足预算,实现上需要遵守:
@@ -309,7 +226,7 @@ M0 阶段先测出基线,之后作为 CI 的回归门槛(超出 10% 即失
- stream 和 bulk 发送使用预先填充的缓冲区,只统计计数,不复制数据;
- 不使用 LINQ。
## 9. 发布
## 8. 发布
- 目标平台:
@@ -318,16 +235,16 @@ M0 阶段先测出基线,之后作为 CI 的回归门槛(超出 10% 即失
| Agent | `linux-x64`、`linux-arm64` | VPS |
| Probe | `linux-x64`、`linux-arm64`、`win-x64` | 普通 PC 或服务器 |
| Probe(OpenWrt) | `linux-musl-x64`、`linux-musl-arm64` | 正式支持;`linux-musl-arm`(armv7)视 M0 的工具链验证结果决定是否支持 |
| fleet-enroll / fleet-updater | 与所在节点相同 | |
| fleet-enroll | 与所在节点相同 | |
| Backend | `linux-x64`、`linux-arm64`、`win-x64` | |
- **MIPS 不支持**(NativeAOT 没有 MIPS 目标)。使用 MIPS 路由器(例如 MT7621)时,把 Probe 运行在局域网内的其他设备上,比如 NAS、树莓派或 PC,测量结果同样代表这条家宽。
- NativeAOT 需要目标平台的原生工具链:musl 目标在 Alpine 容器中构建,arm64 使用交叉编译镜像。CI 矩阵为 Windows、Linux glibc、Linux musl。
- `scripts/publish.(sh|ps1) <probe|agent|backend|fleet-enroll|fleet-updater> <rid>` 输出到 `artifacts/publish/<project>/<rid>/`。
- 发布时由 `fleet-release` 在 CI 中生成并签名清单(见 [fleet/update.md](fleet/update.md)),节点通过 `fleet-updater` 更新。
- `scripts/publish.sh <probe|agent|backend|fleet-enroll> <rid>` 输出到 `backend/artifacts/publish/<project>/<rid>/`。
- 容器镜像由 CI 构建并对镜像 digest 签名,节点的宿主机更新器验签后更新,见 [deployment.md](deployment.md);纯二进制节点的更新见 [TODO.md](../TODO.md)。
- Linux 上 TLS 依赖系统 OpenSSL(`libssl.so.3`)。
### 9.1 OpenWrt 上的 Probe
### 8.1 OpenWrt 上的 Probe
| 事项 | 做法 |
| --- | --- |
@@ -339,8 +256,8 @@ M0 阶段先测出基线,之后作为 CI 的回归门槛(超出 10% 即失
| 内存 | 预算 ≤ 32 MB,适合 ≥ 256 MB 内存的路由器;在本地配置中限制重负载测试的码率(`limits.max_test_mbps`),防止路由器 CPU 成为瓶颈,反过来影响测量结果 |
| 测量偏差 | Probe 运行在网关上时,自己没有 NAT;一旦路由器 CPU 饱和,结果就会失真。因此 Probe 在 `moshiro.event` 中上报自身的 CPU 繁忙标记(仅进程级别),分析层据此标注置信说明 |
| 打包 | 提供 `tar.gz` 和 procd 脚本;包格式(ipk 或 apk)按目标 OpenWrt 版本提供 |
| 更新 | `fleet-updater` 使用 procd 适配器;`keep_versions = 1`,下载前检查剩余空间,避免写满闪存 |
| 更新 | 纯二进制节点的更新器还没有实现([TODO.md](../TODO.md));实现时 `keep_versions = 1`,下载前检查剩余空间,避免写满闪存 |
## 10. Web
## 9. Web
`web/` 是 npm workspaces:`display`(展示端,匿名可读)、`admin`(管理端)、`shared`(API 客户端、图表、组件、主题)。Vite 8、React 19、TypeScript,图表用 uPlot;唯一的契约是 `backend/openapi/v1.json`,用 `openapi-typescript` 生成类型(`npm run gen:api`)。颜色只能写在 `shared/theme/tokens.css`,由 stylelint 强制。详见 [web/README.md](../web/README.md)。