proxy-pool/docs/api/control-plane.md
youfak 6766097ea7
Some checks are pending
ci / proto (push) Waiting to run
ci / test (ubuntu-latest) (push) Waiting to run
ci / test (windows-latest) (push) Waiting to run
ci / race (push) Waiting to run
ci / integration (push) Waiting to run
feat: report gateway proxy outcomes
2026-07-31 17:36:17 +08:00

196 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Control Plane gRPC API
## 1. 契约范围
Proto 源文件位于 `api/proto/controlplane/v1/controlplane.proto`,包含两项
内部服务:
- `WorkerControlPlane`Worker 注册、Snapshot/Delta 分发、ACK、运行态与结果
批量上报。
- `CheckerControlPlane`:健康检查任务流和 Observation 批量上报。
该协议不承载 Client 的独占提取,也没有 extraction lease/release。Proxy 的
`AVAILABLE -> EXTRACTED` 只在 Controller 调用的 Redis 原子操作中完成。
## 当前实现状态
Controller 已实现并验证 `RegisterWorker`、`AcknowledgeSnapshot` 和
`ReportRuntime` 的一元 RPC。Register 创建带 Redis 服务端 TTL 的 sessionACK 只
接受 Controller 已签发的 `(version, ownership_epoch, checksum)`Runtime 的空
`counters` 是完整稀疏替换,也续期 session。负向 ACK 会关闭该 session 的 Runtime
写入栅栏,直到收到新的正向 ACK延迟的旧报告不能重新开启它。
生产配置使用 mTLS并将叶子证书 SPIFFE URI 约束为
`spiffe://<trust-domain>/<environment>/worker/<worker-id>`;仅经配置校验的回环
监听允许明文 fixture 模式。单消息大小、并发流数和 gRPC keepalive 策略由
`controlPlane` 配置限定。
`WatchSnapshots` 已在 Register 后发送与当前 ownership epoch 对应的基础完整快照,
Gateway 校验后 ACK 并开始 Runtime 心跳。Controller 会从 Redis 的有界 Worker ownership
索引构建已归属 Proxy 内容,并将租约到期收紧到 Proxy 的 `usable_until`。Proxy 引用的
凭据材料按 `secret_ref + credential_version` 去重,随完整 Snapshot 经 mTLS 下发,仅保留在
Gateway 当前内存 View。完整 Snapshot
已从配置原始顺序和 Admin 当前状态合成 Gateway Routing并与 Proxy 一起纳入 checksum
Gateway 已将该 payload 编译并原子发布到与 Proxy 相同版本的本地 View动态 Router 只匹配
当前未过期 View派发器已按五种策略从该 View 选择上游,且在 Proxy 容量耗尽时只在该
View 的其余候选中回退。`wait_timeout` 随 `on_unavailable=WAIT` 下发并在 Gateway 作为有界
本地容量等待使用;`DIRECT` 仍先经过 TargetPolicy 再建立 HTTP/CONNECT 直连。`proxy-gateway`
已装配 Register/Watch/ACK/Runtime/Outcome 会话、HTTP 代理监听和 Snapshot 就绪探针;控制面中断时
保持进程运行并以有界退避重连,未取得有效 Snapshot 的 Worker 不会 Ready。`ReportOutcomes`
已实现为客户端流Controller 校验流内固定的 Worker/Session 身份,对每个批次以
`(session_id, sequence, SHA-256)` 建立 Redis 原子栅栏,并只返回最后确认的序列。
Redis 只保留每个当前会话的一条序列和摘要;原始 Outcome、代理明细与逐请求记录均不写入
Redis 或 PostgreSQL。Gateway 只将结果写入本地有界队列,队列满时丢弃样本,不等待控制面
或存储。Checker 闭环尚未实现;`100,000 QPS` 仍是未验证的设计目标。
`WatchSnapshots` 建立时校验当前 session每次签发快照引用时也把 `session_id`
交给 Redis 原子校验。重复 Register 会同时清除旧 Runtime 和已签发引用,因此迟到的
旧 Stream 既不能覆盖新 session 的引用,也不会向旧连接发送未获授权的快照。
## 2. Worker 会话
```mermaid
sequenceDiagram
participant W as Gateway Worker
participant C as Controller
W->>C: RegisterWorker(worker, instance, zone)
C-->>W: session + ownershipEpoch + maxStaleAge
W->>C: WatchSnapshots(lastVersion, checksum)
C-->>W: full WorkerSnapshot
W->>W: validate + build immutable snapshot
W->>W: atomic swap
W->>C: AcknowledgeSnapshot(version, epoch, checksum)
loop bounded interval
W->>C: ReportRuntime(active, reserved, draining)
W->>C: ReportOutcomes(batch sequence)
end
```
`worker_id` 是逻辑节点,`instance_id` 区分进程重启,`session_id` 防止旧进程
继续上报。所有权 `epoch` 小于 Controller 当前值的数据必须拒绝。
同一 `worker_id` 重注册会替换 session并清除旧 Runtime 与已签发 Snapshot
Reference。旧 Stream 即使在替换后仍收到上游更新,其签发操作也会以
`FailedPrecondition` 结束,不能影响新 session 的 ACK 基线。
`ReportRuntimeRequest.report_sequence` 在当前 `session_id` 内严格单调递增。
相同序号只允许内容完全相同的幂等重放;较小序号或相同序号的不同内容必须
拒绝。`observed_at` 只用于观测,不作为乱序判定依据,运行态 TTL 统一使用
Controller 侧 Redis 服务端时间。session 同时保存 Controller 已接受的
`snapshot_version/ownership_epoch`;运行态报告必须与该 ACK 上界完全一致,
Worker 自报的超前版本或 epoch 也必须拒绝。
运行态上报是完整稀疏替换:只携带 Active/Reserved 非零的 Proxy空列表表示
当前会话全部归零。Controller 只有在 Worker session、报告 TTL、Proxy ownership
和 ownership epoch 同时有效时才使用计数;报告缺失、过期或不一致时按零可用
容量 fail-closed不能把未知计数解释成空闲容量。
## 3. Snapshot 与 Delta
完整 Snapshot 包含:
- 单调 `version`、`ownership_epoch`、生成时间和有效期。
- 对该 Worker 可见的有序 Routing。
- 仅归该 Worker 所有的 Proxy、每个 Proxy 的容量、硬过期时间 `expires_at`
和停止新分配的 `usable_until`
- 内容 `checksum`
`usable_until = expires_at - allocationSafetyMargin`。Worker 必须以
`usable_until` 作为最后可分配时刻;达到该时间后即使尚未到 `expires_at`
也不得再为新请求选择该 Proxy。
Gateway 接收完整快照时必须拒绝缺失、格式错误或已到期的 `valid_until`,并将其
保存在本地不可变视图。该整体期限到达后,调度直接按无候选处理,不再使用旧视图
发起新的上游连接,也不查询 Redis 或 PostgreSQL 补偿。
Controller 只会下发尚未到期的完整快照,并在最近一次成功下发快照的
`valid_until` 到达时结束 `WatchSnapshots` 流。Gateway 的 `SessionSupervisor` 会在流
结束或可恢复控制面错误后按带 jitter 的有界退避重建 Register/Watch 会话;参数、认证
和协议不兼容错误直接返回。新快照通过校验并原子替换前,旧视图仍按其整体有效期
fail-closed。
Delta 声明 `base_version`。Worker 只有在本地版本恰好等于 base 且 checksum
验证成功时才能应用;否则丢弃 Delta 并请求完整 Snapshot。构建在后台完成
热路径只读取一次原子指针。
超过 `max_stale_age` 仍未取得有效快照时Worker 停止接收新流量并排空已有
请求。控制面中断不能让 Worker 查询 PostgreSQL 或 Redis 补偿热路径。
## 4. 所有权与 Drain
同一 Proxy 同时只归一个 Worker。Controller 回收用于独占提取的 Proxy 时:
1. 新 Snapshot 标记或移除该 Proxy使 Worker 停止新预留。
2. Worker 上报 `draining=true` 以及 Active/Reserved。
3. 两个计数都归零后 Controller 清除所有权。
4. 无所有权 Proxy 才能进入 Distribution 的 Redis 原子提取操作。
Worker 崩溃时必须等待所有权 epoch/有效期失效后再转移避免双主。Proto 中
`ReportRuntimeResponse.revoke_proxy_ids` 是加速 Drain 的控制信号,不绕过
Snapshot 版本和 Redis 中的权威所有权状态。
## 5. Outcome 上报
Outcome 按 Worker 单调 `sequence` 批量上报。Controller 返回已接受的最大序号,
从而支持有限重试和去重。阶段区分:
- `DIAL`:连接 Proxy 地址失败。
- `PROXY_HANDSHAKE`HTTP CONNECT 或 SOCKS 握手失败。
- `RESPONSE_HEADERS`:目标响应头前失败。
- `TUNNEL`:隧道建立后结束或失败。
Outcome 是 Observation不直接让 Worker 修改 Redis 活动池状态,也不创建
PostgreSQL Proxy 明细。Gateway 将每次尝试写入进程内有界队列,当前默认容量为
`65536` 个事件,单批最多为 `min(512, maxRuntimeCounters)`;一次 ReportOutcomes RPC
最多合并 16 个已就绪批次。队列满时只递增本地丢弃计数,不反压 Gateway 热路径。
同一 session 内Controller 仅接受递增 `sequence`;相同序列且摘要一致视为幂等
重放,相同序列且摘要不同返回 `AlreadyExists`,较小序列返回 `Aborted`。Gateway 在
未收到确认时保留并重发完全相同的批次。暂态传输错误在当前 session 内按有上限的
退避重试;会话栅栏错误交由 session supervisor 重建 session。栅栏随 session 替换
或过期清理,因此长期在线 Worker 不会因独立 Outcome TTL 接受旧序列。
## 6. Checker 任务
Checker 注册自身最大并发与支持层级Controller 发送有 deadline 的任务:
- `BASIC`:基础连通和协议握手。
- `EGRESS`:出口身份与匿名性。
- `TARGET`:针对 Routing/目标组的可达性。
Checker 只返回 `HealthObservation`。Controller reducer 按 Proxy、检查层级和
Routing 决定 AVAILABLE、SUSPECT 或 UNHEALTHY并更新 Redis 活动池,避免
多个 Checker 并发写状态。
## 7. 兼容与演进
- Proto 字段号一旦发布不得复用。
- 删除字段使用 `reserved` 保留名称和编号。
- 新枚举值必须让旧接收方按 UNSPECIFIED/拒绝策略处理。
- Worker 注册携带 `supported_protocol_version`,不兼容时注册失败而不是静默
降级。
- Stream 断开后使用带 jitter 的有界指数退避,禁止紧密重连。
## 8. 传输安全
集群环境使用 mTLS证书身份绑定 Worker/Checker 类型和环境。服务端校验
消息中的逻辑 ID 与证书授权一致,设置单消息大小、流持续时间、并发 Stream
和上报批次上限。`secret_ref` 是受控引用;完整 Snapshot 的 `credentials` 在 mTLS 会话中
携带引用对应材料Controller 和 Gateway 仅在内存处理,禁止写入 Redis/PostgreSQL、日志或指标。
## 9. Gateway 启动参数
Gateway 不复用 `controlPlane.listen` 作为客户端地址。`listen` 是 Controller 的
服务端绑定地址;每个 Gateway 必须显式提供以下独立参数或同名环境变量:
- `-control-plane` / `PROXY_POOL_CONTROL_PLANE_ADDRESS`Controller 的可拨号地址。
- `-cluster-id` / `PROXY_POOL_CLUSTER_ID`:快照所属集群。
- `-worker-id` / `PROXY_POOL_WORKER_ID`:唯一逻辑 Worker。
- `-instance-id` / `PROXY_POOL_INSTANCE_ID`:唯一进程实例。
- `-zone` / `PROXY_POOL_ZONE`:实例可用区。
`controlPlane.tls.mode=mtls`Gateway 使用 `controlPlane.gatewayTLS` 中独立的
客户端证书、私钥和 Controller CA 发起 TLS 1.3 连接;证书必须符合 Controller 的
SPIFFE Worker 身份校验。
`disabled` 仅接受回环控制面地址,供本地 fixture 使用。