# 配置参考 ## 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`。 Gateway、Distribution、Admin 与 Provider API 是独立认证边界。改变其中一套 不得连带改变其他入口。 ## 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/` 与 `.../checker/` 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/Kubernetes 模板保持 `controlPlane.enabled: false`,环境 Overlay 挂载 mTLS 证书并启用后才可启动 Gateway。 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 BASIC 检查。EGRESS 和 TARGET 的生产调度仍在后续实施范围。 `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 retry: maxAttempts: 2 retryMethods: [GET, HEAD] 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 容量上限。 ## 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 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 ``` - Routing 列表有序,首条匹配后停止。 - `purpose` 为 `gateway` 或 `extract`。 - `strategy.type` 支持 `sequential`、`random`、`roundRobin`、`weighted`、 `leastConnections`。 - `weighted` 使用 `weights` 映射,键必须引用本 Routing 的 Upstream。 - `sequential` 至少引用两个 Upstream,并设置大于零的 `switchAfterEmptyFetch`;`endBehavior` 省略时默认为 `stop`,也可显式设置 `loop` 或 `stayLast`。 - `onUnavailable.action` 为 `reject`、`wait` 或 `direct`;默认建议 `reject`。 Sequential 的空计数属于 Upstream,当前索引属于 Routing。只有 Provider 响应 成功、模板成功且合法候选为零时才增加空计数。错误不改变空计数;重复候选 会重置空计数但增加独立 duplicate 指标。 ## 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 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 同时探测。 - 第一次有意义失败进入 SUSPECT;达到 `maxConsecutiveFailures` 后才进入 UNHEALTHY。 ## 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/进程基础指标,Provider、提取和容量等 业务指标仍在后续实施范围。Distribution 启用时 `/readyz` 只以 Redis 活动池为 服务流量门槛,PostgreSQL 故障由 Admin 接口独立报告。Metrics 开关或监听地址 变更需要重启 Controller。 ## 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 未写入日志可见配置转储。