734 lines
33 KiB
Markdown
734 lines
33 KiB
Markdown
# 配置参考
|
||
|
||
## 1. 加载规则
|
||
|
||
主配置格式为 YAML,根字段 `version` 当前固定为 `1`。加载器启用严格字段
|
||
检查,拼写错误或未来版本字段不会被静默忽略。当前仓库可使用与未来进程启动
|
||
相同的严格加载器校验本地部署配置:
|
||
|
||
```powershell
|
||
go run ./deploy/tools/configcheck deploy/config/local.yaml
|
||
```
|
||
|
||
Controller 入口已实现,源码运行方式为:
|
||
|
||
```powershell
|
||
go run ./cmd/proxy-controller -config CONFIG_FILE
|
||
```
|
||
|
||
配置路径优先使用 `-config`,未提供时读取 `PROXY_POOL_CONFIG`。启用 Admin 时还
|
||
必须设置 `PROXY_POOL_CONFIG_FINGERPRINT_KEY`,值为至少 32 字节的独立高熵密钥;
|
||
所有 Controller 副本必须一致,且该密钥不得放入 YAML 或 PostgreSQL。该入口已装配
|
||
PostgreSQL 管理面迁移、Redis 活动池、Distribution/Admin 独立监听与优雅停机;
|
||
Controller Metrics 独立监听、`/livez`、`/readyz` 和基础 Prometheus 运行时指标;
|
||
Provider 自动补池、分布式配额、动态重载和 Admin 低基数统计已装配。Gateway 进程、
|
||
Worker 控制面会话、持续完整 Snapshot 刷新、凭据分发和 Snapshot 就绪探针已装配;Checker 的任务流与
|
||
租约契约已接入控制面,但 Redis 共享调度、独立执行器与代表性负载验证仍在后续实施范围。
|
||
|
||
所有时间值使用 Go duration,例如 `500ms`、`30s`、`5m`。示例中的
|
||
`${TOKEN}`、`${PASSWORD}`、`${POSTGRES_URL}` 等由加载器从同名环境变量
|
||
展开;这些值不得写入日志、指标、配置转储或错误响应。生产配置优先使用
|
||
环境变量或 Secret 文件,不提交明文值。
|
||
|
||
加载与热更新必须遵循同一顺序:
|
||
|
||
```text
|
||
读取文件 -> 严格解析 -> 字段校验 -> 引用/正则校验
|
||
-> 构建不可变快照 -> 计算差异 -> 原子替换
|
||
```
|
||
|
||
新配置任何一步失败时保留旧快照。删除或禁用 Upstream 只停止新 Fetch 和新
|
||
分配,已有连接进入 Drain,不强制中断。
|
||
|
||
当前 Controller 启动时使用同一份不可变快照完成存储、入口和提取策略装配。
|
||
Admin 重载会原子提交新配置、审计并发布到配置 Store;监听地址、存储连接和
|
||
已构造的安全/提取策略尚未自动重建,这些字段变更后需要重启 Controller。
|
||
|
||
## 2. 根结构
|
||
|
||
```yaml
|
||
version: 1
|
||
defaults: {}
|
||
security: {}
|
||
gateway: {}
|
||
distribution: {}
|
||
admin: {}
|
||
controlPlane: {}
|
||
metrics: {}
|
||
storage: {}
|
||
routing: []
|
||
upstreams: {}
|
||
```
|
||
|
||
- `defaults`:Fetch 与 Check 的公共建议值。生产配置仍建议在每个启用的
|
||
Upstream 显式写出关键限制,避免继承关系不清。
|
||
- `security`:跨入口启动保护。
|
||
- `gateway`:HTTP/HTTPS CONNECT 数据面入口。
|
||
- `distribution`:一次性独占提取入口。
|
||
- `admin`:运维管理入口,必须与 Distribution 分端口。
|
||
- `controlPlane`:Worker 注册、Snapshot ACK 和 Runtime 心跳的 gRPC 控制面,默认关闭。
|
||
- `metrics`:Prometheus 入口。
|
||
- `storage`:Controller 使用的 PostgreSQL 与 Redis 地址。
|
||
- `routing`:有序 Routing 列表,自上而下首条命中停止。
|
||
- `upstreams`:全局共享的 Upstream 运行时定义。
|
||
|
||
## 3. 安全与监听器
|
||
|
||
```yaml
|
||
security:
|
||
requireProtectionOnPublicListen: true
|
||
```
|
||
|
||
启用严格保护时,只要 Gateway、Distribution 或 Admin 满足以下全部条件,
|
||
启动即失败:
|
||
|
||
1. 入口已启用且监听地址不是回环地址。
|
||
2. `auth.mode: none`。
|
||
3. `access.allowCIDRs` 为空。
|
||
|
||
监听器公共字段:
|
||
|
||
```yaml
|
||
enabled: true
|
||
listen: 0.0.0.0:8081
|
||
access:
|
||
allowCIDRs: [10.0.0.0/8]
|
||
trustedProxies: [10.10.0.10/32]
|
||
auth:
|
||
mode: apiKey
|
||
header: X-API-Key
|
||
token: "${DISTRIBUTION_API_KEY}"
|
||
limits:
|
||
maxConcurrentConnections: 100000
|
||
requestsPerMinute: 6000
|
||
requestsPerMinutePerClient: 600
|
||
```
|
||
|
||
`trustedProxies` 只决定何时接受 `Forwarded` 或 `X-Forwarded-For`,不能替代
|
||
`allowCIDRs`。来自非可信代理的转发头必须忽略。
|
||
|
||
`requestsPerMinute` 和 `requestsPerMinutePerClient` 为非负整数,最大值为
|
||
`2^53-1`;Distribution 的非零额度由 Redis Lua 计数,因此配置校验统一限制在
|
||
Lua 可精确表示的整数范围内。
|
||
|
||
### 3.1 认证模式
|
||
|
||
- `none`:无身份认证,访问控制与限流仍生效。
|
||
- `usernamePassword`:使用 `username` 和 `password`。
|
||
- `apiKey`:使用 `header` 和 `token`。
|
||
- `bearer`:使用 `Authorization: Bearer TOKEN`;Gateway 对应
|
||
`Proxy-Authorization: Bearer TOKEN`。
|
||
- `ipWhitelist`:使用 `cidrs`。
|
||
- `any`:`methods` 中任一方法成功即可;Bearer 方法的 Secret 使用
|
||
`value`/`valueFile`。
|
||
|
||
### 3.2 端点权限
|
||
|
||
`auth.permissions` 是普通认证方式(Basic、API Key、Bearer、IP 白名单)的固定权限
|
||
集合。未配置时保留历史全权限行为;显式配置后只允许下列有限值:
|
||
|
||
- `admin:read`:读取 Admin Status 与审计页。
|
||
- `admin:write`:执行 Upstream/Routing/配置变更。
|
||
- `distribution:extract`:调用 Distribution 独占提取。
|
||
- `*`:所有权限,且必须单独出现。
|
||
|
||
`mode: any` 必须将 `permissions` 写在各 `methods` 项中,权限随实际命中的凭据
|
||
返回,顶层 `auth.permissions` 不允许同时配置。权限值不接受空白、重复或未知项。
|
||
缺少所需权限的已认证请求返回 `403`,并在进入 JSON 解码、提取或管理 mutation 前
|
||
结束。`mode: none` 不允许配置权限,避免把匿名访问误配置为受控授权。
|
||
|
||
```yaml
|
||
admin:
|
||
auth:
|
||
mode: any
|
||
methods:
|
||
- mode: bearer
|
||
valueFile: /run/secrets/admin-reader-token
|
||
permissions: [admin:read]
|
||
- mode: bearer
|
||
valueFile: /run/secrets/admin-writer-token
|
||
permissions: [admin:read, admin:write]
|
||
```
|
||
|
||
### 3.3 Distribution Client 提取约束
|
||
|
||
认证凭据可在 auth.client 中设置固定的 Client 约束;mode: any 则必须在实际
|
||
命中的 auth.methods 项下配置。当前 Client 约束按入口明确分工,避免出现只声明
|
||
不执行的访问控制。
|
||
|
||
- maxExtractCount:Distribution 单次提取的额外上限;0 表示不追加上限。
|
||
- allowedUpstreams:允许访问的 Upstream 名称集合。省略请求过滤条件时,服务端
|
||
自动使用这个集合;请求携带集合外名称会返回 403。
|
||
- allowedRegions:允许访问的地区集合,行为与 allowedUpstreams 相同。
|
||
|
||
空 client 配置保留旧版兼容语义,不会限制已认证凭据。Upstream 名称必须引用
|
||
当前配置中已有的 Upstream。
|
||
|
||
~~~yaml
|
||
distribution:
|
||
auth:
|
||
mode: any
|
||
methods:
|
||
- mode: apiKey
|
||
header: X-API-Key
|
||
valueFile: /run/secrets/tenant-a-key
|
||
permissions: [distribution:extract]
|
||
client:
|
||
maxExtractCount: 20
|
||
allowedUpstreams: [provider-a, provider-b]
|
||
allowedRegions: [shanghai, beijing]
|
||
~~~
|
||
|
||
### 3.4 Gateway Client 路由约束
|
||
|
||
Gateway 的 auth.client 支持 allowedRoutings、requestsPerMinute 和
|
||
maxConcurrentConnections。认证成功后,
|
||
Gateway 先完成目标地址策略和本地 Routing 匹配,再于本地 Dispatcher 前检查
|
||
Routing 集合;不匹配返回 403,不会尝试选择或预留 Proxy。速率和并发限制均以
|
||
认证 Client 为计数键,在每个 Gateway Worker 内执行,超过限制返回 429。三项
|
||
检查都只读取请求上下文和当前本地状态,不访问 Redis、PostgreSQL 或 Provider。
|
||
|
||
allowedRoutings 中的每个名称必须对应一个启用的 Gateway Routing。Gateway 上的
|
||
maxExtractCount、allowedUpstreams 和 allowedRegions,以及 Distribution 上的
|
||
allowedRoutings 和 requestsPerMinute 均会被配置校验拒绝。Admin 不支持任何
|
||
client 约束。
|
||
|
||
~~~yaml
|
||
gateway:
|
||
auth:
|
||
mode: any
|
||
methods:
|
||
- mode: bearer
|
||
valueFile: /run/secrets/checkout-gateway-token
|
||
client:
|
||
allowedRoutings: [checkout]
|
||
requestsPerMinute: 600
|
||
maxConcurrentConnections: 20
|
||
~~~
|
||
|
||
Gateway、Distribution、Admin 与 Provider API 是独立认证边界。改变其中一套
|
||
不得连带改变其他入口。
|
||
|
||
### 3.5 Gateway 粘性会话
|
||
|
||
gateway.stickySession 让调用方使用一个会话头尽量固定出口 Proxy。绑定键是认证
|
||
Client、已匹配 Routing 和会话标识的组合,因此不同 Client 或不同 Routing 使用
|
||
相同会话值也不会共享出口。该功能要求 Gateway 认证开启。
|
||
|
||
~~~yaml
|
||
gateway:
|
||
auth:
|
||
mode: bearer
|
||
tokenFile: /run/secrets/gateway-token
|
||
stickySession:
|
||
enabled: true
|
||
header: X-Proxy-Session
|
||
ttl: 20s
|
||
maxEntries: 100000
|
||
~~~
|
||
|
||
- header 必须是规范 HTTP Header 名;一个请求只能携带一个值,值由字母、数字、
|
||
点、下划线、连字符组成且最长 128 字节。
|
||
- ttl 是绑定的最长时长;实际到期时间还会被 Proxy 的 usableUntil、代理过期时间
|
||
和 allocation safety margin 截短。
|
||
- maxEntries 是当前 Gateway Worker 的硬上限。缓存只保存 Proxy ID 和过期时间,
|
||
不保存 Proxy 地址、凭据或原始会话标识。
|
||
- 会话头仅用于本地选路,Gateway 在转发 HTTP 请求前删除它。Proxy 不再符合当前
|
||
Snapshot 或一次代理尝试失败时,绑定会被清除并按正常 Routing 重选。
|
||
- 该版本不把 Redis、PostgreSQL 或 gRPC 引入 Gateway 热路径;跨 Worker 粘性需要
|
||
上游负载均衡保持 Worker 亲和,后续由控制面快照协议扩展共享恢复能力。
|
||
|
||
## 4. Worker 控制面
|
||
|
||
控制面默认关闭;默认配置中的 `8443` 端口预留不表示服务已监听。控制面 Session、
|
||
Snapshot ACK 和 Runtime 报告是 Redis 的短效运行时状态,**不写入 PostgreSQL**。
|
||
|
||
仅本机开发可以使用回环明文:
|
||
|
||
```yaml
|
||
controlPlane:
|
||
enabled: true
|
||
listen: 127.0.0.1:8443
|
||
protocolVersion: 1
|
||
heartbeatInterval: 10s
|
||
sessionTTL: 30s
|
||
maxStaleAge: 10s
|
||
maxMessageBytes: 1048576
|
||
maxRuntimeCounters: 100000
|
||
maxConcurrentStreams: 128
|
||
tls:
|
||
mode: disabled
|
||
```
|
||
|
||
任何非回环监听地址都必须使用 mTLS:
|
||
|
||
```yaml
|
||
controlPlane:
|
||
enabled: true
|
||
listen: 0.0.0.0:8443
|
||
protocolVersion: 1
|
||
heartbeatInterval: 10s
|
||
sessionTTL: 30s
|
||
maxStaleAge: 10s
|
||
maxMessageBytes: 1048576
|
||
maxRuntimeCounters: 100000
|
||
maxConcurrentStreams: 128
|
||
tls:
|
||
mode: mtls
|
||
certFile: /run/secrets/controller-cert.pem
|
||
keyFile: /run/secrets/controller-key.pem
|
||
clientCAFile: /run/secrets/worker-ca.pem
|
||
trustDomain: proxy.example
|
||
environment: production
|
||
gatewayTLS:
|
||
certFile: /run/secrets/gateway-cert.pem
|
||
keyFile: /run/secrets/gateway-key.pem
|
||
serverCAFile: /run/secrets/controller-ca.pem
|
||
checkerTLS:
|
||
certFile: /run/secrets/checker-cert.pem
|
||
keyFile: /run/secrets/checker-key.pem
|
||
serverCAFile: /run/secrets/controller-ca.pem
|
||
```
|
||
|
||
- `protocolVersion` 当前固定为 `1`。
|
||
- `sessionTTL` 至少为 `3 * heartbeatInterval`;`maxStaleAge` 不得短于心跳间隔。
|
||
- `maxMessageBytes` 范围为 `1..67108864`;`maxRuntimeCounters` 范围为
|
||
`1..1000000`;`maxConcurrentStreams` 必须为正数。
|
||
- `tls.mode` 只能为 `disabled` 或 `mtls`。`disabled` 只允许回环监听;`mtls` 必须
|
||
同时配置证书、私钥、客户端 CA、全小写 DNS `trustDomain` 和单 URI 路径段
|
||
`environment`。
|
||
- `gatewayTLS` 是 Gateway 的客户端证书、私钥和 Controller CA,与 `tls` 的服务端
|
||
证书和 Worker CA 分离。三项可以同时省略(未运行 Gateway),配置任一项时必须完整提供。
|
||
- `checkerTLS` 与 `gatewayTLS` 有相同字段和完整性校验,但必须使用独立的 Checker
|
||
证书。Controller 分别验证 `.../worker/<worker-id>` 与
|
||
`.../checker/<checker-id>` SPIFFE URI,不能跨角色复用证书。
|
||
|
||
Gateway 连接 Controller 时使用独立启动参数而非 `controlPlane.listen`。至少设置
|
||
`PROXY_POOL_CONTROL_PLANE_ADDRESS`、`PROXY_POOL_CLUSTER_ID`、
|
||
`PROXY_POOL_WORKER_ID`、`PROXY_POOL_INSTANCE_ID` 和 `PROXY_POOL_ZONE`,详见
|
||
[控制面协议](../api/control-plane.md#9-gateway-启动参数)。Compose 本地模板使用固定
|
||
Worker/Checker 身份和被忽略的开发证书目录;Kubernetes Base 保持
|
||
`controlPlane.enabled: false`,必须由具备每工作负载唯一身份的 mTLS Overlay 启用。
|
||
|
||
Checker 同样使用独立的可拨号地址:`proxy-checker` 的 `-control-plane`、
|
||
`-checker-id`、`-instance-id` 和 `-max-in-flight` 可由对应的
|
||
`PROXY_POOL_*` 环境变量提供。mTLS 模式下该命令读取 `checkerTLS`,明文 fixture
|
||
模式只接受回环 Controller 地址。Checker 只从 gRPC 领取任务并批量上报事实,不读取
|
||
Redis/PostgreSQL;Controller 在生产启动拓扑中装配 Redis 共享任务队列,当前调度
|
||
HTTP/HTTPS/SOCKS5 BASIC、EGRESS 和 TARGET 检查。调度监督器在每轮从已发布配置读取启用的
|
||
上游与 Routing;启用 Admin 时还要求 PostgreSQL 管理态与配置 revision 一致,并取两者均启用的
|
||
上游。管理态禁用的上游不会再产生新的 BASIC、EGRESS 或 TARGET 任务;revision 不一致或上游状态
|
||
不完整时本轮失败关闭。Admin reload 发布后,上游/路由启停、有效 `check` 策略和目标列表会在下一轮生效,
|
||
新启用的上游无需重启 Controller。
|
||
|
||
`maxRuntimeCounters` 同时限制单个 Runtime 报告和单个 Outcome 批次的条目数。Gateway
|
||
在本地维护容量为 `65536` 的非阻塞 Outcome 队列,默认微批上限为 `512`,实际取二者中
|
||
较小值;该队列与其序列确认状态仅存在于 Gateway 进程内。Controller 的 Redis 状态只保存
|
||
当前 Worker session 的最后确认序列和摘要,PostgreSQL 不保存 Outcome、代理明细或逐请求记录。
|
||
|
||
## 5. Gateway
|
||
|
||
```yaml
|
||
gateway:
|
||
enabled: true
|
||
listen: 127.0.0.1:8080
|
||
auth: {mode: none}
|
||
limits:
|
||
maxConcurrentConnections: 50000
|
||
transport:
|
||
dialTimeout: 10s
|
||
handshakeTimeout: 15s
|
||
responseHeaderTimeout: 30s
|
||
idleConnTimeout: 90s
|
||
maxIdleConns: 20000
|
||
maxIdleConnsPerHost: 32
|
||
maxConnsPerHost: 32
|
||
tunnelBufferBytes: 32768
|
||
tunnelIdleTimeout: 5m
|
||
retry:
|
||
maxAttempts: 2
|
||
retryMethods: [GET, HEAD]
|
||
stickySession:
|
||
enabled: true
|
||
header: X-Proxy-Session
|
||
ttl: 20s
|
||
maxEntries: 100000
|
||
destinationPolicy:
|
||
denyPrivateNetworks: true
|
||
denyLoopback: true
|
||
denyLinkLocal: true
|
||
denyCIDRs: [169.254.169.254/32]
|
||
allowedPorts: [80, 443]
|
||
```
|
||
|
||
- `retryMethods` 默认只应包含幂等方法。POST、PUT、PATCH、DELETE 不自动重试。
|
||
- CONNECT 向 Client 写出 `200 Connection Established` 后不透明重放。
|
||
- 目的地址策略必须在 DNS 解析前后都执行,防止 DNS Rebinding。
|
||
- 三个 `deny*` 字段省略时均按 `true` 处理;只有显式配置为 `false` 才放行
|
||
对应类别,部分配置不会改变其他类别的安全默认值。
|
||
- `allowedPorts` 省略时安全默认值为 `[80, 443]`;配置空列表不表示开放全部端口。
|
||
- 保留地址、CGNAT 与云元数据端点始终拒绝,不能通过私网/链路本地开关放行。
|
||
- `maxConcurrentConnections` 是入口准入上限,不是 Proxy 容量上限。
|
||
|
||
### 5.1 Gateway 本地传输层
|
||
|
||
**gateway.transport** 只配置每个 Gateway Worker 的本地 HTTP Transport 和 CONNECT
|
||
隧道 I/O,不读取或写入 Redis、PostgreSQL、Provider,也不参与热路径分配。修改
|
||
这些字段需要滚动重启对应 Gateway Worker 后生效。
|
||
|
||
- **dialTimeout**、**handshakeTimeout**、**responseHeaderTimeout**、
|
||
**idleConnTimeout**:分别限制拨号、上游 TLS/CONNECT 握手、HTTP 响应头和空闲
|
||
HTTP 连接的时长。
|
||
- **maxIdleConns**:单个本地 HTTP Transport 的总空闲连接上限。Gateway 分别为代理
|
||
转发和 direct fallback 建立 Transport,最坏情况下两者都可能保有空闲连接。
|
||
- **maxIdleConnsPerHost**:单个上游 Host 的空闲 HTTP 连接上限。
|
||
- **maxConnsPerHost**:单个上游 Host 的 HTTP 活跃加空闲连接上限。它不替代
|
||
**limits.maxConcurrentConnections**,也不限制已经建立的 CONNECT 隧道。
|
||
- **tunnelBufferBytes**:每个活跃 CONNECT 隧道的每个复制方向使用一个缓冲区,内存
|
||
预算至少按 活跃隧道数 * 2 * tunnelBufferBytes 计算。
|
||
- **tunnelIdleTimeout**:CONNECT 隧道双向没有数据活动时的最长存活时间。
|
||
|
||
所有字段均为可选项;数值 0 表示沿用进程默认值:拨号 10s、握手 15s、
|
||
响应头 30s、空闲 HTTP 连接 90s、总空闲连接 1024、每 Host 空闲连接
|
||
64、隧道缓冲 32768 字节、隧道空闲 5m。计数和时长不得为负数;
|
||
maxIdleConnsPerHost 不得大于显式配置的 maxIdleConns。示例中的 20000/32
|
||
是高并发 HTTP 转发的起点,不是每秒请求量的换算公式,应同时按可用文件描述符、
|
||
上游限额和观测到的复用率调节。
|
||
|
||
## 6. Distribution
|
||
|
||
```yaml
|
||
distribution:
|
||
enabled: true
|
||
listen: 127.0.0.1:8081
|
||
auth: {mode: none}
|
||
clientIdentification: {mode: sourceIP}
|
||
limits:
|
||
requestsPerMinute: 60
|
||
requestsPerMinutePerClient: 30
|
||
extraction:
|
||
fulfillment: partial
|
||
maxCountPerRequest: 20
|
||
minRemainingTTL: 30s
|
||
maxHealthCheckAge: 15s
|
||
reserveForGateway: 5
|
||
idempotencyTTL: 5m
|
||
```
|
||
|
||
Extraction 是固定的一次性独占行为,**没有** `mode`、`leaseDuration`、
|
||
`release` 或 `renew` 配置。Redis 在一个原子操作内选择候选、执行
|
||
`AVAILABLE -> EXTRACTED` 并短期保存幂等结果;成功后该 Proxy 不再由 Gateway
|
||
或 Distribution 在当前 TTL 生命周期中分配。
|
||
|
||
- `fulfillment`:`partial` 或 `allOrNothing`,默认语义为 `partial`。
|
||
- `maxCountPerRequest`:单次请求硬上限。
|
||
- `minRemainingTTL`:剩余寿命低于此值时不参与提取。
|
||
- `maxHealthCheckAge`:最近检查早于此窗口时不参与提取。
|
||
- `reserveForGateway`:提取后必须留给 Gateway 的最低符合条件库存数量。
|
||
- `idempotencyTTL`:Redis 幂等结果的最长保留时间;省略时为 5 分钟,实际
|
||
保留时间不会超过本次返回代理中最早的 `expiresAt`。
|
||
|
||
`partial` 会原子提取实际可得数量;`allOrNothing` 数量不足时不改变任何候选,
|
||
一个也不提取。认证关闭时仍应使用 `sourceIP` 识别匿名 Client 并执行全局/
|
||
来源限流。
|
||
|
||
`clientIdentification.mode` 支持:
|
||
|
||
- `sourceIP`:使用可信代理链解析后的来源地址;省略配置时采用此模式。
|
||
- `authenticatedClient`:使用 Basic 用户名或 Token 的稳定不可逆摘要;要求
|
||
启用认证。
|
||
- `authenticatedClientOrSourceIP`:优先认证主体,无主体时回退来源地址。
|
||
|
||
## 7. Routing
|
||
|
||
```yaml
|
||
routing:
|
||
- name: api-post
|
||
enabled: true
|
||
purpose: gateway
|
||
action: proxy
|
||
match:
|
||
hostRegex: '^api\\.example\\.com$'
|
||
methods: [POST]
|
||
pathRegex: '^/v1/'
|
||
headers: {X-Tenant: premium}
|
||
upstreams: [provider-a, provider-b]
|
||
strategy:
|
||
type: sequential
|
||
switchAfterEmptyFetch: 5
|
||
endBehavior: stop
|
||
onUnavailable:
|
||
action: reject
|
||
waitTimeout: 0s
|
||
check:
|
||
targets: [https://api.example.com/health]
|
||
```
|
||
|
||
- Routing 列表有序,首条匹配后停止。
|
||
- `purpose` 为 `gateway` 或 `extract`。
|
||
- `action` 省略时为 `proxy`。`proxy` 使用 Upstream 与 Strategy;`direct` 直接连接已通过
|
||
目标地址策略验证的目标;`reject` 拒绝匹配请求。`direct` 与 `reject` 仅允许
|
||
`gateway` Routing,且不得同时配置 `upstreams`、`strategy`、`onUnavailable` 或
|
||
`check.targets`。
|
||
- `strategy.type` 支持 `sequential`、`random`、`roundRobin`、`weighted`、
|
||
`leastConnections`。
|
||
- `weighted` 使用 `weights` 映射,键必须引用本 Routing 的 Upstream。
|
||
- `sequential` 至少引用两个 Upstream,并设置大于零的
|
||
`switchAfterEmptyFetch`;`endBehavior` 省略时默认为 `stop`,也可显式设置
|
||
`loop` 或 `stayLast`。
|
||
- `onUnavailable.action` 为 `reject`、`wait` 或 `direct`;默认建议 `reject`。
|
||
- `action: direct` 在 Dispatcher 前执行,不申请 Proxy 容量、不写 Proxy Outcome,也不建立
|
||
粘性 Proxy 绑定;它仍执行入口认证、Client 路由约束、速率/并发限制和目标地址策略。
|
||
`onUnavailable.action: direct` 仅是 `action: proxy` 在本地没有候选时的回退,二者
|
||
不可互换。
|
||
- `check.targets` 是 Routing 级 HTTP/HTTPS 探测目标。每个 Routing 最多 16 个 URL,且一个
|
||
启用 Upstream 被其引用的 TARGET Profile 总数最多 64 个。每个 Profile 的身份是
|
||
`(routing.name, target URL)`;失败只影响该 Profile,不改变 Proxy 的全局健康状态。
|
||
|
||
Sequential 的空计数属于 Upstream,当前索引属于 Routing。只有 Provider 响应
|
||
成功、模板成功且合法候选为零时才增加空计数。错误不改变空计数;重复候选
|
||
会重置空计数但增加独立 duplicate 指标。
|
||
|
||
Controller 在连续空结果达到阈值时按顺序切换当前 Upstream;到达最后一个可用
|
||
Upstream 且 `endBehavior: stop` 时,会以当前 Upstream 的 CAS 条件原子停用该
|
||
Routing。停用后的 Gateway 规则按既有 `onUnavailable.action` 执行 `reject`、`wait`
|
||
或 `direct`,直到一次配置重载提交新的管理快照。
|
||
|
||
管理态禁用当前 Sequential Upstream 不等待新的 Provider 空结果:Controller 会按
|
||
配置顺序以相同的 `ExpectedCurrent` CAS 推进到下一个启用 Upstream,并立即刷新完整
|
||
Snapshot。若当前项之后没有启用候选,只有 `endBehavior: loop` 会回绕搜索更早的候选;
|
||
`stop` 与 `stayLast` 都会原子停用 Routing,因为已禁用的当前项不能继续承接新分配。
|
||
|
||
## 8. Upstream
|
||
|
||
```yaml
|
||
upstreams:
|
||
provider-a:
|
||
enabled: true
|
||
exposure: [gateway, extract]
|
||
provider:
|
||
billingMode: fetch
|
||
protocols: [http, https]
|
||
api:
|
||
url: https://provider-a.example/proxies
|
||
method: GET
|
||
auth:
|
||
type: apiKey
|
||
location: header
|
||
name: X-Provider-Key
|
||
value: "${PROVIDER_API_KEY}"
|
||
headers: {}
|
||
query: {count: '100'}
|
||
body: {type: json, value: {}}
|
||
template: '{{.}}'
|
||
proxyAuth: {type: response}
|
||
pool: {maxSize: 1000, shrinkDelay: 30s}
|
||
capacity: {maxConcurrencyPerProxy: 10}
|
||
refill:
|
||
reconcileInterval: 1s
|
||
minimumAvailableSlots: 2000
|
||
targetAvailableSlots: 5000
|
||
lifecycle: {ttl: 2m, allocationSafetyMargin: 15s}
|
||
fetch:
|
||
estimatedIPsPerCall: 100
|
||
requestInterval: 1s
|
||
timeout: 3s
|
||
maxAttempts: 3
|
||
maxInFlight: 1
|
||
maxTotal: 100000
|
||
maxResponseBytes: 1048576
|
||
templateTimeout: 100ms
|
||
retry: {initial: 500ms, max: 30s, jitter: 20}
|
||
check:
|
||
interval: 30s
|
||
jitter: 20
|
||
maxInFlight: 100
|
||
timeout: 2s
|
||
maxAttempts: 2
|
||
maxConsecutiveFailures: 3
|
||
unhealthyRemoveAfter: 0s
|
||
urls: [http://connect.rom.miui.com/generate_204]
|
||
```
|
||
|
||
### 8.1 Provider 与代理认证
|
||
|
||
- `api.auth` 用于系统访问 Provider API。
|
||
- `proxyAuth` 用于最终连接被获取的 Proxy。
|
||
- Provider `api.auth.type` 支持 `none`、`basic`、`bearer`、`apiKey`。
|
||
- `apiKey` 使用 `location: header|query` 与 `name`、`value`,程序负责 Header
|
||
设置或 Query URL 编码。
|
||
- `basic` 使用 `username`、`password`;`bearer` 使用 `token`。
|
||
- `proxyAuth.type: response` 表示凭据来自 Provider 响应。
|
||
- `proxyAuth.type: static` 使用配置中的 `username`、`password`。
|
||
- `proxyAuth.type: ipWhitelist` 表示 Provider 按出口 IP 放行,不配置账号密码。
|
||
|
||
Provider API 认证**不使用**入口的 `auth.mode`,代理连接认证也不使用
|
||
`mode`。三者的字段和凭据不可互相回退:
|
||
|
||
```yaml
|
||
# 对外 Distribution/Gateway/Admin
|
||
auth:
|
||
mode: apiKey
|
||
header: X-API-Key
|
||
token: "${CLIENT_API_KEY}"
|
||
|
||
# 请求 Provider API
|
||
auth:
|
||
type: bearer
|
||
token: "${PROVIDER_API_TOKEN}"
|
||
|
||
# 连接 Provider 返回的 Proxy
|
||
proxyAuth:
|
||
type: static
|
||
username: "${PROVIDER_PROXY_USER}"
|
||
password: "${PROVIDER_PROXY_PASSWORD}"
|
||
```
|
||
|
||
### 8.2 Pool 与累计额度
|
||
|
||
- `pool.maxSize`:当前系统维护且尚未 EXTRACTED 的 Proxy 硬上限,包括
|
||
FETCHED、CHECKING、AVAILABLE、SUSPECT、DRAINING 和 pending expected。
|
||
- `fetch.maxTotal`:当前 Redis generation 内的累计获取停止阈值;`0` 表示不设置。
|
||
Redis 在调用前原子校验 `fetched total + pending expected + expected`,并在达到
|
||
阈值后停止发起新调用。
|
||
|
||
`fetch.maxTotal` 不得小于 `pool.maxSize`。提取一个 Proxy 会释放当前库存位置,
|
||
但不会恢复累计获取额度。Provider 调用结果不确定、响应无法解析或 Permit 过期时,
|
||
系统按 `estimatedIPsPerCall` 保守记账,避免故障或换主造成额度低估。Redis 全量
|
||
状态丢失会创建新 generation,因此需要由外部计费系统提供跨 generation 的长期额度。
|
||
|
||
`estimatedIPsPerCall` 是预留估值,不是通用的 Provider 响应硬限制。如果某次实际
|
||
合法返回量超过估值,系统只保留本地池容量允许的数量,但累计账本按实际合法数量
|
||
记账并停止后续调用;该次可能越过停止阈值。需要绝对硬上限时,必须同时在 Provider
|
||
请求参数中配置供应商支持的批量上限,并保证其不超过剩余额度。
|
||
|
||
当前实现限制单个 `pool.maxSize <= 1,000,000`、配置内 Upstream 总数不超过
|
||
`4,096`,并要求单代理并发、Refill 双水位、理论总槽位及其他传入 Redis Lua 的
|
||
累计/并发计数不超过 `2^53-1`。这些边界在配置加载和 Admin reload 提交前校验,
|
||
不会等到 Provider Runtime 启动后才失败。
|
||
|
||
`proxyAuth.type: response` 的用户名/密码是 Parser 到 Redis Activity Adapter 之间的
|
||
临时凭据。每次 Parser handoff 使用独立、幂等释放的 lease,避免并发 Fetch 互相
|
||
删除或轮换版本覆盖;成功复制到 TTL 活动记录、候选被截断或解析失败后都会释放。
|
||
内存 lease 上限按所有配置 Upstream 的 `pool.maxSize * fetch.maxInFlight` 汇总,
|
||
配置 reload 只提高上限,不预分配对应内存。
|
||
|
||
### 8.3 Fetch 限制
|
||
|
||
- `estimatedIPsPerCall`:冷启动时每次 Provider 调用预计返回的合法 Proxy 数,
|
||
同时用于 `pool.maxSize` 的 pending 预占;不得从任意 Query 或 Body 字段推断。
|
||
- `requestInterval`:同一 Provider 请求间隔。
|
||
- `timeout`:单次调用超时。
|
||
- `maxAttempts`:单次补池动作最大尝试次数。
|
||
- `maxInFlight`:同一 Provider 同时在途请求数。
|
||
- `maxResponseBytes`:读取响应的硬上限。
|
||
- `templateTimeout`:模板解析执行上限。
|
||
- `retry`:错误退避;HTTP 429 还必须尊重 `Retry-After`。
|
||
|
||
大量缺池信号必须合并成 singleflight 或容量为 1 的通知,不能按 Gateway 请求
|
||
数量线性触发 Provider API。
|
||
|
||
### 8.4 Refill 水位
|
||
|
||
- `reconcileInterval`:无事件时重新读取库存的兜底周期,启动时仍立即检查一次。
|
||
- `minimumAvailableSlots`:可用并发槽位低于该值时进入补池。
|
||
- `targetAvailableSlots`:进入补池后持续补到该目标,再退出补池状态。
|
||
|
||
三个字段均为必填正值,且 `targetAvailableSlots > minimumAvailableSlots`。
|
||
目标不得超过 `pool.maxSize * capacity.maxConcurrencyPerProxy` 的理论上限。
|
||
`requestInterval` 只限制外部 API 调用,不能兼任库存复核周期。pending Proxy
|
||
按 `estimatedIPsPerCall * maxConcurrencyPerProxy` 折算槽位,避免并发补池超量。
|
||
|
||
### 8.5 生命周期与健康
|
||
|
||
- 明确绝对过期时间优先于响应 TTL,响应 TTL 优先于配置 `lifecycle.ttl`。
|
||
- 距离过期不足 `allocationSafetyMargin` 时停止新分配。
|
||
- `check.jitter` 为调度抖动百分比,避免所有 Proxy 同时探测。
|
||
- `check.urls` 最多 16 个规范化 HTTP/HTTPS URL,作为 EGRESS 检查的有界目标集;
|
||
BASIC 检查不依赖该字段。EGRESS 成功响应必须包含合法 IP:支持纯文本 IP,或 JSON 的
|
||
`ip`、`ip_address`、`address`、`origin`、`query` 字段。
|
||
- 启用 Routing 的 `check.targets` 会为其引用的每个启用 Upstream 创建 TARGET 检查组。BASIC、
|
||
EGRESS 与 TARGET 使用固定批次和轮转顺序,并共享该 Upstream 的 `check.maxInFlight`,避免
|
||
配置多个目标后产生无界检查流量。
|
||
- 启用 Admin 时,调度仅使用配置和管理态均启用、且 revision 一致的 Upstream;管理态停用会在
|
||
下一调度轮阻止新的 BASIC、EGRESS、TARGET 任务。revision 不一致、重复或缺失的上游状态按失败
|
||
关闭处理,不读取 Redis due-index。
|
||
- 第一次有意义失败进入 SUSPECT;达到 `maxConsecutiveFailures` 后才进入
|
||
UNHEALTHY。
|
||
- `unhealthyRemoveAfter` 控制 UNHEALTHY 持续多久后可由 Controller 回收;`0s`
|
||
(默认)表示只保留异常状态,永不因该策略删除。首次进入 UNHEALTHY 的时间会跨
|
||
CHECKING 重试保持不变,成功恢复 AVAILABLE 时清除。
|
||
- 回收器只运行在 Controller 后台,使用 Redis 有序索引和固定批次,不进入 Gateway
|
||
请求热路径。没有 Worker ownership 的 Proxy 在同一清理边界删除;仍属于 Worker 的候选
|
||
先在有界回执中携带 worker、assignment epoch 和首次异常时间,再由条件式 Drain 原子
|
||
复核 `UNHEALTHY` 状态与归属未变化后创建 Ticket。后续必须等待排除快照 ACK 与 Runtime
|
||
的 Active/Reserved 归零,才会释放 ownership 并由下一轮回收,避免删除仍可能出现在
|
||
Gateway Snapshot 中的代理。
|
||
|
||
## 9. 存储、Admin 与 Metrics
|
||
|
||
```yaml
|
||
admin:
|
||
enabled: true
|
||
listen: 127.0.0.1:8082
|
||
auth: {mode: none}
|
||
metrics:
|
||
enabled: true
|
||
listen: 127.0.0.1:9090
|
||
storage:
|
||
postgresURL: "${POSTGRES_URL}"
|
||
redisURL: "${REDIS_URL}"
|
||
```
|
||
|
||
Proxy 明细不写入 PostgreSQL。Redis 是短效 Proxy 活动池的运行时事实源,使用
|
||
TTL 保存 Proxy 状态、所有权和过期时间,并原子执行独占提取及短期幂等结果
|
||
读写;Redis 丢失后由 Provider 重新补池,不从 PostgreSQL 恢复原 Proxy。
|
||
|
||
PostgreSQL 只保存配置版本、Upstream/Routing 管理状态、Admin 审计与 Outbox,
|
||
以及可选的无 Proxy 明细聚合指标。Distribution 提取不依赖 PostgreSQL,因而
|
||
PostgreSQL 故障本身不应使 Redis 中可完成的 Extract 返回 `503`。Metrics 标签
|
||
禁止 Proxy IP、Client ID、Session、完整 URL 和 Request ID。
|
||
|
||
启用 Admin 的多 Controller 部署必须让所有副本读取同一版本化配置源、Secret
|
||
版本和 `PROXY_POOL_CONFIG_FINGERPRINT_KEY`。Supervisor 每秒比较本地完整配置的
|
||
HMAC-SHA-256 与 PostgreSQL 权威值:管理状态暂时不可读时沿用 last-known
|
||
Runtime;指纹已更新但本地源尚未同步时停止旧 Provider,待源匹配并通过预检后
|
||
按全局 revision 恢复,避免旧凭据换主或迟到旧配置回写。
|
||
|
||
Metrics 启用时 `listen` 必须是合法 `host:port`。该入口固定提供 `/livez`、
|
||
`/readyz` 和 `/metrics`,不复用 Distribution/Admin 的认证边界;外部访问必须由
|
||
网络策略限制。当前 `/metrics` 包含 Go/进程基础指标,以及 Checker 的
|
||
`proxy_pool_checker_tasks_dispatched_total{level}` 和
|
||
`proxy_pool_checker_observations_total{level,result}`。`level` 固定为 BASIC、EGRESS、TARGET,
|
||
`result` 固定为 accepted、rejected。Gateway 另暴露
|
||
`proxy_pool_gateway_outcomes_total{stage,result}` 与
|
||
`proxy_pool_gateway_outcome_queue_dropped_total`;`stage` 固定为 DIAL、PROXY_HANDSHAKE、
|
||
RESPONSE_HEADERS、TUNNEL,`result` 固定为 success、failure。Provider、Extraction、
|
||
Capacity 和 Drain 业务指标也已接入,所有标签均为固定枚举;绝不包含 Proxy、IP、Checker、
|
||
Client、路由、目标 URL 或凭据标签。Capacity 从既有 Provider 库存对账循环聚合,
|
||
不进入 Gateway 热路径。Distribution 启用时 `/readyz` 只以 Redis 活动池为
|
||
服务流量门槛,PostgreSQL 故障由 Admin 接口独立报告。Metrics 开关或监听地址
|
||
变更需要重启 Controller。Controller、Gateway 和 Checker 的进程级致命错误以 JSON
|
||
结构化日志输出,包含组件和稳定错误类型,不输出错误原文;敏感属性、URL 用户信息和
|
||
查询 Secret 在写出前统一替换为 `[REDACTED]`。
|
||
|
||
Gateway 还按每个 Worker 暴露 HTTP/CONNECT 请求总数、在途请求数和活跃 CONNECT
|
||
隧道数;协议标签固定为 HTTP、CONNECT。请求数在 Handler 接受请求时增加,在途数在
|
||
全部拒绝、转发或隧道关闭后归零;活跃隧道只覆盖成功建立并开始 relay 的连接。这些
|
||
指标是连接池、入口并发、文件描述符和长连接排空的本地观测,不携带 Proxy、路由、
|
||
目标、Client 或凭据。
|
||
|
||
## 10. 启动前校验清单
|
||
|
||
1. `version` 必须为 `1`,未知字段拒绝。
|
||
2. 所有启用监听器具有合法 `host:port`。
|
||
3. 非回环监听器满足认证或来源 CIDR 保护。
|
||
4. Routing 名称唯一,正则可编译,引用的 Upstream 存在。
|
||
5. Sequential 至少引用两个 Upstream、阈值大于零,`onUnavailable.action` 明确。
|
||
6. 启用的 Upstream 有正数 `pool.maxSize`、并发、Refill 水位和 Fetch 估值/限制。
|
||
7. `allocationSafetyMargin < ttl`。
|
||
8. `fetch.maxTotal == 0` 或 `fetch.maxTotal >= pool.maxSize`。
|
||
9. `minimumAvailableSlots < targetAvailableSlots`,且目标不超过理论并发容量。
|
||
10. Distribution 的 fulfillment 合法,单次数量大于零。
|
||
11. Secret 未写入日志可见配置转储。
|