proxy-pool/docs/api/control-plane.md
youfak 04c67d733c
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
fix: preserve outcomes across session replacement
2026-07-31 22:26:30 +08:00

13 KiB
Raw Blame History

Control Plane gRPC API

1. 契约范围

Proto 源文件位于 api/proto/controlplane/v1/controlplane.proto,包含两项 内部服务:

  • WorkerControlPlaneWorker 注册、Snapshot/Delta 分发、ACK、运行态与结果 批量上报。
  • CheckerControlPlane:健康检查任务流和 Observation 批量上报。

该协议不承载 Client 的独占提取,也没有 extraction lease/release。Proxy 的 AVAILABLE -> EXTRACTED 只在 Controller 调用的 Redis 原子操作中完成。

当前实现状态

Controller 已实现并验证 RegisterWorkerAcknowledgeSnapshotReportRuntime 的一元 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_timeouton_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 的有界任务领取、任务租约归属校验和 Observation 上报已经由 gRPC 契约测试覆盖Controller 已装配 Redis 共享 due-index、生产任务 broker 与独立 Checker 的 HTTP/HTTPS BASIC 探测进程。EGRESS 和 TARGET 尚未进入生产调度。 100,000 QPS 仍是未验证的设计目标。

WatchSnapshots 建立时校验当前 session每次签发快照引用时也把 session_id 交给 Redis 原子校验。重复 Register 会同时清除旧 Runtime 和已签发引用,因此迟到的 旧 Stream 既不能覆盖新 session 的引用,也不会向旧连接发送未获授权的快照。

2. Worker 会话

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 包含:

  • 单调 versionownership_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_HANDSHAKEHTTP 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 从序列 1 重新封装后上报。栅栏随 session 替换或过期清理, 因此长期在线 Worker 不会因独立 Outcome TTL 接受旧序列。

6. Checker 任务

Checker 注册自身最大并发与支持层级Controller 发送有 deadline 的任务:

  • BASIC:基础连通和协议握手。
  • EGRESS:出口身份与匿名性。
  • TARGET:针对 Routing/目标组的可达性。

Checker 只返回 HealthObservation。Controller reducer 按 Proxy、检查层级和 Routing 决定 AVAILABLE、SUSPECT 或 UNHEALTHY并更新 Redis 活动池,避免 多个 Checker 并发写状态。

StreamCheckTasks 是有界 pull请求中的 max_in_flight 受服务端单次领取上限 限制,且同一 checker_id 的未完成租约会占用该窗口,重连不会扩大并发。任务仅在 被领取时通过认证的 mTLS 流携带 endpoint、secret_ref、版本和任务期凭据Checker 不访问 Redis 或 PostgreSQL。ReportObservations 在调用 Reducer 前校验 task、Proxy、 检查层级、目标 Profile 与领取者一致Reducer 成功后才确认任务;相同领取者对已确认 任务的同一事实可重放,由活动池摘要幂等处理。生产 Controller 装配 Redis 共享 broker按启用 Upstream 的有效检查策略调度 BASIC 任务;未装配 broker 的 fixture 服务仍会以 Unavailable 拒绝任务流而不下发无租约任务。EGRESS 和 TARGET 的 任务索引及调度策略尚未实现。 每次 Claim 还会签发新的不可预测 lease_tokenObservation 必须回传该值。任务被重新 领取后,旧 token 即使拥有相同 task_idchecker_id 也会被拒绝,避免过期实例的 迟到事实覆盖新租约结果。

proxy-checker 使用固定大小 worker-pool 执行每个 pull 批次,任务数不超过该请求的 max_in_flight;每次尝试都受 deadlinetimeout 的较小值约束,失败可在同一 deadline 内最多执行到 max_attempts。BASIC 针对 HTTP/HTTPS Proxy 验证到 Proxy 的 请求/认证握手TARGET 通过 Proxy 请求指定目标并将非成功状态作为事实。SOCKS5 与 EGRESS 的专用出口语义仍待后续探测器扩展。

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、日志或指标。 Checker 任务也遵循相同边界:连接凭据只存在于 Controller 的解析过程、任务流和 Checker 的单次执行期,不进入任务日志、指标或独立任务存储。

9. Gateway 启动参数

Gateway 不复用 controlPlane.listen 作为客户端地址。listen 是 Controller 的 服务端绑定地址;每个 Gateway 必须显式提供以下独立参数或同名环境变量:

  • -control-plane / PROXY_POOL_CONTROL_PLANE_ADDRESSController 的可拨号地址。
  • -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=mtlsGateway 使用 controlPlane.gatewayTLS 中独立的 客户端证书、私钥和 Controller CA 发起 TLS 1.3 连接;证书必须符合 Controller 的 SPIFFE Worker 身份校验。 disabled 仅接受回环控制面地址,供本地 fixture 使用。

10. Checker 启动参数

  • -control-plane / PROXY_POOL_CONTROL_PLANE_ADDRESSController 的可拨号地址。
  • -checker-id / PROXY_POOL_CHECKER_ID:唯一逻辑 Checker。
  • -instance-id / PROXY_POOL_CHECKER_INSTANCE_ID:唯一进程实例。
  • -max-in-flight / PROXY_POOL_CHECKER_MAX_IN_FLIGHT:本进程任务上限。
  • -levels:逗号分隔的 basic,egress,target 能力集合。

controlPlane.tls.mode=mtlsChecker 使用 controlPlane.checkerTLS 的独立 客户端证书、私钥和 Controller CA 建立 TLS 1.3 连接;证书必须符合 Controller 的 SPIFFE Checker 身份校验。disabled 仅接受回环控制面地址。