2026-05-18 22:22:22 +08:00
|
|
|
|
# Real Host Acceptance Runbook
|
|
|
|
|
|
|
2026-05-21 09:16:45 +08:00
|
|
|
|
日期:2026-05-21
|
|
|
|
|
|
|
|
|
|
|
|
## 先读哪几份文档
|
|
|
|
|
|
|
|
|
|
|
|
1. `docs/EXECUTION_BOARD.md`
|
|
|
|
|
|
- 当前 gate、最新阻断、最新 live 真相以它为准。
|
|
|
|
|
|
2. `docs/PRODUCTION_CLOSURE_BOARD.md`
|
|
|
|
|
|
- 看是否已经达到可上线口径,以及哪些只是历史 PASS。
|
2026-05-21 13:37:49 +08:00
|
|
|
|
3. `docs/REAL_HOST_ACCEPTANCE_CHECKLIST.md`
|
|
|
|
|
|
- 每次 real-host 验收先走这一页。
|
|
|
|
|
|
- 适合快速确认红线、三层证据和最短诊断顺序。
|
|
|
|
|
|
4. `docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md`
|
2026-05-21 09:16:45 +08:00
|
|
|
|
- 看已经调通的细节、典型误判点、推荐诊断顺序。
|
2026-05-18 22:22:22 +08:00
|
|
|
|
|
|
|
|
|
|
## 目标
|
|
|
|
|
|
|
|
|
|
|
|
把当前 `CONDITIONAL_APPROVED` 的剩余外部门禁收敛为一套可直接执行的真实宿主验收流程,覆盖:
|
|
|
|
|
|
|
|
|
|
|
|
1. 真实 sub2api 宿主接入探测
|
|
|
|
|
|
2. pack 安装
|
|
|
|
|
|
3. preview/import 验证
|
|
|
|
|
|
4. access preview / access status 验证
|
|
|
|
|
|
5. reconcile 验证
|
|
|
|
|
|
6. rollback smoke
|
|
|
|
|
|
|
|
|
|
|
|
## 前置条件
|
|
|
|
|
|
|
|
|
|
|
|
### 控制面
|
|
|
|
|
|
- `sub2api-cn-relay-manager` 已启动
|
|
|
|
|
|
- `CRM_BASE_URL` 可访问,例如 `http://127.0.0.1:8080`
|
|
|
|
|
|
- 已设置 `CRM_ADMIN_TOKEN`
|
|
|
|
|
|
|
|
|
|
|
|
### 真实宿主
|
|
|
|
|
|
- 已知真实宿主 `HOST_BASE_URL`
|
|
|
|
|
|
- 已知宿主管理认证:
|
|
|
|
|
|
- `HOST_API_KEY` 或
|
|
|
|
|
|
- `HOST_BEARER_TOKEN`
|
|
|
|
|
|
- 至少一个真实 provider key
|
|
|
|
|
|
- 已知 pack 路径,例如 `/app/packs/openai-cn-pack`
|
|
|
|
|
|
|
|
|
|
|
|
## 推荐执行方式
|
|
|
|
|
|
|
2026-05-21 13:37:49 +08:00
|
|
|
|
### 0. 先跑脚本回归自检(避免把 harness 漂移带进真实宿主结论)
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cd /path/to/sub2api-cn-relay-manager
|
|
|
|
|
|
bash ./scripts/test_real_host_scripts.sh
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
- 当前推荐显式用 `bash` 调起,确保在不同机器上不会因为脚本执行位差异把 harness 回归误报成逻辑失败。
|
|
|
|
|
|
- 只有这一步通过后,再继续真实宿主验收。
|
|
|
|
|
|
|
2026-05-18 22:22:22 +08:00
|
|
|
|
### 1. 构建本地容器镜像(适用于代理/离线开发机)
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cd /path/to/sub2api-cn-relay-manager
|
|
|
|
|
|
scripts/build_local_image.sh
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
默认输出:
|
|
|
|
|
|
- 二进制:`bin/sub2api-cn-relay-manager`
|
|
|
|
|
|
- 镜像:`sub2api-cn-relay-manager:local`
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 先 dry-run 检查真实验收参数
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
CRM_BASE_URL=http://127.0.0.1:8080 \
|
|
|
|
|
|
CRM_ADMIN_TOKEN=replace-me \
|
|
|
|
|
|
HOST_NAME=prod-sub2api \
|
|
|
|
|
|
HOST_BASE_URL=https://sub2api.example.com \
|
|
|
|
|
|
HOST_API_KEY=host-admin-key \
|
|
|
|
|
|
PACK_PATH=/app/packs/openai-cn-pack \
|
|
|
|
|
|
PROVIDER_ID=deepseek \
|
|
|
|
|
|
KEYS=sk-live-1,sk-live-2 \
|
|
|
|
|
|
ACCESS_MODE=self_service \
|
|
|
|
|
|
ACCESS_API_KEY=user-gateway-key \
|
|
|
|
|
|
DRY_RUN=1 \
|
|
|
|
|
|
scripts/real_host_acceptance.sh
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 执行真实验收
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
CRM_BASE_URL=http://127.0.0.1:8080 \
|
|
|
|
|
|
CRM_ADMIN_TOKEN=replace-me \
|
|
|
|
|
|
HOST_NAME=prod-sub2api \
|
|
|
|
|
|
HOST_BASE_URL=https://sub2api.example.com \
|
|
|
|
|
|
HOST_API_KEY=host-admin-key \
|
|
|
|
|
|
PACK_PATH=/app/packs/openai-cn-pack \
|
|
|
|
|
|
PROVIDER_ID=deepseek \
|
|
|
|
|
|
KEYS=sk-live-1,sk-live-2 \
|
|
|
|
|
|
ACCESS_MODE=self_service \
|
|
|
|
|
|
ACCESS_API_KEY=user-gateway-key \
|
|
|
|
|
|
scripts/real_host_acceptance.sh
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4. 订阅模式示例
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
CRM_BASE_URL=http://127.0.0.1:8080 \
|
|
|
|
|
|
CRM_ADMIN_TOKEN=replace-me \
|
|
|
|
|
|
HOST_NAME=prod-sub2api \
|
|
|
|
|
|
HOST_BASE_URL=https://sub2api.example.com \
|
|
|
|
|
|
HOST_BEARER_TOKEN=host-bearer-token \
|
|
|
|
|
|
PACK_PATH=/app/packs/openai-cn-pack \
|
|
|
|
|
|
PROVIDER_ID=deepseek \
|
|
|
|
|
|
KEYS=sk-live-1 \
|
|
|
|
|
|
ACCESS_MODE=subscription \
|
|
|
|
|
|
SUBSCRIPTION_USERS=user-a,user-b \
|
|
|
|
|
|
SUBSCRIPTION_DAYS=30 \
|
|
|
|
|
|
scripts/real_host_acceptance.sh
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-19 13:58:03 +08:00
|
|
|
|
### 5. 导入后自动补 access 前置(可选)
|
|
|
|
|
|
|
|
|
|
|
|
当真实宿主需要额外完成“普通用户余额 / key-group 绑定 / 订阅写入 / 缓存失效”等宿主侧动作时,可在 import 完成后插入自定义 hook:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
AFTER_IMPORT_HOOK_COMMAND='bash /path/to/host-access-hook.sh' \
|
|
|
|
|
|
... \
|
|
|
|
|
|
scripts/real_host_acceptance.sh
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
hook 执行时会额外导出:
|
|
|
|
|
|
- `BATCH_ID`
|
|
|
|
|
|
- `BATCH_DETAIL_FILE`(若非 dry-run,会指向 `05a-batch-detail-pre-access.json`)
|
|
|
|
|
|
- `PROVIDER_ID`
|
|
|
|
|
|
- `HOST_BASE_URL`
|
|
|
|
|
|
- `CRM_BASE_URL`
|
|
|
|
|
|
- `ACCESS_MODE`
|
|
|
|
|
|
- `MODE`
|
|
|
|
|
|
- `ARTIFACT_DIR`
|
|
|
|
|
|
|
|
|
|
|
|
标准产物会新增:
|
|
|
|
|
|
- `05a-batch-detail-pre-access.json`
|
|
|
|
|
|
- `05b-after-import-hook.stdout.txt`
|
|
|
|
|
|
- `05b-after-import-hook.stderr.txt`
|
|
|
|
|
|
|
2026-05-18 22:22:22 +08:00
|
|
|
|
## 产物
|
|
|
|
|
|
|
|
|
|
|
|
脚本会把每一步 JSON 响应落到:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
artifacts/real-host-acceptance/<timestamp>/
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
默认文件顺序:
|
|
|
|
|
|
- `01-create-host.json`
|
|
|
|
|
|
- `02-probe-host.json`
|
|
|
|
|
|
- `03-install-pack.json`
|
|
|
|
|
|
- `04-preview-import.json`
|
|
|
|
|
|
- `05-import.json`
|
2026-05-19 13:58:03 +08:00
|
|
|
|
- `05a-batch-detail-pre-access.json`(若拿到了 `batch_id` 且非 dry-run)
|
|
|
|
|
|
- `05b-after-import-hook.stdout.txt` / `05b-after-import-hook.stderr.txt`(若配置了 hook)
|
2026-05-18 22:22:22 +08:00
|
|
|
|
- `06-access-preview.json`
|
|
|
|
|
|
- `07-access-status.json`
|
|
|
|
|
|
- `08-provider-status.json`
|
|
|
|
|
|
- `09-reconcile.json`
|
|
|
|
|
|
- `10-batch-detail.json`
|
|
|
|
|
|
- `11-rollback.json`(若未跳过)
|
|
|
|
|
|
|
|
|
|
|
|
## 通过标准
|
|
|
|
|
|
|
|
|
|
|
|
至少同时满足:
|
|
|
|
|
|
|
|
|
|
|
|
1. `probe-host` 返回宿主版本与 capability 快照
|
|
|
|
|
|
2. `install-pack` 成功
|
|
|
|
|
|
3. `import` 返回 `batch_id`,且 batch/provider 状态不为 `failed`
|
|
|
|
|
|
4. `access-preview` 返回 `available=true` 或 access status 进入:
|
|
|
|
|
|
- `subscription_ready`
|
|
|
|
|
|
- `self_service_ready`
|
|
|
|
|
|
- `fully_ready`
|
|
|
|
|
|
5. `reconcile` 不返回关键失败
|
|
|
|
|
|
6. `rollback smoke` 成功(若本次需要验证回滚链路)
|
|
|
|
|
|
|
2026-05-21 09:16:45 +08:00
|
|
|
|
## 推荐额外落盘的三层证据
|
|
|
|
|
|
|
|
|
|
|
|
为了避免把不同语义混为一谈,真实宿主验收建议每次都额外记录下面三层证据:
|
|
|
|
|
|
|
|
|
|
|
|
1. account 单体视角
|
|
|
|
|
|
- `GET /api/v1/admin/accounts/:id`
|
|
|
|
|
|
- `GET /api/v1/admin/accounts/:id/models`
|
|
|
|
|
|
2. group/普通用户聚合视角
|
|
|
|
|
|
- 用真实可用的普通用户 key 或 subscription managed key 请求 `GET /v1/models`
|
|
|
|
|
|
3. completion smoke
|
|
|
|
|
|
- 用同一条普通用户访问链路请求 `POST /v1/chat/completions`
|
|
|
|
|
|
|
|
|
|
|
|
注意:
|
|
|
|
|
|
- `GET /api/v1/admin/accounts/:id/models` 正确,不等于普通用户 `/v1/models` 一定正确
|
|
|
|
|
|
- `/v1/models` 正确,也不等于 `/v1/chat/completions` 一定正确
|
|
|
|
|
|
- 这三层必须分开记证据、分开下结论
|
|
|
|
|
|
|
2026-05-18 22:22:22 +08:00
|
|
|
|
## 当前门禁解释
|
|
|
|
|
|
|
|
|
|
|
|
- 若以上脚本在真实宿主环境全部通过:
|
2026-05-21 21:19:19 +08:00
|
|
|
|
- 可以把当前项目推进到 **真实环境放行**
|
2026-05-18 22:22:22 +08:00
|
|
|
|
- 若脚本未执行:
|
2026-05-21 21:19:19 +08:00
|
|
|
|
- 仍然不能把最新代码直接视作真实宿主已放行
|
2026-05-18 22:22:22 +08:00
|
|
|
|
- 若脚本执行但失败:
|
|
|
|
|
|
- 失败应被归类为真实宿主兼容性 / 凭据 / 网络 / pack 内容问题,而不是再泛化成“代码是否已完成”
|
|
|
|
|
|
|
|
|
|
|
|
## 注意事项
|
|
|
|
|
|
|
|
|
|
|
|
1. 默认会执行 rollback smoke;若当前环境不允许回滚,设置:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
SKIP_ROLLBACK=1 scripts/real_host_acceptance.sh
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
2. `PACK_PATH` 必须是控制面进程可读路径,不是用户本地概念路径。
|
|
|
|
|
|
3. 如果控制面部署在容器中,确保 pack 目录已经挂载进去。
|
|
|
|
|
|
4. `HOST_API_KEY` 与 `HOST_BEARER_TOKEN` 二选一即可;脚本会自动推导 `auth.type=apikey|bearer`。
|
|
|
|
|
|
5. `ACCESS_API_KEY` 必须使用真实未脱敏的普通用户 gateway key;不能直接复用数据库/列表接口中的展示值。
|
|
|
|
|
|
6. 真实宿主初始化只会准备管理员账号;普通用户账号/密码不会自动生成,验收前必须显式创建并留存可复用凭据。
|
|
|
|
|
|
7. `self_service` 验证除普通用户 key 外,还需要该 key 绑定目标 group;若目标 group 是标准计费组,还需要用户侧具备可用余额,否则 `/v1/models` 可能从“未授权”转为 `INSUFFICIENT_BALANCE`。
|
|
|
|
|
|
8. `subscription` 验证需要目标 group 本身是 `subscription` 类型,并且完成“普通用户订阅分配 + 普通用户 key 绑定该 group”;仅有管理员主体或未绑定 key 不足以通过 `/v1/models`。
|
|
|
|
|
|
9. 若需要验证 `reconcile` 收敛,优先在干净宿主场景或隔离 group 下执行,避免历史残留资源把结果污染成 `status=drifted` / `extra_count>0`。
|
2026-05-19 20:21:21 +08:00
|
|
|
|
10. `scripts/import_remote43_provider.sh` 现已内置 remote43 的 subscription 验收补全动作:会根据 import batch 自动解析目标 group,执行“普通用户最低余额补齐 + key/group 绑定 + user_subscriptions upsert + 定向 Redis 缓存失效(auth / balance / subscription)”,并把 SQL / host state 证据写入 artifact 目录。
|
|
|
|
|
|
11. 当 CRM 进程与 operator 到 host 的访问地址不一致时,优先显式设置 `CRM_HOST_BASE`,避免把 CRM 侧探测地址和本地运维隧道地址混用。
|
2026-05-19 22:24:32 +08:00
|
|
|
|
12. 对 `Upstream service temporarily unavailable` 一类 502,不要先认定是上游聊天链路故障;先看脚本落盘的 `09-models.headers.txt` / `10-models.body.json`。若 `/v1/models` 已返回了别的 provider 模型集(例如 GPT 系列而不是预期的 DeepSeek/Minimax 模型),先检查普通用户 key/group 绑定,也要检查 CRM 导入时是否把 provider 的 `channel_template.model_mapping`、`restrict_models`、`billing_model_source` 一并下发到宿主 channel。
|
2026-05-21 09:16:45 +08:00
|
|
|
|
13. subscription 场景里,closure 最终用于 gateway probe 的 key 是宿主侧 managed key,不一定是请求体里外部传入的 `ACCESS_API_KEY`。如果你拿原始 key 直打 `/v1/models` 收到 `403 not assigned to any group`,先不要判定 CRM 主链路失败。
|
|
|
|
|
|
14. 对“既有 channel 没自动补 `model_pricing`”这类 live 现象,先核对在线 CRM 进程的启动时间与 git 提交时间;之前 MiniMax 的该现象最终被确认是 stale CRM 进程导致,而不是源码缺逻辑。
|
|
|
|
|
|
15. 当 CRM 切换到本机运行时,`PACK_PATH` 也必须切换到控制面本机可读路径;继续沿用远端 `/home/ubuntu/...` 会触发 `stat pack path ... no such file or directory`,这是验收 harness 参数问题,不是导入业务逻辑问题。
|
2026-05-21 14:19:39 +08:00
|
|
|
|
16. 若要把 DeepSeek 的“host `/v1/models`=200 但 host `/v1/chat/completions`=502,而 upstream 直探 `/chat/completions`=200”做成可提 issue 的最小复现,直接运行 `scripts/check_deepseek_completion_split.sh`。它会同时落盘 host `/v1/models`、host `/v1/chat/completions`、upstream `/chat/completions` 三层证据,并在 `summary.json` 里给出 `host_compatibility_gap|upstream_key_quota_issue|unknown` 分类。
|
2026-05-21 21:19:19 +08:00
|
|
|
|
17. 若 reconcile 面对的是“非 latest batch 的同前缀旧账号”,最新代码会把它们记为 `stale_noise_count` / `stale_noise_accounts` 并保留 `raw_extra_count`,而不是继续把它们算进 `extra_count` 造成 drift 误报;因此应优先看 `extra_count` 是否归零,再看 `probe_failures`/`access_status` 是否仍有真实异常。
|
|
|
|
|
|
18. self_service 场景里,普通用户 gateway key 访问宿主 `/v1/models` / `/v1/chat/completions` 时,真实语义是 `Authorization: Bearer <gateway-key>`;若 CRM 的 self_service closure 仍显示 `401/403 broken`,优先排查 gateway probe 是否错误复用了 `x-api-key`。
|
|
|
|
|
|
19. fresh-host 管理员 bearer token 过期时,最前面的 `POST /api/hosts` / `probe-host` 可能直接表现成 CRM 侧 `502`。遇到这类现象,先刷新 host bearer token,再继续验收,不要先把它归因为最新代码故障。
|
|
|
|
|
|
20. shared fresh-host 上若 `05-import.json` / `07-access-status.json` 已经 ready,而 `09-reconcile.json` 仍是 `status=drifted`,优先把它解释为历史残留资源噪音;PRD 首版放行判断应以 import/access 闭环是否打通为主。
|
2026-05-21 09:16:45 +08:00
|
|
|
|
|
|
|
|
|
|
## 建议固定执行的快速诊断顺序
|
|
|
|
|
|
|
|
|
|
|
|
1. 先看环境
|
|
|
|
|
|
- CRM 是否是最新提交对应的在线进程
|
|
|
|
|
|
- `PACK_PATH` 是否是 CRM 本机可读路径
|
|
|
|
|
|
- `CRM_HOST_BASE` 是否与 CRM 到 host 的实际访问地址一致
|
|
|
|
|
|
2. 再看宿主落库
|
|
|
|
|
|
- account `credentials.model_mapping`
|
|
|
|
|
|
- `GET /api/v1/admin/accounts/:id/models`
|
|
|
|
|
|
- channel `model_mapping/model_pricing/restrict_models/billing_model_source`
|
|
|
|
|
|
3. 最后看普通用户流量
|
|
|
|
|
|
- `/v1/models`
|
|
|
|
|
|
- `/v1/chat/completions`
|
|
|
|
|
|
|
|
|
|
|
|
若需要背景解释、误判案例和已调通经验,直接看:`docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md`
|