Files
sub2api-cn-relay-manager/docs/REAL_HOST_ACCEPTANCE_RUNBOOK.md
2026-05-25 10:48:04 +08:00

16 KiB
Raw Blame History

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 漂移带进真实宿主结论)

cd /path/to/sub2api-cn-relay-manager
bash ./scripts/test_real_host_scripts.sh

说明:

  • 当前推荐显式用 bash 调起,确保在不同机器上不会因为脚本执行位差异把 harness 回归误报成逻辑失败。
  • 只有这一步通过后,再继续真实宿主验收。

1. 构建本地容器镜像(适用于代理/离线开发机)

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 检查真实验收参数

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. 执行真实验收

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. 订阅模式示例

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

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 响应落到:

artifacts/real-host-acceptance/<timestamp>/

Artifact 安全模式

默认:

ARTIFACT_SECURITY_MODE=safe
ARTIFACT_INCLUDE_SECRETS=0

含义:

  • safe:主 artifact 目录只允许落脱敏后的验收证据,可作为仓库内长期保留材料。
  • debug:允许额外生成本地敏感调试材料;这类材料不得作为默认主证据提交或长期保留。
  • ARTIFACT_INCLUDE_SECRETS=1 只允许用于本地短时调试;一旦开启,产物不再默认视为可入库证据。

safe 模式下的硬规则:

  • 不落完整 upstream / managed / user API key
  • 不落完整 bearer token
  • 不落可直接复用的 SQL 明文 key 语句
  • 不落 Redis cache key 原文
  • header 文件必须去掉 Authorization / Cookie / Set-Cookie / x-api-key

默认文件顺序:

  • 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(若未跳过)

