# Proxy Pool 运维手册 ## 1. 运行边界 - Gateway 是数据面,正常请求热路径不访问 PostgreSQL、Redis 或 Provider。 - Gateway 的 Proxy Outcome 仅进入进程内有界队列;Controller 只维护当前 session 的最后确认序列和摘要,原始 Outcome 不写入 Redis 或 PostgreSQL。队列满时丢弃 观测样本,代理转发和 Redis session TTL 不受影响。 - Controller 编排 Fetch、生命周期、所有权、Snapshot 与 Extract;多个副本只有 一个 Provider 逻辑 Leader。短效 Proxy 明细只存在于 Redis TTL 活动池和节点 内存,可由 Provider 重建。 - PostgreSQL 只持久化配置版本、Upstream/Routing 管理状态、Admin 审计与 outbox,以及可选聚合指标;不保存 Proxy 明细或逐次提取记录。 - Checker 执行有界健康探测,只上报 Observation,最终状态由 Controller reducer 决定。 - Extract 是一次性独占发放。Redis 在单次原子操作中校验并从可分配活动池移除 候选,同时写入带 TTL 的幂等结果;没有 Lease、续租或 Release 接口。 - `reserveForGateway` 是共享池硬约束,Extract 不得把 Gateway 库存清空。 - 集群峰值 100,000 QPS 是设计目标,只有完成本文容量验收后才能作为已验证 能力对外承诺。 ## 2. 本地拓扑模板 `cmd/proxy-controller` 已完成配置单次加载、PostgreSQL 迁移、Redis 活动池、 Distribution/Admin/Metrics 独立监听和有界停机装配。Provider 自动补池、分布式 配额、动态重载和 Admin 低基数统计已装配;Controller 已装配 Redis BASIC/EGRESS/TARGET 任务 broker, `proxy-checker` 可执行 HTTP/HTTPS/SOCKS5 BASIC/EGRESS/TARGET 探测。`proxy-loadgen` 已提供有界 HTTP、 CONNECT 长连接和 Extract 场景,并可按最大错误率和 p99 延迟输出机器可判定的验收结果; 它不替代代表性集群压测。Compose 已声明本地 Controller/Gateway/Checker mTLS 拓扑, 但本机尚未完成容器端到端启动;Kubernetes 仍需要环境专属的工作负载身份 Overlay。 ### 2.1 前置条件 - Docker Engine 25+,Compose v2.30+。 - 至少 8 CPU、16 GiB 内存和 20 GiB 可用磁盘。 - 本地端口 `3000`、`8080`、`8081`、`8082`、`8404`、`9091` 未占用。 ### 2.2 配置凭据 `deploy/config/local.yaml` 只用于本机拓扑验证。进入运行时实施阶段后,再通过 密钥系统提供下列变量,并把 Provider 地址替换为测试 fixture: ```powershell $env:PROXY_POOL_GATEWAY_PASSWORD = "LOCAL_GATEWAY_PASSWORD" $env:PROXY_POOL_EXTRACT_TOKEN = "LOCAL_EXTRACT_TOKEN" $env:PROXY_POOL_ADMIN_TOKEN = "LOCAL_ADMIN_TOKEN" $env:PROXY_POOL_CONFIG_FINGERPRINT_KEY = "LOCAL_HIGH_ENTROPY_KEY_AT_LEAST_32_BYTES" $env:PROVIDER_A_TOKEN = "PROVIDER_A_TOKEN" $env:PROVIDER_B_TOKEN = "PROVIDER_B_TOKEN" ``` 首次启动 Compose 前,生成不入库的本地控制面证书。生成器要求目标目录为空,避免覆写 已有密钥: ```powershell .\scripts\generate-local-controlplane-certs.ps1 ``` ### 2.3 静态检查 ```powershell docker compose -f deploy/docker-compose.yml config kubectl kustomize deploy/kubernetes/base > rendered.yaml ``` Compose 启动后,Gateway 的 `/readyz` 需要先接收有效 Snapshot;Checker 的 `/readyz` 需要成功建立一次任务领取流,并会在后续领取失败时回到未就绪。两者的 `/livez` 只表示 进程仍在运行。 本地 Compose 的 Redis 只作为可重建短效状态 fixture,固定使用 `--appendonly no --save ""`,且不挂载 `/data` 或命名卷。真实 Redis 8.2 契约可 通过 `.\scripts\test-redis.ps1` 执行;脚本使用唯一命名空间并在结束时定向清理, 不执行 `FLUSHDB`。生产环境的 Redis 高可用与持久化策略必须独立评审,不能照搬 本地 fixture。 PostgreSQL 管理面集成测试通过 `.\scripts\test-postgres.ps1` 启动 `postgres:18-alpine`,仅绑定 `127.0.0.1:15432`,并将 PG18 数据根目录挂载为 tmpfs。`ApplyMigrations` 在同一物理连接上执行仓库内嵌的幂等前向迁移;测试为 每个契约创建唯一 Schema,结束时只删除该 Schema 和临时 Compose 项目。该脚本 禁止指向开发或生产数据库。 `.\scripts\test-controller.ps1` 同时启动两个隔离 fixture,验证 Controller bootstrap 的迁移、启动配置提交、Redis Readiness、Admin Status、`/readyz` 与 Prometheus 输出。脚本不启动 部署模板中的 Controller 容器,也不连接开发或生产存储。 目标拓扑入口: - Gateway:`127.0.0.1:8080` - Distribution:`http://127.0.0.1:8081` - Admin:`http://127.0.0.1:8082` - Controller Metrics:`http://127.0.0.1:9090` - HAProxy 状态:`http://127.0.0.1:8404/stats` - Prometheus:`http://127.0.0.1:9091` - Grafana:`http://127.0.0.1:3000` `deploy/config/local.yaml` 中的 `.invalid` Provider 是故障演示占位。未替换时 Fetch 应表现为 Error 与退避,不应增加 Empty 计数,也不影响已有 Proxy。 ## 3. Kubernetes 发布 本节是运行时实施完成后的发布规格,不代表当前代码已达到生产就绪。 ### 3.1 准备 1. 使用托管 PostgreSQL 和 Redis,分别配置 TLS、备份、监控和多可用区。 2. 复制 `secret.example.yaml` 到环境私密配置系统,由 External Secrets、SOPS 或密钥管理平台生成 `proxy-pool-secrets`,不要提交真实 Secret。 `PROXY_POOL_CONFIG_FINGERPRINT_KEY` 必须使用至少 32 字节的高熵随机值,所有 Controller 副本保持一致,且与配置中的业务 Secret 分离管理。 3. 在环境 Overlay 替换镜像、Provider 地址、允许网段、外部存储地址、资源量 和 LoadBalancer 注解。 4. 根据集群 CNI 能力收紧 NetworkPolicy 的外部网段。 5. 在预发布环境完成数据库向前兼容迁移,再发布 Controller。 ### 3.2 服务端应用顺序 ```bash kubectl apply -f deploy/kubernetes/base/namespace.yaml kubectl -n proxy-pool apply -f ENVIRONMENT_SECRET.yaml kubectl apply -k deploy/kubernetes/base kubectl -n proxy-pool rollout status deployment/proxy-controller --timeout=5m kubectl -n proxy-pool rollout status deployment/proxy-gateway --timeout=10m ``` ### 3.3 探针语义 - `/livez`:进程事件循环仍可运行。数据库或 Redis 短暂失败不得导致 Gateway liveness 失败。 - `/readyz`:进程可以接收新工作。Gateway 只有在持有未超过 `maxStaleAge` 的完整 Snapshot 且仍有准入能力时才 Ready。 - Controller 按能力判定就绪:Distribution/Fetch 依赖 Redis 活动池,Admin 持久化 写依赖 PostgreSQL 与兼容迁移。PostgreSQL 故障不得单独使 Extract 返回 503。 - 同一 Controller 同时启用 Distribution 与 Admin 时,Pod `/readyz` 以 Redis 活动池为服务流量门槛;PostgreSQL 故障由 Admin 接口独立返回不可用,不把仍可 完成的 Extract 从 Service 摘除。Admin-only 进程才同时检查 PostgreSQL 与 Redis。 - `/metrics`:独立于业务入口,NetworkPolicy 仅允许监控命名空间访问。 探针不得执行 Provider 请求或完整数据库扫描。 Checker 指标使用固定标签集: - `proxy_pool_checker_tasks_dispatched_total{level}`:成功写入 Checker 任务流后的任务数。 - `proxy_pool_checker_observations_total{level,result}`:已解码的 Observation 被 Controller 接受或拒绝的数量。 `level` 仅为 BASIC、EGRESS、TARGET,`result` 仅为 accepted、rejected。不得将 Proxy ID、IP、Checker ID、目标 URL、Client ID 或凭据加入指标标签。 Gateway 指标同样使用固定标签集: 每个 Gateway Worker 还暴露 requests_total、requests_in_flight 与 active_tunnels 三类本地连接指标。protocol 标签只允许 HTTP、CONNECT;请求指标覆盖从 Handler 接受请求到全部转发、拒绝或隧道关闭的生命周期。活跃隧道只在成功写出 CONNECT 200 并开始 relay 后增加,关闭后立即减少。这些指标用于核对入口准入、连接池调优、 文件描述符预算和长连接排空,不得按 Proxy、路由、目标、Client 或凭据拆分。 - `proxy_pool_gateway_outcomes_total{stage,result}`:每次代理尝试在最远完成阶段的成功/失败数量。 - `proxy_pool_gateway_outcome_queue_dropped_total`:Outcome 本地有界队列已满后丢弃的观测数量。 `stage` 仅为 DIAL、PROXY_HANDSHAKE、RESPONSE_HEADERS、TUNNEL,`result` 仅为 success、failure。 前者在写入本地队列前计数,后者用于识别观测背压;二者均不包含 Proxy ID、路由、目标、客户端或凭据。 ### 3.4 发布顺序与兼容性 1. 先做向前兼容数据库迁移。 2. 发布 Controller,确认旧 Worker 仍能消费旧 Snapshot 协议版本。 3. 发布 Checker。 4. 逐批发布 Gateway;每次至少保留 PDB 要求的健康副本。 5. 观察 30 分钟,再清理已经无人读取的旧字段或旧迁移。 迁移失败时停止 Controller 发布,不自动重试结果不确定的管理 mutation。恢复后 先确认 Schema 版本、最近 revision、审计和 outbox 一致,再开放 Admin 写入口。 回滚只能回到仍兼容当前 Schema 和 Snapshot 版本的镜像。涉及不可逆数据迁移时, 必须使用前向修复。 ## 4. 容量规划 ```text worker_replicas = ceil(peak_qps / (tested_worker_qps * target_utilization)) + largest_failure_domain_replicas ``` - `peak_qps`:当前目标为 100,000。 - `tested_worker_qps`:在同 CPU、内存、网络、Go 版本、Snapshot 规模、TLS 与 上游响应模型下测出的单 Pod 持续能力。 - `target_utilization`:不高于 0.60,给突发、GC 和故障转移留空间。 - `largest_failure_domain_replicas`:最大单可用区失效时丢失的副本数。 禁止用 CPU 核数直接推算 QPS,也禁止把短时峰值当持续容量。Kubernetes 基线的 6 个 Gateway 副本只是初始值,必须由压测结果调整。 HPA 使用 CPU/内存作为保护性信号;生产环境建议通过 Prometheus Adapter 加入: - 每 Pod Gateway QPS。 - 活跃连接数和建连速率。 - p99 Dispatch 延迟。 - 拒绝率和 Available Slots。 连接型工作负载缩容至少稳定 10 分钟,终止前先 NotReady,再等待现有隧道排空。 ## 5. 日常检查 每班次检查: 1. Gateway QPS、错误率、p95/p99 和活跃连接。 2. Snapshot age、epoch/version、重同步与 ACK 延迟。 3. Available Slots、Reserved、Active、各 Proxy 状态数量。 4. Provider Success、Empty、Duplicate-only、Error、429 与退避。 5. Checker 队列、SUSPECT 数、检查延迟和目标级失败。 6. Extract requested/returned、insufficient、冲突和幂等命中。 7. PostgreSQL 连接、管理事务失败、outbox backlog、WAL 与备份。 8. Redis 延迟、内存、TTL 淘汰、原子操作错误、主从状态和 Leader 租约抖动。 Prometheus 标签禁止包含 Proxy IP、Client ID、Session、完整 URL、request ID。 需要逐请求调查时使用受控、脱敏且采样的结构化日志。 ## 6. 优雅停机 ### Gateway 1. readiness 立即失败,停止新连接。 2. 停止应用新 Snapshot,但保留当前不可变版本。 3. 等待 HTTP 请求和 CONNECT 隧道排空。 4. 到达 60 秒上限后关闭残余连接,保证容量 reservation 被释放。 ### Controller 1. 停止接收新的 Extract/Admin 写请求。 2. 停止发起 Fetch,释放 Provider Leader 租约。 3. 等待已提交 Redis 原子操作返回;未确认请求的客户端必须使用相同幂等键重试。 4. 刷新 Admin outbox、审计和 Worker ACK,再关闭 PostgreSQL/Redis 连接池。 Outbox 发布器必须以稳定 consumer ID 有界领取;发布成功后原子 ACK。进程在发布 成功但 ACK 结果不确定时,不得伪造确认;等待租约到期后重领,并由下游事件消费者 按事件 ID/revision 去重。持续积压时先暂停新的管理变更,检查发布目标、租约和 最老未发布事件,不删除未发布行。 ### Checker 1. 停止领取新任务。 2. 在 45 秒内完成或取消现有探测。 3. 批量上报已完成 Observation,未完成任务由队列重新投递。 ## 7. 故障处置 ### 7.1 Snapshot 陈旧 症状:`ProxyPoolGatewaySnapshotStale`、Worker 重同步增加、Gateway Ready 下降。 1. 检查 Controller、控制流和 outbox 延迟。 2. 确认 Worker epoch 与 Controller epoch,禁止手工降低 epoch。 3. 缺版本时强制完整 Snapshot,不要继续应用 Delta。 4. `maxStaleAge` 内允许旧 Snapshot 服务;超限自动拒绝新流量并排空。 5. 不得通过无限增大 `maxStaleAge` 隐藏控制面故障。 ### 7.2 PostgreSQL 不可用 1. Gateway 继续使用最后有效 Snapshot。 2. Redis 健康且运行配置有效时,Distribution 继续执行原子 Extract,Provider 按 last-known 管理状态继续刷新 TTL 活动池;状态读取失败不得触发全进程退出。 3. 拒绝配置版本、Upstream/Routing 管理状态和其他需要 Admin 审计/outbox 的写入; 不得把 Proxy 明细临时落入 PostgreSQL。 4. 恢复后核对迁移、管理事务回滚、Admin 审计与 outbox backlog;不存在 Proxy 明细或逐次提取记录恢复步骤。 5. 若权威配置指纹已变化,确认每个 Controller 的共享配置源和 Secret 版本已同步; 同时确认 `PROXY_POOL_CONFIG_FINGERPRINT_KEY` 一致。指纹不匹配的副本会停止旧 Provider,匹配并预检成功后按 PostgreSQL revision 自动恢复;迟到旧 revision 不会覆盖较新本地配置。 ### 7.3 Redis 不可用 1. Gateway 继续读取节点内最后有效 Snapshot,直到 `maxStaleAge`;请求热路径不 回查 PostgreSQL 或 Provider。 2. Distribution 立即失败关闭并返回 503,禁止本地内存提取或 PostgreSQL 兜底。 3. Controller 停止活动池写入和需要分布式互斥的工作,防止多个 Fetch Leader; Distribution 的 Redis 权威限流同时失败关闭,本地早期限流不冒充跨副本额度。 4. 恢复后确认 Leader 唯一和租约 epoch 单调,由 Provider 重新 Fetch 并构建 TTL 活动池,再恢复 Distribution。Redis 整体丢失会终止原活动池代次的排他状态和 短期幂等窗口;高可用、持久化、监控和告警必须明确并降低该风险。 ### 7.4 Provider 故障 1. 超时、DNS、认证、非预期 HTTP、响应超限与模板错误全部计 Error。 2. 429 尊重 `Retry-After`,其余 Error 使用指数退避和 jitter。 3. Error 不增加 Empty;合法候选为零才增加 Empty。 4. Duplicate-only 重置 Empty 并记录独立指标。 5. 达到 Empty 阈值后每条受影响 Routing 只原子切换一次;最后一个可用 Upstream 仍持续 Empty 且配置为 `stop` 时,确认审计中存在 `disable_routing` 和 `routing.disabled` Outbox 事件,并检查 Gateway 已收到关闭该 Routing 的完整快照。 ### 7.5 Gateway 容量耗尽 1. 检查 Available Slots,而不是只看 Proxy 数量。 2. 确认是否大量容量停留在 Reserved,排查 Commit/Cancel 泄漏。 3. 检查 TTL safety margin、健康状态和 Worker 所有权是否导致候选被过滤。 4. 快速拒绝新请求,禁止无界等待或把压力转移到 Controller。 5. 扩容 Gateway 前确认存在可分配 Proxy 所有权切片。 ### 7.6 Extract 库存不足 1. `partial` 返回实际数量;`allOrNothing` 不足时不改变 Redis 活动池。 2. 检查 TTL、health age、filter、Worker ownership 和 `reserveForGateway`。 3. 不得降低 `reserveForGateway` 到导致 Gateway 容量告警的水平。 4. 回收 Worker-owned Proxy 必须先 DRAINING、等待 active/reserved 为零、清除 ownership,再由 Redis 原子操作将其从可分配活动池移除并写入短期幂等结果。 5. 已提取代理没有 Release;客户端归还请求只记录为无效调用,不恢复库存。 ### 7.7 Checker 积压 1. 优先新 Proxy 与 SUSPECT 复检。 2. 降低稳定 AVAILABLE 的普通复检频率。 3. 检查目标超时、DNS 与出口网络,再按任务延迟扩容 Checker。 4. 队列必须有上限;不得无限积压耗尽内存或 Redis。 ### 7.8 持续 UNHEALTHY 回收 1. 先确认 `check.maxConsecutiveFailures`、检查目标和出口网络;首次失败进入 SUSPECT,阈值达到后才进入 UNHEALTHY。 2. `check.unhealthyRemoveAfter: 0s` 仅保留异常状态;设置正值后 Controller 才会在 持续异常超过该窗口时执行有界回收。 3. 回收器只删除没有 Worker ownership 的记录。发现所有权时会延后处理,不得手动 删除 Redis 记录或索引。 4. 对已分配代理,按既有流程执行 DRAINING,等待 Worker ACK 且 active/reserved 都归零;所有权清除后,下一轮回收才会删除该异常 Proxy。 ## 8. 备份与恢复 - PostgreSQL:每日全量、连续 WAL/PITR,保护配置版本、Upstream/Routing 管理 状态、Admin 审计与 outbox;至少每季度做恢复演练。 - Redis:保存可由 Provider 重建的 TTL 活动池、所有权/Leader 协调和短期幂等 结果;生产环境可使用高可用与受控持久化降低窗口丢失风险,但不把它当长期 业务档案或备份源。本地 Compose 刻意关闭持久化并且不挂载数据卷。 - 配置:版本化保存校验通过的不可变 Revision 与校验和。 - Secret:由密钥平台版本化,日志和备份中不得出现明文。 管理面恢复顺序:PostgreSQL -> Controller/Admin;代理运行面恢复顺序:Redis -> Provider 重建活动池 -> Controller/Checker -> 发布新 Snapshot。验证 Worker ownership epoch、活动池 TTL、短期幂等窗口、outbox 和配置 Revision 一致后,再 开放相应能力;Gateway 在旧 Snapshot 未超过 `maxStaleAge` 时无需等待数据库恢复。 ## 9. Secret 轮换 1. 创建新 Secret 版本,不覆盖旧值。 2. Provider/API 凭据支持双版本重叠时先发布新版本。 3. 更新配置 Revision,确认 Controller 成功重建 Adapter。 4. 观察 Fetch Error、认证失败与 Snapshot ACK。 5. 所有副本应用后撤销旧 Secret。 Proxy 凭据轮换必须增加 `credentialVersion`,确保唯一键不会把新旧凭据错误合并。 ## 10. 主机与网络容量基线 在预发布压测和每次生产扩容前,记录 Gateway 节点、容器运行时和 Pod 内的下列只读基线; 不要在故障处理中临时提高内核限制,任何变更都必须经过压测和变更评审。 ```bash ulimit -n cat /proc/self/limits | grep 'open files' sysctl fs.file-max net.core.somaxconn net.ipv4.ip_local_port_range test -r /proc/sys/net/netfilter/nf_conntrack_max && cat /proc/sys/net/netfilter/nf_conntrack_max ss -s ``` 同时从节点或受监控的 NAT 网关采集 conntrack 使用率、丢包、重传、SYN backlog、TIME_WAIT、 端口分配失败与 SNAT 端口耗尽事件。`conntrack -L` 会遍历整个表,不得在高峰时将它作为 常规排障命令;优先使用节点监控或 `conntrack -S` 的聚合计数。 Gateway 的文件描述符预算必须按实际连接模型计算,而不是仅按 QPS:每条活跃 CONNECT 隧道通常占用客户端和上游两个 socket;HTTP 上游并发、idle 连接池、监听 socket、日志和 运行时也会占用描述符。部署前确认 Pod 内 `ulimit -n`、容器运行时 `LimitNOFILE` 与节点 `fs.file-max` 均高于下式结果并保留至少 20% 余量: ```text fd_budget = 2 * max_active_connect_tunnels + max_concurrent_http_upstreams + max_idle_transport_connections + process_reserve ``` 如果节点通过 NAT 访问上游,源端口和 conntrack 表同样构成硬上限。压测报告必须记录每个 节点的 egress IP 数、可用临时端口范围、NAT/SNAT 设备限制与单目标连接分布;单一 egress IP 不足时应在发布前增加 egress IP 或拆分节点池,不能依靠无限重试掩盖端口耗尽。 建议将 FD、conntrack、NAT 端口利用率、SYN overflow、TCP retransmit、TIME_WAIT、CPU、RSS 和 Gateway p99 作为同一份容量证据采集。任何一项超过预设预警线时停止扩容或发布,先降低 新连接速率并保留现有 CONNECT 隧道排空;回滚到兼容镜像或配置 revision 后,再根据原始 指标分析根因。