生产最佳实践
本章给出 ScootGate 的推荐生产基线。它不是替代平台安全规范的万能模板; 当可用性与授权、出口或计费副作用边界冲突时,优先保证后者正确。
1. 推荐拓扑
┌──────────────────────────┐
│ 外部控制面 │
│ auth / routing snapshots │
└────────────┬─────────────┘
│ HTTPS 拉取
客户端 ── TLS ──► ScootGate ── CONNECT ──► 专用出站代理 ── TLS ──► Provider
│ │
│ ├── MQTT:usage + snapshot notifications
│ └── 持久盘:snapshot cache + usage outbox
└── health listener(仅内网/本机)
推荐同时在两层强制出口边界:
- ScootGate 配置只提供一个受控 HTTP CONNECT 或 SOCKS5 出口;
- 主机或网络策略禁止 ScootGate 直接访问 Provider,只允许访问控制面、 MQTT、DNS 和专用出站代理。
第二层不是为了弥补应用逻辑,而是防止未来配置错误或回归绕开代理。
2. 配置与秘密
Provider 凭据
- 优先使用
[credentials.<id>].file指向 secret manager 挂载的只读文件; - 使用
env时,仅向 ScootGate 进程注入,不写进 shell profile、镜像或日志; - 每个 Provider/账户使用独立 credential ID,便于撤销和审计;
- 文件建议权限
0400,所属用户为运行 ScootGate 的无特权账户。
服务身份
当前 [auth].token、[egress].username/password 与
[mqtt].username/password 是配置字段。生产环境应由 secret manager
在启动前生成完整配置到 tmpfs 或受限目录,并设置 0600;不要把渲染后的
配置提交到 Git、烘焙进镜像或作为命令行参数传递。
routing_source.token_env 只写环境变量名,实际 token 由进程环境注入。
本地状态
将以下路径放在持久盘,并限制目录权限为 0700:
- 授权快照缓存;
- 路由快照缓存(snapshot 模式);
- 用量 outbox。
快照缓存与 outbox 不包含原始客户端 key 或 Provider key,但仍包含租户、 key ID、后端与流量元数据,必须按敏感运行数据保护。
3. 入口与健康面
- 生产 API listener 必须配置
[server.tls],证书至少支持 TLS 1.2; - health listener 与 API listener 使用不同端口;
- health listener 仅向本机探针、私网负载均衡器或 DNS controller 开放;
- 不要把
/healthz、/readyz暴露到公共互联网; - 私有 CA 通过
SCOOTGATE_EXTRA_CA_CERTS提供,不增加跳过校验的开关。
流量入口只依据 /readyz 接流:
/healthz表示进程存活;/readyz还要求授权快照可用、路由表可用且节点不在排空;- snapshot 路由控制面超过
max_stale_secs未成功刷新时,节点转为 unready,但继续使用最后一份有效路由表服务已有流量。
4. 强制代理出口
- 为每个节点或节点组分配专用代理服务身份,不复用个人账号;
- 跨不可信网络访问代理时启用
egress.tls = true; - 代理 TLS 和 Provider TLS 都必须使用可信根验证;
connect_timeout_ms应小于整体response_head_timeout_ms;- 上线前主动停止代理,确认请求返回 502 且 Provider 没收到连接。
ScootGate 不提供直连回落。代理不可用时降低成功率是预期的安全行为。
5. 模型与容量
公共模型名
公共模型名是客户端契约,应保持稳定;真实 Provider 模型名只放在 target 中。 不同协议必须使用不同模型池,不做 OpenAI 与 Anthropic 的隐式互转。
权重与能力
weight表示相对容量/成本意图,不等于严格流量百分比;- 默认
weighted_p2c会同时考虑权重、节点在途数、EWMA 首包延迟和慢启动; - 只有确认 target 支持时才开启
supports_stream、supports_tools、supports_json_schema; - 新后端先以较低权重加入,观察首包延迟、429 和 5xx 后再逐步提高。
在途上限
max_inflight_per_node 应取 Provider 配额、代理容量和节点内存三者中的保守值。
不要把它当作全局配额:每个 ScootGate 节点独立维护本地在途数。
6. 故障隔离与重试
- 保持
max_pre_send_attempts = 2:总共最多两次选择; - 只有连接代理或 Provider TLS 失败等
DefinitelyNotSent场景允许改选一次; - 请求字节可能发出后(
PossiblySent)绝不重试; - 401/403 表示网关持有的 Provider 凭据失效,整个 backend 会被停用;
- 429 原样返回,并让 backend 进入短暂冷却;
- 熔断半开探测固定单飞,避免恢复瞬间放大流量。
不要在外层反向代理、SDK 或 service mesh 中对 POST Provider 请求增加自动重试, 否则会绕过 ScootGate 的发送状态判断并产生重复计费风险。
7. static 与 snapshot 的选择
| 条件 | 推荐模式 |
|---|---|
| 单节点、路由很少变化、由配置发布系统管理 | static |
| 多节点、频繁调整后端/权重、需要集中控制 | snapshot |
两种模式严格互斥:
static:本地必须声明[backends]与[models];snapshot:本地禁止声明 backends/models,只保留 credentials;- 不存在字段级合并、局部覆盖或“本地兜底”。
snapshot 发布必须遵守:
- 版本严格递增;
- 每份快照完整包含全部 backends/models;
- 先在控制面完成引用与协议校验,再发布;
- MQTT 只发送
{"version": N}提示,真实配置仍由受认证 HTTPS 拉取; max_stale_secs >= poll_interval_secs,并与流量摘除窗口匹配。
坏快照会被整体拒绝,不会覆盖最后一份有效表。
8. 用量 outbox 与 MQTT
- outbox 必须位于持久盘,而不是容器临时层;
max_outbox_bytes必须小于卷可用容量,并预留快照、日志与文件系统空间;- 控制面按
event_id去重;QoS 1 和重启重放都可能产生重复投递; - 监控 health 响应中的
pending_events、pending_bytes、oldest_event_age_secs与dropped_events; - broker 故障时数据面继续服务;outbox 满后按
drop_newest丢新并告警; - token 只取 Provider 原生 usage;缺失保持
null,不得按响应字节估算。
该通道固定 authoritative = false,不能直接作为权威账单。
9. 发布与回滚
推荐按以下顺序滚动:
- 在预发布节点验证配置解析、代理失败、TLS、授权与模型目录;
- 新节点启动后等待
/readyz为 200; - 逐步加入流量并观察 429、5xx、熔断、首包延迟和 outbox;
- 再排空旧节点;
- snapshot 模式先发布新凭据到节点,再发布引用该凭据的新快照;
- 回滚时发布更高版本的“回滚内容”,不要重放旧版本号。
请求在开始时固定使用一份路由表;热更新不会改变进行中的请求。
10. 上线前故障演练
| 注入故障 | 必须观察到的结果 |
|---|---|
| 无授权快照冷启动 | /readyz 非 200,客户端 key 被拒绝 |
| 吊销客户端 key | 新授权快照生效后返回 401 |
| 停止出站代理 | 请求返回 502,Provider 无直连连接 |
| Provider TLS 不可信 | 请求失败,无跳过校验回落 |
| Provider 401/403 | 客户端收到 502,backend 被停用 |
| Provider 429 | 当前 429 原样返回,后续流量短暂避开该 backend |
| 非法路由快照 | 版本不切换,最后有效表继续服务 |
| 路由控制面超期不可达 | /readyz 转非 200,旧表继续服务 |
| MQTT 不可达 | 请求继续服务,outbox 积压增长 |
| outbox 达上限 | 新事件丢弃、计数增长,数据面继续服务 |
| SIGTERM | 节点先排空并在 grace period 内退出 |
11. 生产检查清单
- API listener 使用可信 TLS 证书;
- health listener 未暴露公网;
- 网络策略禁止 Provider 直连;
- 代理、控制面与 MQTT 使用专用服务身份;
- Provider 凭据只来自环境或只读文件;
- 配置文件与状态目录权限已收紧;
- 公共模型名、target 能力与协议已核验;
- 每 backend 在途上限与 Provider 配额匹配;
-
/readyz已接入入口摘除; - snapshot 版本、staleness 与回滚流程已演练;
- outbox 容量与四项健康指标已监控;
- 外层组件没有对 Provider POST 请求自动重试;
- 上表中的高风险失败路径已在预发布环境演练。