remote43 / 本地缩圈脚本若需要额外证据,会在同目录追加:

  • 00-local-key-source.json(只保留 redacted key 指纹/前后缀)
  • 01-runtime-context.json(仅保留 hash 后的 user/admin/managed 身份)
  • 05-subscription-access-prep.summary.json(替代默认明文 SQL
  • 07-redis-targeted-invalidation.json(只保留失效动作结果,不保留 cache key 原文)
  • 08-subscription-group-state.json(已裁剪 key 明文)
  • 21-summary.json / 99-semantic-summary.json(推荐长期保留的摘要证据)
  • 若你是在清理旧目录,而不是生成新验收产物,优先运行:
    python3 scripts/migrate_historical_artifacts.py artifacts/real-host-acceptance
    
    它会把主目录中的历史敏感材料迁到 artifacts/real-host-acceptance-sensitive/,并在原目录生成安全摘要版。
  • 历史目录迁移脚本当前已覆盖两层:
    1. 固定命名标准 artifactruntime-context / key-source / redis invalidation / group-state / sql summary / headers
    2. 复杂业务快照与 JSON-in-string 字段(summary.json99-summary.json99-semantic-summary.json05a-batch-detail-pre-access.json07-access-status.json10-batch-detail.json 以及其中的 DetailsJSON/details_json/probe_summary_json
  • 若迁移后仍看到类似 00-managed-key-corrected.txt 的手工 probe 文本,它们属于非标准人工产物,当前仍建议迁到 artifacts/real-host-acceptance-sensitive/ 或直接删除。

通过标准

至少同时满足:

  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若当前环境不允许回滚设置
SKIP_ROLLBACK=1 scripts/real_host_acceptance.sh
  1. PACK_PATH 必须是控制面进程可读路径,不是用户本地概念路径。
  2. 如果控制面部署在容器中,确保 pack 目录已经挂载进去。
  3. HOST_API_KEYHOST_BEARER_TOKEN 二选一即可;脚本会自动推导 auth.type=apikey|bearer
  4. ACCESS_API_KEY 必须使用真实未脱敏的普通用户 gateway key不能直接复用数据库/列表接口中的展示值。
  5. 真实宿主初始化只会准备管理员账号;普通用户账号/密码不会自动生成,验收前必须显式创建并留存可复用凭据。
  6. self_service 验证除普通用户 key 外,还需要该 key 绑定目标 group若目标 group 是标准计费组,还需要用户侧具备可用余额,否则 /v1/models 可能从“未授权”转为 INSUFFICIENT_BALANCE
  7. subscription 验证需要目标 group 本身是 subscription 类型,并且完成“普通用户订阅分配 + 普通用户 key 绑定该 group”仅有管理员主体或未绑定 key 不足以通过 /v1/models
  8. 若需要验证 reconcile 收敛,优先在干净宿主场景或隔离 group 下执行,避免历史残留资源把结果污染成 status=drifted / extra_count>0
  9. scripts/import_remote43_provider.sh 现已内置 remote43 的 subscription 验收补全动作:会根据 import batch 自动解析目标 group执行“普通用户最低余额补齐 + key/group 绑定 + user_subscriptions upsert + 定向 Redis 缓存失效auth / balance / subscription并把 SQL / host state 证据写入 artifact 目录。
  10. 当 CRM 进程与 operator 到 host 的访问地址不一致时,优先显式设置 CRM_HOST_BASE,避免把 CRM 侧探测地址和本地运维隧道地址混用。
  11. 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_mappingrestrict_modelsbilling_model_source 一并下发到宿主 channel。
  12. subscription 场景里closure 最终用于 gateway probe 的 key 是宿主侧 managed key不一定是请求体里外部传入的 ACCESS_API_KEY。如果你拿原始 key 直打 /v1/models 收到 403 not assigned to any group,先不要判定 CRM 主链路失败。
  13. 对“既有 channel 没自动补 model_pricing”这类 live 现象,先核对在线 CRM 进程的启动时间与 git 提交时间;之前 MiniMax 的该现象最终被确认是 stale CRM 进程导致,而不是源码缺逻辑。
  14. 当 CRM 切换到本机运行时,PACK_PATH 也必须切换到控制面本机可读路径;继续沿用远端 /home/ubuntu/... 会触发 stat pack path ... no such file or directory,这是验收 harness 参数问题,不是导入业务逻辑问题。
  15. 若要把 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 分类。
  16. 若 reconcile 面对的是“非 latest batch 的同前缀旧账号”,最新代码会把它们记为 stale_noise_count / stale_noise_accounts 并保留 raw_extra_count,而不是继续把它们算进 extra_count 造成 drift 误报;因此应优先看 extra_count 是否归零,再看 probe_failures/access_status 是否仍有真实异常。
  17. 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
  18. fresh-host 管理员 bearer token 过期时,最前面的 POST /api/hosts / probe-host 可能直接表现成 CRM 侧 502。遇到这类现象,先刷新 host bearer token再继续验收不要先把它归因为最新代码故障。
  19. shared fresh-host 上若 05-import.json / 07-access-status.json 已经 ready09-reconcile.json 仍是 status=drifted优先把它解释为历史残留资源噪音PRD 首版放行判断应以 import/access 闭环是否打通为主。
  20. 如果 CRM 本身运行在本机,而宿主运行在远端 SSH 隧道后面,必须同时明确 2 个地址:
  • CRM_HOST_BASE:本地 CRM 实际访问宿主时使用的地址,例如 http://127.0.0.1:18089
  • REMOTE_HOST_BASE:远端 SSH 会话内部访问宿主时使用的地址,例如 http://127.0.0.1:18097
  • 两者不能混用;混用后 POST /api/hosts 往往会先在 get host version / probe host capabilities 处直接变成 500
  1. 如果远端同时存在 relaymgrfresh-host 两套容器栈,不要手填 REMOTE_PG_CONTAINER / REMOTE_REDIS_CONTAINER 到旧的 relaymgr 容器。优先按目标宿主端口自动解析同栈的 postgres/redis;否则最容易回到“错库取 key 导致统一 401”。
  2. 若同一个 provider 已在本地 CRM 状态库里跑过多个宿主样本,尾部查询必须带 host_id
  • GET /api/providers/{provider}/status
  • GET /api/providers/{provider}/access/status
  • POST /api/providers/{provider}/access/preview
  • 否则很容易在最后一步收到 provider exists on multiple hosts; host_id is required
  1. 不要把“隧道端口还在 LISTEN”误判成“链路可用”。
  • curl -I --max-time 5 $CRM_HOST_BASE/healthz 完全收不到 header
  • 就应先判定为 tunnel 失活或远端链路异常
  • 这类现象会在 03-import.body.json 中表现为 get host version ... context deadline exceeded
  1. 当前 artifact 安全模式默认是 safe
  • 主目录 artifacts/real-host-acceptance/ 只能保留脱敏后证据
  • 若为了缩圈必须看原始 SQL / headers / token 相关细节,应显式切到 ARTIFACT_SECURITY_MODE=debug 并把产物视为本地敏感材料,不进入仓库主证据区

建议固定执行的快速诊断顺序

  1. 先看环境
  • CRM 是否是最新提交对应的在线进程
  • PACK_PATH 是否是 CRM 本机可读路径
  • CRM_HOST_BASE 是否与 CRM 到 host 的实际访问地址一致
  • 如果走 SSH 隧道,CRM_HOST_BASE 是否真的可在本机 curl -I --max-time 5 读到响应
  • 若脚本还要在 SSH 会话里执行 host probeREMOTE_HOST_BASE 是否与远端主机看到的地址一致
  1. 再看宿主落库
  • account credentials.model_mapping
  • GET /api/v1/admin/accounts/:id/models
  • channel model_mapping/model_pricing/restrict_models/billing_model_source
  1. 最后看普通用户流量
    • /v1/models
    • /v1/chat/completions

若需要背景解释、误判案例和已调通经验,直接看:docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md