23 KiB
配置参考
1. 加载规则
主配置格式为 YAML,根字段 version 当前固定为 1。加载器启用严格字段
检查,拼写错误或未来版本字段不会被静默忽略。当前仓库可使用与未来进程启动
相同的严格加载器校验本地部署配置:
go run ./deploy/tools/configcheck deploy/config/local.yaml
Controller 入口已实现,源码运行方式为:
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 文件,不提交明文值。
加载与热更新必须遵循同一顺序:
读取文件 -> 严格解析 -> 字段校验 -> 引用/正则校验
-> 构建不可变快照 -> 计算差异 -> 原子替换
新配置任何一步失败时保留旧快照。删除或禁用 Upstream 只停止新 Fetch 和新 分配,已有连接进入 Drain,不强制中断。
当前 Controller 启动时使用同一份不可变快照完成存储、入口和提取策略装配。 Admin 重载会原子提交新配置、审计并发布到配置 Store;监听地址、存储连接和 已构造的安全/提取策略尚未自动重建,这些字段变更后需要重启 Controller。
2. 根结构
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. 安全与监听器
security:
requireProtectionOnPublicListen: true
启用严格保护时,只要 Gateway、Distribution 或 Admin 满足以下全部条件, 启动即失败:
- 入口已启用且监听地址不是回环地址。
auth.mode: none。access.allowCIDRs为空。
监听器公共字段:
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。
仅本机开发可以使用回环明文:
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:
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、全小写 DNStrustDomain和单 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,详见
控制面协议。基础 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/SOCKS5 BASIC、EGRESS 和 TARGET 检查。调度监督器在每轮从已发布配置读取启用的
上游与 Routing;Admin reload 发布后,上游/路由启停、有效 check 策略和目标列表会在下一轮生效,
新启用的上游无需重启 Controller。
maxRuntimeCounters 同时限制单个 Runtime 报告和单个 Outcome 批次的条目数。Gateway
在本地维护容量为 65536 的非阻塞 Outcome 队列,默认微批上限为 512,实际取二者中
较小值;该队列与其序列确认状态仅存在于 Gateway 进程内。Controller 的 Redis 状态只保存
当前 Worker session 的最后确认序列和摘要,PostgreSQL 不保存 Outcome、代理明细或逐请求记录。
5. Gateway
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
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
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
check:
targets: [https://api.example.com/health]
- 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。check.targets是 Routing 级 HTTP/HTTPS 探测目标。每个 Routing 最多 16 个 URL,且一个 启用 Upstream 被其引用的 TARGET Profile 总数最多 64 个。每个 Profile 的身份是(routing.name, target URL);失败只影响该 Profile,不改变 Proxy 的全局健康状态。
Sequential 的空计数属于 Upstream,当前索引属于 Routing。只有 Provider 响应 成功、模板成功且合法候选为零时才增加空计数。错误不改变空计数;重复候选 会重置空计数但增加独立 duplicate 指标。
8. Upstream
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。三者的字段和凭据不可互相回退:
# 对外 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,避免 配置多个目标后产生无界检查流量。 - 第一次有意义失败进入 SUSPECT;达到
maxConsecutiveFailures后才进入 UNHEALTHY。 unhealthyRemoveAfter控制 UNHEALTHY 持续多久后可由 Controller 回收;0s(默认)表示只保留异常状态,永不因该策略删除。首次进入 UNHEALTHY 的时间会跨 CHECKING 重试保持不变,成功恢复 AVAILABLE 时清除。- 回收器只运行在 Controller 后台,使用 Redis 有序索引和固定批次,不进入 Gateway 请求热路径。当前只原子删除没有 Worker ownership 的 Proxy;仍属于 Worker 的候选会 延后,等待既有 Drain/ACK 完成后再由下一轮回收,避免删除仍可能出现在 Gateway Snapshot 中的代理。
9. 存储、Admin 与 Metrics
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。绝不包含 Proxy、IP、Checker、
Client、路由、目标 URL 或凭据标签。Provider、提取和容量等业务指标仍在后续实施范围。Distribution 启用时 /readyz 只以 Redis 活动池为
服务流量门槛,PostgreSQL 故障由 Admin 接口独立报告。Metrics 开关或监听地址
变更需要重启 Controller。
10. 启动前校验清单
version必须为1,未知字段拒绝。- 所有启用监听器具有合法
host:port。 - 非回环监听器满足认证或来源 CIDR 保护。
- Routing 名称唯一,正则可编译,引用的 Upstream 存在。
- Sequential 至少引用两个 Upstream、阈值大于零,
onUnavailable.action明确。 - 启用的 Upstream 有正数
pool.maxSize、并发、Refill 水位和 Fetch 估值/限制。 allocationSafetyMargin < ttl。fetch.maxTotal == 0或fetch.maxTotal >= pool.maxSize。minimumAvailableSlots < targetAvailableSlots,且目标不超过理论并发容量。- Distribution 的 fulfillment 合法,单次数量大于零。
- Secret 未写入日志可见配置转储。