docs: clarify sub2api multi-target public exposure (#968)

Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
Lyon
2026-06-26 12:35:47 +08:00
committed by GitHub
parent 754789c43c
commit d1c189e498
2 changed files with 15 additions and 13 deletions
+10 -8
View File
@@ -5,7 +5,7 @@ description: UniDesk Sub2API 平台运维技能。用户提到 Sub2API、sub2api
# UniDesk Sub2API
UniDesk 在 k3s `platform-infra` namespace 运维 Sub2API。D601 是当前默认 active runtime,使用外置 PostgreSQL 和目标级 public exposureG14 由同一 YAML/CLI 控制为 standby predeploy,默认缩容且不运行 sentinel、FRP 或 HTTPS egress proxy。日常操作统一使用 UniDesk CLI,不直接写 Kubernetes 资源或手工调用 Sub2API 管理 API。
UniDesk 在 k3s `platform-infra` namespace 运维 Sub2API。D601 是当前默认 target,使用外置 PostgreSQL 和目标级 public exposureD518 可作为独立 external-active target 与 D601 同时对外服务;G14 由同一 YAML/CLI 控制为 standby predeploy,默认缩容且不运行 sentinel、FRP 或 HTTPS egress proxy。日常操作统一使用 UniDesk CLI,不直接写 Kubernetes 资源或手工调用 Sub2API 管理 API。
**固定入口**: `cd /root/unidesk && bun scripts/cli.ts platform-infra sub2api ...`
@@ -33,11 +33,11 @@ bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <request
- 配置真相是 YAML`config/platform-infra/sub2api.yaml``config/platform-infra/sub2api-codex-pool.yaml`
- 业务策略和具体数值只以 YAML 为准。已有字段的数值调整只改 YAML 并跑 `plan` / `sync --confirm` / `validate`;不要自动补代码硬编码、schema 硬范围、合同测试、单元测试或长期参考文档。配置校验只校验格式、类型、必填和可渲染性,不判断数值策略是否“合理”。
- 本 skill 目录下若存在 `agents/*.yaml`,只作为 skill/agent 展示与调用元数据,不是 Sub2API 或 Codex pool 运行配置;不要在 skill 目录维护第二份账号、capacity、priority、endpoint 或 Secret 配置。
- Runtime target 由 `config/platform-infra/sub2api.yaml` 声明;默认 `D601:k3s` 是 active target`G14:k3s` 是 standby target。master server 只是控制端和消费者,不部署 Sub2API/PostgreSQL/Redis。
- Standby target 不部署本地 PostgreSQL,不运行 sentinel、FRP 管理入口或 HTTPS egress proxy;只能预部署 namespace、NetworkPolicy、Service,以及 replicas=0 的 Sub2API/Redis Deployment。Redis 激活后也只允许 ephemeral cache。External-active target 仍不部署本地 PostgreSQL,必须直连 YAML 声明的外置 DB,使用本地 ephemeral Redis,并且只有在 YAML 启用时才运行 frpc、egress proxy 和目标级 sentinel。
- Runtime target 由 `config/platform-infra/sub2api.yaml` 声明;默认 target 是 `D601:k3s``D518:k3s` 这类 external-active target 必须通过显式 `--target` 选择`G14:k3s` 是 standby target。master server 只是控制端和消费者,不部署 Sub2API/PostgreSQL/Redis。
- Standby target 不部署本地 PostgreSQL,不运行 sentinel、FRP 管理入口或 HTTPS egress proxy;只能预部署 namespace、NetworkPolicy、Service,以及 replicas=0 的 Sub2API/Redis Deployment。Redis 激活后也只允许 ephemeral cache。External-active target 仍不部署本地 PostgreSQL,必须直连 YAML 声明的外置 DB,使用本地 ephemeral Redis,并且只有在 YAML 启用时才运行 frpc、egress proxy 和目标级 sentinel;多个 external-active target 可以并存,不得把一个 target 的 public exposure 当作另一个 target 的替代或回退
- Secret、`~/.codex/config.toml*``~/.codex/auth.json*` 是运行时输入或本地状态,不提交。
- 默认 `~/.codex/config.toml``~/.codex/auth.json` 只作为统一 Sub2API consumer 使用;`config.toml` 必须指向 YAML-selected active target 的 consumer URL,当前由 `codex-pool configure-local --confirm` 写入 D601 target-level public URL`auth.json` 必须使用统一 pool API key。新增上游账号不得覆盖这两个默认文件,只能新增 `config.toml.<profile>` / `auth.json.<profile>` 并在 YAML 里声明。
- 输出只能包含 Secret 路径、长度、preview/fingerprint;禁止打印完整 API key、admin password、JWT secret、TOTP key。
- 输出只能包含 Secret 路径、key 名、presence、fingerprint 和 `valuesPrinted=false`;禁止打印完整 API key、admin password、JWT secret、TOTP key、base64 payload 或可复制的 preview
## 部署与状态
@@ -140,7 +140,7 @@ bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --target D60
`sync --confirm` 会登录 Sub2API admin、创建/更新 group、创建/更新 YAML 中的 `unidesk-codex-*` accounts、创建/复用统一 API key Secret,并部署/更新哨兵资源;它不把既有 managed account 直接恢复为 `schedulable=true`。恢复只由哨兵在读取 Sub2API runtime `schedulable=false` 后触发 recovery probe,并在 marker 命中时执行。`sync` 默认不删除 YAML 中缺席的 managed account。只有明确退役上游时才使用 `sync --confirm --prune-removed` 删除缺席且 `extra.unidesk_managed=true``unidesk-codex-*` account。对 `manualAccounts.protected``sync` 只执行 YAML 显式允许的窄同步;当前允许项是从目标 `egressProxy` 创建/更新 Sub2API internal proxy 记录并绑定 `proxy_id`,以及把受保护手动账号加入当前 `pool.groupName`。它仍不接管该账号凭据、status、schedulable、priority/capacity/loadFactor 或哨兵状态。
`sentinel-image status|build` 管理哨兵 Python 运行环境镜像。镜像由 YAML 的 `sentinel.image` 基础镜像和 `sentinel.sdk.openaiPythonVersion` 派生,发布到目标 runtime 的本地 registry`build --confirm` 会先检查 registry tag,存在则快速复用,不存在才在目标 host 构建并 push。CronJob 启动时只校验 SDK 版本,不在运行时 `pip install`
`sentinel-image status|build` 管理哨兵 Python 运行环境镜像。镜像由 YAML 的 `sentinel.image` 基础镜像和 `sentinel.sdk.openaiPythonVersion` 派生,发布到目标 runtime 的本地 registry`build --confirm` 会先检查 registry tag,存在则快速复用,不存在才在目标 host 构建并 push。CronJob 启动时只校验 SDK 版本,不在运行时 `pip install`目标是否启用哨兵以 `config/platform-infra/sub2api.yaml``sentinel.enabledOnTargets` 为准;未启用的 target 在 `sync`/`validate` 中应显示 `skipped-target-disabled`,不得要求镜像构建、CronJob、Secret 或 state ConfigMap 存在。
`sync --confirm` 同时会按 YAML 渲染账号级哨兵资源,并在 monitor 开启时先确保可复用哨兵镜像存在。当前目标是 `sentinel.monitor.enabled=true` + `sentinel.actions.enabled=true` 的 marker-only 自动冻结/恢复;不要手工 patch CronJob、Secret 或 Sub2API account。若 YAML 新增账号或修改 profile/base URL/API key fingerprint/upstream User-Agent/Responses WebSocket modesync 会从变更前 runtime state 写入 pending probe 记录并立即安排 sentinel probe,但不会把既有账号直接恢复为可调度;只有 sentinel 读取到 Sub2API runtime `schedulable=false` 后执行 recovery probe,且 marker 命中,才恢复 `schedulable=true`。sentinel 冻结/恢复只改 `schedulable=false|true`,不得顺手调用 Sub2API `recover-state` 清除请求路径临时不可调度或其他 runtime backoff。无关账号的既有成功/失败退避不能被重置。若 YAML 下调失败冻结最大窗口,sync 会把仍 active 的旧冻结状态迁移到当前最大窗口内并立即安排 recovery probe,但不会直接解冻。若怀疑某个账号被误判,先用 `codex-pool sentinel-probe --account <accountName> --confirm` 立即触发该账号测量;该命令从现有 CronJob 模板派生一次性 Job,复用同一份 Secret、ConfigMap、OpenAI SDK probe、token/cost 账本和冻结/恢复状态机。
@@ -203,15 +203,15 @@ bun scripts/cli.ts platform-infra sub2api codex-pool expose
bun scripts/cli.ts platform-infra sub2api codex-pool expose --confirm
```
- 由 YAML `publicExposure` 控制。Codex pool 默认公共端是 `publicBaseUrl`master 本地消费端是 `masterBaseUrl`D601 external-active 的目标级 public exposure 在 `config/platform-infra/sub2api.yaml` 中声明。
- 由 YAML `publicExposure` 控制。Codex pool 默认公共端是 `publicBaseUrl`master 本地消费端是 `masterBaseUrl`external-active target 的目标级 public exposure 在 `config/platform-infra/sub2api.yaml` 中声明。
- `expose --confirm` 只为 YAML 指定的 `remotePort` 补 master `frps` allow port,并在 G14 创建/更新 `sub2api-frpc`
- master Caddy site 也由 `publicExposure.masterCaddy` 渲染;`responseHeaderTimeoutSeconds` 必须足够覆盖 Codex `/responses/compact` 长请求,避免 Caddy 先返回 504 而 Sub2API 后台实际稍后成功。具体数值只改 `config/platform-infra/sub2api-codex-pool.yaml`,修改后跑 `codex-pool expose --confirm`,再核对 Caddyfile 中渲染出的 `response_header_timeout`
- master Caddy 的短窗口边缘重试由 `publicExposure.masterCaddy.edgeRetry` 渲染;用于吸收 FRP remotePort 短暂关闭、`connect: connection refused`、EOF 或 connection reset 这类请求尚未稳定到达 Sub2API 的 502。具体 retry 时长、间隔和 `retryMatch` 范围只写 YAML,修改后跑 `codex-pool expose --confirm`,再核对 Caddyfile 中渲染出的 `lb_try_duration``lb_try_interval``lb_retry_match`。不要手工 patch `/etc/caddy/Caddyfile`
- PK01 `/etc/caddy/Caddyfile` 是 Sub2API、LangBot、n8n、HWLAB 等多 YAML 来源共享的 edge artifact。Sub2API apply/expose 只能更新自己的 `# BEGIN unidesk managed sub2api` 块并保留其他 managed blocks若 apply 输出显示 managed block 数异常,先停止 closeout,检查 PK01 Caddy 合并与 validation 结果,不要手工整文件覆盖。
- PK01 `/etc/caddy/Caddyfile` 是 Sub2API、LangBot、n8n、HWLAB 等多 YAML 来源共享的 edge artifact。Sub2API apply/expose 只能更新自己的 managed block 并保留其他 blocks;同一 Sub2API 服务暴露多个 target 时,D601 保留既有 `# BEGIN unidesk managed sub2api`,非默认 target 必须使用 target-scoped owner(例如 `sub2api-d518`),避免 `api.pikapython.com``api2.pikapython.com` 互相覆盖。若 apply 输出显示 managed block 数异常,先停止 closeout,检查 PK01 Caddy 合并与 validation 结果,不要手工整文件覆盖。
- 非幂等 POST 的 round-trip retry 必须收窄到 YAML `retryMatch` 声明的安全路径;普通 `/responses` 上游账号错误仍归 Sub2API failover / temp-unschedulable / sentinel 处理,不用 Caddy 重放整段推理请求来掩盖账号池问题。
- 同一个 FRP TCP 入口同时暴露 OpenAI-compatible API 和 Sub2API 管理 UI `/login`。不要另开第二个管理端口,除非 YAML 明确声明新的暴露决策。
- Sub2API Kubernetes Service 继续保持 ClusterIP。
- D601 external-active 的公开路径是 `client -> PK01 Caddy -> PK01 frps remotePort -> D601 frpc -> Sub2API`,不经过 pikanode,也不经过 master server 反代。PK01 Caddy 下载必须使用 YAML `publicExposure.pk01.caddyDownloadProxyUrl` 指定的 proxy;如果 Caddy 下载慢,先确认 apply 输出里是 `downloadProxy.mode=curl-proxy``api.pikapython.com` 必须先解析到 YAML 声明的 PK01 公网地址,HTTPS 才能作为最终验证。
- External-active target 的公开路径是 `client -> PK01 Caddy -> PK01 frps remotePort -> target frpc -> Sub2API`,不经过 pikanode,也不经过 master server 反代。PK01 Caddy 下载必须使用 YAML `publicExposure.pk01.caddyDownloadProxyUrl` 指定的 proxy;如果 Caddy 下载慢,先确认 apply 输出里是 `downloadProxy.mode=curl-proxy`目标域名必须先解析到 YAML 声明的 PK01 公网地址,HTTPS 才能作为最终验证D601 `api.pikapython.com` 与 D518 `api2.pikapython.com` 应分别验收
## 配置 master Codex 消费端
@@ -239,6 +239,7 @@ bun scripts/cli.ts platform-infra sub2api codex-pool configure-local --confirm
- `sub2api validate`app、PostgreSQL、Redis、service proxy、`NetworkPolicy/allow-all` 和临时跨 Pod PostgreSQL/Redis 连通性检查通过。
- `codex-pool validate`:统一 key 的 `GET /v1/models` 成功,并用 `localCodex.responsesSmokeModel` 跑一次小的 `POST /v1/responses` smokeowner balance / owner concurrency 已满足 YAML 最小值,capacity、WebSocket v2、Sub2API 内置 temporary-unschedulable 开关/规则和 sentinel runtime 状态与 YAML 对齐;`validation.gatewayResponsesRecent` 汇总最近 6 小时普通 `/responses``/v1/responses` 的 failover、forward failure、最终 4xx/5xx、慢 final error 与 `context canceled` 证据,`validation.gatewayCompactRecent` 单独汇总 `/responses/compact` 证据。若当前 Responses smoke `ok=true` 但 recent 字段 `degraded=true`,先区分是历史窗口残留还是新的 request id 正在失败;长期判定见 `docs/reference/platform-infra.md`
-`publicExposure.enabled=true`,确认 FRP path 可用;`expose --confirm` 会用未带 key 的 public `/v1/models` 401 作为网关可达性探针。
- 多 target 同时启用 public exposure 时,必须分别验证每个 target 的 root、`/health`、未带 key `/v1/models` 401,以及各自 `codex-pool validate --target <id>`;一个域名可用不能替代另一个域名的验收。
- 若目标声明了 `egressProxy.enabled=true`,确认 proxy Deployment/Service readySub2API 和 sentinel env 与 YAML 对齐,并通过 YAML 声明的 health URL 完成代理出站探针。
如果要证明真实模型请求可用,使用最小 `/v1/responses` 或等价 Codex smoke。不要把 group-level `/v1/models` 成功解释成每个上游 account 都健康。
@@ -253,6 +254,7 @@ bun scripts/cli.ts platform-infra sub2api codex-pool configure-local --confirm
- 手动 OAuth/API-key 账号 WebUI account test 正常,但 PC Codex 客户端通过统一 key 访问 `/responses` 返回 503 且 trace 是 `account-select-failed` / `no available accounts`:按“受保护手动账号代理与分组绑定”小节确认 `manualAccounts.protected[].groupBinding.source: pool-group`,跑 `codex-pool sync --target D601 --confirm` 后用 `codex-pool validate --target D601 --full` 复测统一 key。
- Sub2API 卡在 `wait-postgres` / `wait-redis` 或服务内大量 `context deadline exceeded`:先跑 `sub2api status``networkPolicy.ok`,再跑 `sub2api validate``postgresCrossPodPgIsReady` / `redisCrossPodPing`;缺失或异常时用 `sub2api apply --confirm` 恢复受控 `NetworkPolicy/allow-all`,不要保留手工 iptables bypass 作为长期修复。
- pool key 401:跑 `codex-pool sync --confirm` 重建 Sub2API key 与 k3s Secret 绑定,再跑 `codex-pool validate`
- pool key、admin password 或 k8s Secret `.data` 被 stdout、日志、issue 或本地 transcript 打印时,按泄露处理:撤销对应 Sub2API key 或 token,删除/重建受影响的 target Secret,通过 `codex-pool sync --target <id> --confirm` 或相应 YAML sourceRef 重新下发,再用 fingerprint、presence 和 `valuesPrinted=false` 作为 closeout 证据;不要复述旧值或新值。
- 运行中过去的验证探针残留:只用 `codex-pool cleanup-probes --confirm` 清理 `unidesk-probe-*` 临时资源;不要把真实 managed account 删除当作探针清理或可用性恢复。
- FRP 不通:先看 `codex-pool expose --confirm` 输出的 `masterFrps``masterCaddy``sub2api-frpc` 和 public 401 probe;需要低层证据时只用 `trans G14:k3s` 做 bounded 查询。
- D601 external-active 的 `api.pikapython.com` 不通:先区分 DNS/TLS/Caddy/FRP/Sub2API。DNS 未解析到 YAML 声明的 PK01 地址时,Caddy ACME 会失败,`https://api.pikapython.com` 不能算完成;可用 PK01 loopback FRP 端口和 PK01 公网 remotePort 证明 D601 FRP 数据路径,但最终仍要等 DNS 生效后重跑 HTTPS health、`/v1/models``/v1/responses`