Files
sub2api-cn-relay-manager/docs/REAL_HOST_ACCEPTANCE_RUNBOOK.md
2026-05-21 21:45:56 +08:00

251 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Real Host Acceptance Runbook
日期2026-05-21
## 先读哪几份文档
1. `docs/EXECUTION_BOARD.md`
- 当前 gate、最新阻断、最新 live 真相以它为准。
2. `docs/PRODUCTION_CLOSURE_BOARD.md`
- 看是否已经达到可上线口径,以及哪些只是历史 PASS。
3. `docs/PROVIDER_ONBOARDING_PLAYBOOK.md`
- 当你是在“新增 provider”或“宿主升级后重新适配”场景下工作时先看这份。
- 它定义稳定的 onboarding / rerun 顺序,而不是只定义一次性的验收动作。
4. `docs/REAL_HOST_ACCEPTANCE_CHECKLIST.md`
- 每次 real-host 验收先走这一页。
- 适合快速确认红线、三层证据和最短诊断顺序。
5. `docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md`
- 看已经调通的细节、典型误判点、推荐诊断顺序。
## 目标
把当前 `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`
## 推荐执行方式
### 0. 先跑脚本回归自检(避免把 harness 漂移带进真实宿主结论)
```bash
cd /path/to/sub2api-cn-relay-manager
bash ./scripts/test_real_host_scripts.sh
```
说明:
- 当前推荐显式用 `bash` 调起,确保在不同机器上不会因为脚本执行位差异把 harness 回归误报成逻辑失败。
- 只有这一步通过后,再继续真实宿主验收。
### 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
```
### 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`
## 产物
脚本会把每一步 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`
- `05a-batch-detail-pre-access.json`(若拿到了 `batch_id` 且非 dry-run
- `05b-after-import-hook.stdout.txt` / `05b-after-import-hook.stderr.txt`(若配置了 hook
- `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` 成功(若本次需要验证回滚链路)
## 推荐额外落盘的三层证据
为了避免把不同语义混为一谈,真实宿主验收建议每次都额外记录下面三层证据:
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` 一定正确
- 这三层必须分开记证据、分开下结论
## 当前门禁解释
- 若以上脚本在真实宿主环境全部通过:
- 可以把当前项目推进到 **真实环境放行**
- 若脚本未执行:
- 仍然不能把最新代码直接视作真实宿主已放行
- 若脚本执行但失败:
- 失败应被归类为真实宿主兼容性 / 凭据 / 网络 / 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`
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 侧探测地址和本地运维隧道地址混用。
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。
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 参数问题,不是导入业务逻辑问题。
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` 分类。
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 闭环是否打通为主。
## 建议固定执行的快速诊断顺序
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`