title: One API(香港)经海外中继接入 Bedrock(Claude + GPT-5.6)—— 修正版方案 + 三步走 Runbook
date: 2026-09-15(2026-09-16 补 ASG+ALB 实测;2026-09-16 再补 mantle/Claude 与香港 Lambda 实测更正)
status: 已在测试账号(香港 EC2 + 东京 ASG 2 台中继 + ALB;香港 ap-east-1 Lambda 复测地域门)全链路实测,所有测试资源已删除
based_on: 合作者的 word文档(2026-09-10)与 Bedrock-OneAPI-双机部署runbook.md(2026-09-13),两份原文未改动
在线地址: https://projects.zikunzh.people.aws.dev/projects/bedrock-proxy/

⬇ 下载全部文件 (zip)

目录

2026-09-16 重要更正与风险提示(务必先读) 1. Claude 也能走 bedrock-mantle:路径是 /anthropic/v1/messages(Anthropic Messages 面),模型用 mantle 的短 ID(不带版本后缀、不带 us. 前缀),如 anthropic.claude-opus-4-8anthropic.claude-sonnet-5。此前 word文档/本文判定"Claude 不在 mantle"是因为拿 runtime 的长版本 ID(...-20250929-v1:0)去打 mantle,mantle 不认这种 ID → 404,被误读成"端点不支持 Claude"。实测(2026-09-16,us-east-1 与香港 ap-east-1 Lambda)均 200。 2. 地域门按"模型/厂商"判,不按"端点"判:香港出口下,OpenAI 系在 mantle 和 runtime 两个端点都被挡(400 unsupported countries);Claude 在两个端点都能过(200)。 3. ⚠️ 但 Claude 从香港能用属"意外",理论上不该放行,随时可能被收紧。生产架构不要依赖"Claude 从香港直连"这条——Claude 也走海外中继,既统一出口/鉴权/模型名,又不受这条政策变化影响。GPT 是明确被挡的,本来就必须走中继。

0. 一页结论

问题 结论(2026-09-15 实测)
香港出口到底能不能调 GPT 系(openai.*)从香港被挡(400 Access to OpenAI models is not allowed from unsupported countries,mantle 与 runtime 两端点都挡);Claude 从香港目前是通的(runtime Converse/Invoke 与 mantle /anthropic/v1/messages 均 200,2026-09-16 香港 Lambda 复测)。⚠️ Claude 能用是意外、随时可能收紧,架构不要依赖它。
Claude 能不能走 mantle 。路径 /anthropic/v1/messages,用短 ID(anthropic.claude-opus-4-8 等)。这是对 word文档"Claude 不在 mantle"的更正(原判断用错了模型 ID 形式)。对本方案的影响:中继上 Claude 的 LiteLLM 后端可指向 runtime Converse(现方案,稳)或 mantle /anthropic/v1/messages(等价可选);One API 侧走 OpenAI 类型渠道不变,格式转换仍由 LiteLLM 做。
中继放哪 东京、新加坡、美东都能过门。实测经东京中转的总时延(53+150=203ms)与香港直连美东(205ms)几乎相同,中继不增加延迟;经新加坡多约 55ms(33+225=258ms),因为新加坡到美东要绕行。推荐东京,放美东效果等价(中继到 Bedrock 同区),新加坡也可用。
中继怎么做 一台 Ubuntu EC2 + 3 个容器(nginx / aws-sigv4-proxy / LiteLLM),挂 IAM 角色,无任何静态 AWS 密钥。一段脚本粘进 EC2"用户数据",开机 ~80 秒自动就绪。
One API 侧改什么 只加两个 OpenAI 类型渠道(GPT、Claude 各一)+ 信任中继证书 + 把"失败重试次数"从 0 改成 3。One API 原生的 "AWS Bedrock" 渠道类型不能用(源码确认)。
高可用 推荐:中继层 = Auto Scaling Group(≥2 台、跨可用区、启动模板带安装脚本)+ ALB 固定入口;香港 One API 每个模型对同一 ALB 建两个渠道 + 失败重试 3 次。实测直接终止一台中继:ALB 16 秒摘除、ASG 3 分 45 秒补齐两台;期间 20 个请求 19 个成功(唯一失败是终止后 2 秒内那个)。滚动升级(实例刷新)期间请求零失败。香港 One API 不管一台还是多台,都只指向 ALB 域名。
原文档最大的坑 ① 中继 IAM 角色缺 bedrock-mantle:CreateInference,GPT 路 403;② nginx 镜像自带的默认站点会抢 80 端口,ALB 健康检查 404;③ TLS 证书从哪来、One API 如何信任没交代(One API 不会跳过证书校验);④ GPT 渠道"密钥任意"等于中继裸奔;⑤ 高可用只讲了 LB,没讲实例挂了谁来补(需要 ASG)。全部已修正并实测。

本文所有文件在 deliverables/ 目录下,见 §6 文件清单。三步走每一步都给了 控制台点选版命令行版,两者任选其一。

读者定位:熟悉 Linux / docker / nginx / TLS 的运维同学,但没碰过 AWS。所以 docker compose、nginx upstream、openssl 自签这些不展开;AWS 专有名词在 §0.1 用你们熟悉的东西对照解释一遍,正文里第一次出现时也会点一下。

0.1 AWS 概念对照(懂运维、不懂 AWS 看这个就够)

AWS 名词 相当于你熟悉的 本方案里怎么用
Region(区域) 一个独立机房集群,如 ap-northeast-1 东京、ap-east-1 香港、us-east-1 美东 EC2 建在东京;Bedrock 端点调美东 控制台右上角要先切区域再找资源;香港区是"可选加入"区,新账号要先在账户设置里开启
VPC / 子网 / 可用区(AZ) 私有网络 / 网段 / 同城不同机房 全用每个区自带的默认 VPC,不用自己建;两台中继放不同 AZ 每个区默认 VPC 网段都是 172.31.0.0/16,所以跨区 VPC 对等会冲突,本方案走公网 TLS 而不是内网
安全组 (Security Group) 实例级有状态防火墙(只写允许规则,默认拒绝入站、放行出站) 中继只放 香港机IP/32 → 443 规则改了立即生效,不用重启;来源可以填"另一个安全组"(ALB → 中继 80 就这么写)
EC2 / AMI / 实例类型 虚拟机 / 系统镜像 / 规格 Ubuntu 24.04 官方 AMI,t3.small(2C2G) t3.micro 只有 1G,LiteLLM 会 OOM
用户数据 (User data) cloud-init 开机脚本 install-relay.sh 整段贴进去,开机自动装好 只在首次启动跑一次;以 root 执行;日志在 /var/log/cloud-init-output.log
IAM 角色 + 实例配置文件 (Instance Profile) 给"这台机器"发的短期凭证,类似 k8s 的 ServiceAccount / IRSA 中继挂 bedrock-relay-role,容器里的 SDK 自动拿到临时 AK/SK,机器上不存任何密钥 控制台建角色时会自动建同名实例配置文件;CLI 要手动建并关联(脚本已做)
IAM 策略 RBAC 的规则 JSON bedrock:Invoke* + bedrock-mantle:CreateInference 权限改动对已在跑的实例立即生效,不用重启
IMDS / 元数据服务 / 跃点限制 169.254.169.254 上的本机信息接口,角色凭证从这里取 容器(docker bridge,多一跳)要取凭证 → 跃点限制设 2 默认 1,忘了改的话 LiteLLM/sigv4-proxy 报 "no credentials",nginx 却是好的,很迷惑
Session Manager 浏览器/CLI 里的 SSH,走 AWS 内部通道 不开 22 端口、不配密钥对也能进机器 需要角色带 AmazonSSMManagedInstanceCore(脚本已加);Ubuntu AMI 自带 agent
弹性 IP (EIP) 固定公网 IP 生产给中继绑一个,否则 stop/start 会换 IP 绑完要重签证书(gen-cert.sh),并更新香港机安全组/LB 配置
ALB 托管的七层 LB(类似 nginx/HAProxy as a service),自带健康检查、多 AZ 方案 B 用;方案 A 不用 空闲超时默认 60s 要改 300s;POST 失败不会重试到另一台
目标组 (Target Group) LB 的 upstream 列表 + 健康检查配置 两台中继:80,/healthz 最短检查间隔 5 秒
ACM 证书管理器 ALB 的证书必须放在 ACM;没域名就"导入"自签证书 自签证书 CN 限 64 字符,ALB 域名太长只能放 SAN
Bedrock / 模型访问权限 托管的模型推理 API 美东 us-east-1;控制台"模型访问权限"页要先勾 Anthropic 与 OpenAI 端点分两个:bedrock-runtime(原生 Converse/Invoke)和 bedrock-mantle(OpenAI 兼容),IAM 动作也不同
inference profile(us./global. 前缀) 跨区路由的模型别名 runtime 端点必须用带前缀的 ID;mantle 端点只认裸 ID 记反了分别报 400 invalid model / 404
启动模板 (Launch Template) 虚拟机的"镜像 + 规格 + 网卡 + 开机脚本"模板,带版本号 ASG 用它拉起中继;改配置 = 发新版本 用户数据要 base64;Version=$Latest 才会跟着新版本
Auto Scaling Group (ASG) 保证"始终有 N 台按模板起的机器"的控制器,类似 k8s 的 ReplicaSet 最少 2 台跨 AZ;健康检查类型选 ELB,ALB 判定不健康就换新机 换出来的是新 IP,所以前面必须有 ALB 这种固定入口;health-check-grace-period 要 ≥ 开机脚本时长(180s)
实例刷新 (Instance Refresh) 滚动发布 升级中继:发新版启动模板 → start-instance-refresh,一台一台换 MinHealthyPercentage=50 保证始终有一台在线;实测期间请求零失败
CloudWatch 告警 → 恢复/重启实例 监控 + 自愈动作 给中继加 StatusCheckFailed 两个告警 与容器 restart: always 配合就是"EC2 崩了自动回来"
标签 (Tag) label 所有资源打 Project=bedrock-relay-poc 99-cleanup.sh 按标签删,别漏打

1. 对 word文档 与 runbook 的逐条修正

1.1 word文档

# 原文 问题 修正
A1 §1/§5:Claude 走 One API 的 AWS Bedrock 渠道,base_url 指向中继 /claude 不成立。源码 relay/adaptor/aws/adaptor.go:只用 Region + AK/SK 构造 bedrockruntime.New(...),没有任何 endpoint/base_url 入口;模型表 relay/adaptor/aws/claude/main.go 停在 claude-3-5,没有 Sonnet 4.5。runbook 已改,word文档 未同步。 Claude 用 OpenAI 类型渠道 → 中继 LiteLLM → Bedrock Converse(本文方案)。
A2 §2:两个端点同一把凭证即可 凭证是同一把,但 IAM 动作不同:mantle 端点要 bedrock-mantle:CreateInference,只给 bedrock:InvokeModel* 实测 403。 角色策略见 cli/bedrock-invoke-policy.json
A3 §3 nginx:if ($http_x_relay_auth = ...) 做闸门;§5 说"两个渠道都加自定义头 X-Relay-Auth" One API 原版渠道 没有自定义请求头功能,X-Relay-Auth 发不出去,闸门永远 401。 用 One API 一定会发的 Authorization: Bearer <渠道密钥> 当闸门:GPT 路由 nginx 校验后再交给 sigv4-proxy 换成 SigV4;Claude 路由 LiteLLM 用 master_key 校验。
A4 §3 nginx 无 client_max_body_size nginx 默认 1MB,长 prompt / 带图片请求会 413。 设 20m。
A5 §3 rewrite ^/mantle/v1/(.*)$ /openai/v1/$1 vs runbook base_url=/mantle/openai + rewrite ^/mantle/(.*)$ /$1 两份文档路径约定不一致。 统一用 runbook 的:渠道 base_url https://<relay>/mantle/openai,nginx 去掉 /mantle 前缀。
A6 §4:"地域门只挡中国大陆/香港" 精确化:按模型/厂商判,不按端点判——只挡 OpenAI 系;Claude 在 runtime 与 mantle 两端点从香港都 200(2026-09-16 香港 Lambda 复测)。 见 §0。但 Claude 从香港能用属意外,随时可能收紧,架构不要依赖;为了统一出口/鉴权/模型名、并规避这条政策风险,Claude 也走中继。
A10 §5/§8:"Claude 不在 Bedrock 的 /openai/v1,也不在 /anthropic/v1/messages(runtime+mantle 均 404)" 对 mantle 的结论错了:错在拿 runtime 的长版本 ID 去打 mantle。mantle 只认短 ID,用 anthropic.claude-opus-4-8bedrock-mantle .../anthropic/v1/messages 实测 200。(runtime 侧 Claude 确实只认原生 Converse/Invoke,这部分不变。) 见 §0 更正与 §1.3。
A7 §1 高可用版:ALB + WAF + ECS Fargate ECS Fargate 对不熟 AWS 的团队过重;ALB 保留,但原文没说"实例挂了谁来补",而且 ALB 对宕机节点有 10~20 秒 502 窗口且不会重发 POST(实测)。 §3.3:ASG(自动补机)+ ALB + One API 双渠道重试兜窗口。
A8 §3/§6:ssl_certificate /etc/nginx/tls/fullchain.pem 没说证书从哪来。客户多半没域名;One API 的 Go HTTP 客户端不跳过证书校验(源码无 InsecureSkipVerify),自签证书直接用会握手失败。 中继自签证书(脚本自动生成),One API 容器用 SSL_CERT_FILE 挂信任包(add-relay-cert.sh 一条命令)。
A9 §6 compose 用自定义 entrypoint 跑 envsubst 不必要。 nginx 官方镜像自带 /etc/nginx/templates/*.template → conf.d/ 的 envsubst 机制。

1.2 Bedrock-OneAPI-双机部署runbook.md

# 原文 问题 修正
R1 A 节:IAM Role 含 bedrock:InvokeModel + InvokeModelWithResponseStream bedrock-mantle:CreateInference,GPT 路 403(实测)。 补上,见策略文件。
R2 B 节渠道 A:密钥"任意(mantle 侧由 sigv4-proxy 签,忽略此值)" 中继 GPT 路 零鉴权:安全组之外没有第二道防线,且安全组放的是整台香港机。 nginx 校验 Bearer 必须等于 RELAY_GPT_KEY(错钥匙实测 401)。
R3 nginx 只 listen 443 上 ALB 时要 listen 80,但 nginx 镜像自带 conf.d/default.conf 也监听 80 并抢默认站点 → ALB 健康检查 /healthz 得到 404(实测 Target.ResponseCodeMismatch)。 模板文件名用 default.conf.template,覆盖镜像自带文件。
R4 ./tls 挂 fullchain.pem / privkey.pem 同 A8。 同 A8。
R5 未提 EC2 元数据跳数 容器内(docker bridge)拿实例角色凭证,IMDSv2 hop limit 必须 ≥2,否则 LiteLLM/sigv4-proxy 拿不到凭证。 启动参数 HttpPutResponseHopLimit=2(控制台:高级详情 → 元数据响应跃点限制 = 2)。
R6 验证命令给 Claude/GPT 都发 max_tokens GPT-5.6 经 mantle 与 OpenAI 官方一致:不接受 max_tokens,要 max_completion_tokens(实测 400 unsupported_parameter)。Claude 经 LiteLLM 两者皆可。 文档/测试脚本已改;若客户端改不了,可让 GPT 走 LiteLLM 的 gpt-5.6-converse(实测 200,见 §3.2)。
R7 没提模型重定向 若要给用户暴露 gpt-5.6 这样的短名,除了填"模型重定向",别名也必须写进渠道的模型列表,否则 503 "无可用渠道"(实测)。 setup-channels.sh 已处理。
R8 未提失败重试 One API 的 RetryTimes 默认 0,是系统设置项(设置 → 运营设置 → 失败重试次数),不是环境变量。 设为 3(脚本已自动设置)。
R9 litellm:main-latest 滚动 tag,重装可能换版本。 main-stable
R10 "验证状态标注"最后一行:双机端到端未跑过 本次已跑通,见 §5 实测表。
R11 "地域门只挡大陆/香港(香港未亲测)" 已亲测:GPT 挡、Claude 不挡。 同 A6。
R12 多节点故障转移用 "LB nginx + 2 后端" 结论对(实测零 502),但 LB nginx 放哪、后端 IP 变了谁改、后端挂了谁补 都没说:单独一台 nginx LB 是新的单点,写死 IP 又和自动补机冲突。 生产用 ASG + ALB(§3.3);nginx LB 只作为"没有 ASG 的过渡方案",以 sidecar 形式跟随每个 One API 节点。
R13 litellm.yaml 里 master_key 明文 sk-relay-claude 建议随机。 脚本自动生成 32 位随机,也可预先指定。

1.3 原文 §7 "待确认" 的答案

  1. One API 的 AWS Bedrock 渠道能否指向中继? 不能(源码确认,见 A1)。Claude 必须走 OpenAI 渠道 + 中继上的转换盒。转换盒用 LiteLLM(本文)或 aws-samples/bedrock-access-gateway 都行;LiteLLM 更通用、社区大。
  2. 账号内实际可用模型 IDaws bedrock list-foundation-models --region us-east-1 实查,2026-09-15):
用途 端点 要填的 ID
GPT-5.6 走 mantle /openai/v1 bedrock-mantle 裸 IDopenai.gpt-5.6-sol / -luna / -terraopenai.gpt-6-astra
Claude 走 runtime Converse(LiteLLM,现方案) bedrock-runtime 必须带前缀的 inference profileus.anthropic.claude-sonnet-4-5-20250929-v1:0us.anthropic.claude-opus-4-8us.anthropic.claude-haiku-4-5-20251001-v1:0…(global. 前缀亦可)
Claude 走 mantle /anthropic/v1/messages(可选后端) bedrock-mantle 裸/短 IDanthropic.claude-opus-4-8anthropic.claude-opus-5anthropic.claude-sonnet-5anthropic.claude-haiku-4-5anthropic.claude-fable-5anthropic.claude-opus-4-7bedrock-mantle .../v1/models 实查,2026-09-16;注意此列表与 runtime 的可用型号/版本不完全相同)
GPT 走 runtime Converse(LiteLLM 备选) bedrock-runtime us.openai.gpt-5.6-sol

规则:mantle 只认短 ID,runtime 只认带前缀 profile ID。Claude 在 mantle 走 Anthropic Messages 面(/anthropic/v1/messages),不支持 OpenAI 的 /v1/chat/completions(实测 400 does not support the ... API);GPT 反之走 /openai/v1/chat/completions。列 mantle 模型清单需 bedrock-mantle:ListModels 权限(调用只需 CreateInference)。 3. 香港到底挡不挡:见 §0。 4. 中继放哪(从香港 EC2 与东京/新加坡 EC2 实测 TCP 建连时延):

路径 TCP 备注
香港 → 新加坡 33ms
香港 → 东京 53ms
香港 → 美西 us-west-2 145ms
香港 → 美东 us-east-1 205ms
东京 → 美东 bedrock-runtime / mantle 150ms
新加坡 → 美东 225ms
端到端(香港 One API → 东京中继 → 美东 Bedrock → 回) GPT 0.9~1.1s,Claude 1.5~1.9s 短问答整体耗时,含模型推理

端点必须留在 us-east-1(GPT-5.6 的 mantle OpenAI 兼容端点只有美东,runbook 已实测)。 5. 要不要 Bedrock API Key(Bearer):不需要。IAM 角色 + sigv4-proxy/LiteLLM 自动签名,没有任何长期密钥要保管。API Key 只在"中继不在 AWS 上"时才有意义。


2. 目标架构(修正版)

香港(客户现有 One API,1 台或多台都一样,不动业务逻辑)        东京
┌──────────────────────────────────────────┐        ┌─────────────────────────────────────────────────────────┐
│ One API 节点 ×N                           │        │ ALB(固定域名,HTTPS 443,安全组只放香港各节点出口 IP)      │
│  渠道 GPT-5.6  ×2  OpenAI类型             │ HTTPS  │   目标组 :80 /healthz 5s×2                                │
│    base_url https://<ALB>/mantle/openai ──┼───────►│   ┌──────────────── Auto Scaling Group(min 2,跨 AZ)──────┐│
│    密钥 = RELAY_GPT_KEY                   │        │   │ 中继 EC2(启动模板 = Ubuntu + install-relay.sh 用户数据)  ││
│  渠道 Claude   ×2  OpenAI类型             │        │   │  nginx :80 ─┬ /mantle/* 校验Bearer → sigv4-proxy ──────┼┼─→ bedrock-mantle.us-east-1 /openai/v1 (GPT)
│    base_url https://<ALB>/claude ─────────┼───────►│   │             └ /claude/*  → LiteLLM (master_key) ──────┼┼─→ bedrock-runtime.us-east-1 Converse (Claude)
│    密钥 = RELAY_CLAUDE_KEY                │        │   │  IAM 角色:bedrock:Invoke* + bedrock-mantle:CreateInference ││
│  系统设置:失败重试 3                      │        │   └─────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────┘        │   实例挂/不健康 → ASG 自动换新并注册回 ALB                      │
                                                     └─────────────────────────────────────────────────────────┘

3. 三步走 Runbook

下面凡是 AWS 专有名词(角色、实例配置文件、安全组、跃点限制、Session Manager、EIP、ALB、目标组、ACM),含义与坑见 §0.1,正文不再重复解释。docker / nginx / openssl 部分默认你已熟悉。

3.0 前置:一次性创建 IAM 角色(账号级,只做一次)

控制台:IAM → 角色 → 创建角色 → 可信实体 AWS 服务 / EC2 → 下一步 → 先勾 AmazonSSMManagedInstanceCore(让你能在浏览器里点"连接 → Session Manager"进机器,不用密钥对)→ 角色名 bedrock-relay-role → 创建。再进该角色 → 添加权限 → 创建内联策略 → JSON → 粘贴 cli/bedrock-invoke-policy.json 全文 → 名字 bedrock-invoke

命令行bash cli/01-create-iam-role.sh

策略要点:bedrock:InvokeModel*/Converse* 加上 bedrock-mantle:CreateInference(GPT 走 mantle 端点必需)。另外要在 Bedrock 控制台(us-east-1)确认已开通 Anthropic 与 OpenAI 模型访问("模型访问权限"页面)。

3.1 第一步:证明账号权限没问题(任意海外区 EC2)

目的:在不涉及中继的情况下,证明 (a) 这个 AWS 账号能调 Claude 和 GPT,(b) 该 EC2 所在区的出口 IP 过得了地域门。建议就在你打算放中继的区(东京)开一台临时机;同一脚本在香港机上跑一遍,可以亲眼看到 GPT 被挡而 Claude 不挡。

控制台:EC2(右上角切到 东京 ap-northeast-1)→ 启动实例 → 名称 bedrock-test → AMI 选 Ubuntu Server 24.04 → 类型 t3.micro 即可 → 密钥对"不使用" → 网络:默认 VPC、不需要开任何入站端口 → 高级详情 → IAM 实例配置文件 = bedrock-relay-role → 启动。等状态检查通过后,选中实例 → 连接 → Session Manager → 连接,得到一个浏览器终端。然后:

# 把 step1-账号权限测试/bedrock-test.py 的全文粘进去保存(或 nano bedrock-test.py 粘贴),再:
python3 bedrock-test.py

命令行:(在自己电脑或任意有 AWS CLI 的地方)

REGION=ap-northeast-1; AMI=$(aws ssm get-parameter --region $REGION --name /aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id --query Parameter.Value --output text)
aws ec2 run-instances --region $REGION --image-id $AMI --instance-type t3.micro --iam-instance-profile Name=bedrock-relay-role \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=bedrock-test}]' --query "Instances[0].InstanceId" --output text
# 然后 aws ssm start-session --region $REGION --target <实例ID>,进去后同上跑 python3 bedrock-test.py

判读(脚本自带 ✅/❌ 和说明):

编号 测的是 期望 不通时最可能的原因
1 GPT-5.6 @ mantle /openai/v1 200 PONG 403 bedrock-mantle:CreateInference → 角色策略缺;400 unsupported countries → 本机出口被挡(香港/大陆),GPT 必须经中继
2 Claude @ runtime Converse 200 PONG 403 AccessDenied → 角色策略/模型访问权限;400 invalid model → 没带 us. 前缀
3 Claude @ runtime InvokeModel 200 PONG 同上
4 GPT @ runtime Converse 200 PONG 同 1
5 Claude @ mantle /anthropic/v1/messages(短 ID) 200 PONG 404 → model ID 用错(别用 runtime 的长版本 ID);403 → 缺 bedrock-mantle:CreateInference。香港出口下也应 200(Claude 不被地域门挡;但这属意外,勿依赖)
6 mantle /v1/models 列表 200 403 → 缺 bedrock-mantle:ListModels(可选权限,不影响调用)
7/8 对照组:Claude 走 mantle 的 OpenAI 面 / 用 runtime 长 ID 打 mantle 400 / 404 才是预期 Claude 在 mantle 不支持 /v1/chat/completions(400);mantle 不认长版本 ID(404)

脚本只用 python3 标准库,自带 SigV4 签名,不需要 AWS CLI、不需要 pip。(原 curl 版在 Ubuntu 24.04 自带的 curl 8.5 上签名有 bug,已弃用。)

3.2 第二步:PoC 双机(香港 One API → 东京中继 → Bedrock)

3.2.1 中继机(先装)

控制台:EC2(东京)→ 启动实例:

填什么
名称 bedrock-relay-a
AMI Ubuntu Server 24.04 LTS (amd64)
实例类型 t3.small(2GB 内存,LiteLLM 需要;t3.micro 会 OOM)
密钥对 不使用(用 Session Manager)
网络 → 安全组 新建 bedrock-relay-sg:入站 HTTPS 443 来源 = <香港 One API 机公网IP>/32。不要开 22、不要 0.0.0.0/0
存储 16 GiB gp3
高级详情 → IAM 实例配置文件 bedrock-relay-role
高级详情 → 元数据版本 / 跃点限制 IMDSv2 仅令牌;元数据响应跃点限制 = 2
高级详情 → 用户数据 粘贴 step2-中继机/install-relay.sh 全文,并把开头两行改成你的钥匙:RELAY_GPT_KEY="sk-relay-gpt-随便32位"RELAY_CLAUDE_KEY="sk-relay-claude-随便32位"(留空也行,脚本会自动生成并打印)

启动后约 80~90 秒就绪。连接 → Session Manager,运行:

sudo cat /opt/relay/.env           # 看两把钥匙
sudo bash /opt/relay/selftest.sh   # 期望:healthz=ok;[2] GPT PONG;[3] Claude PONG;[4] 401
sudo cat /opt/relay/tls/server.crt # 证书内容,香港机要信任它(也可由香港机自动抓取,见下)

命令行

export HK_CIDR=<香港机公网IP>/32 RELAY_GPT_KEY=sk-relay-gpt-xxx RELAY_CLAUDE_KEY=sk-relay-claude-xxx
REGION=ap-northeast-1 NAME=bedrock-relay-a bash cli/02-launch-relay.sh

建议:给中继绑一个弹性 IP(EC2 → 弹性 IP → 分配 → 关联),否则停机再开 IP 会变。绑完在机上执行 sudo /opt/relay/gen-cert.sh && cd /opt/relay && sudo docker compose restart nginx 重新签发含新 IP 的证书。

中继机上到底有什么(/opt/relay/,完整内容见 step2-中继机/配置参考/):

文件 作用
docker-compose.yml 三个容器:litellm(Claude 转换 + 签名)、sigv4mantle(给 GPT 路签 SigV4)、nginx(TLS、分流、钥匙校验)
relay.conf.template nginx 配置:/mantle/* 校验 Bearer 后去前缀转 sigv4-proxy;/claude/* 转 LiteLLM;client_max_body_size 20mproxy_buffering off(SSE)
litellm.yaml claude-sonnet-4-5 → bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0;备选 gpt-5.6-converse → bedrock/converse/us.openai.gpt-5.6-sol
.env 两把钥匙 + region(chmod 600)
tls/server.crt|key 自签证书,SAN = DNS:bedrock-relay + 本机公网/内网 IP
gen-cert.sh / selftest.sh 重签证书 / 自检

要加模型:改 litellm.yaml 加一段 model_namesudo docker compose restart litellm;GPT 路不用改(mantle 直接认 openai.gpt-5.6-luna 等裸 ID),只需在 One API 渠道模型列表里加名字。

3.2.2 香港 One API 机

客户已有 One API(docker 部署) —— 只做三件事:

  1. 信任中继证书:把中继的 server.crt 追加到一个 PEM 文件(系统 CA + 中继证书),挂进容器并设 SSL_CERT_FILEstep2-香港OneAPI机/add-relay-cert.sh <中继IP> 会自动抓证书、追加、重启容器。手工版 compose 片段: yaml services: one-api: volumes: - ./relay-ca-bundle.pem:/etc/ssl/certs/relay-ca-bundle.pem:ro environment: SSL_CERT_FILE: /etc/ssl/certs/relay-ca-bundle.pem # Go 程序读这个变量 RELAY_TIMEOUT: "300"relay-ca-bundle.pem = cat /etc/ssl/certs/ca-certificates.crt 中继server.crt,这样其它 https 渠道不受影响。)
  2. 配两个渠道(控制台:渠道 → 添加新的渠道):
字段 渠道 A(GPT-5.6) 渠道 B(Claude)
类型 OpenAI OpenAI(不是 Anthropic、不是 AWS)
名称 GPT-5.6 via relay-a Claude via relay-a
代理 / Base URL https://<中继IP>/mantle/openai https://<中继IP>/claude
密钥 RELAY_GPT_KEY RELAY_CLAUDE_KEY
模型 openai.gpt-5.6-sol(想给用户短名则再加 gpt-5.6 claude-sonnet-4-5(可再加 gpt-5.6-converse
模型重定向(可选) {"gpt-5.6":"openai.gpt-5.6-sol"}
分组 default default

或者命令行:./setup-channels.sh <中继IP> <RELAY_GPT_KEY> <RELAY_CLAUDE_KEY>(用管理令牌走 One API 的 /api/channel 接口,效果与上表一致,并顺手把重试次数设为 3、建一个测试令牌)。 3. 设置 → 运营设置 → 失败重试次数 = 3(默认 0;多渠道时才会自动换渠道)。

全新装 One API 做 PoC(本次测试就是这样):控制台在 ap-east-1 启动 Ubuntu 24.04 t3.small,用户数据粘 install-oneapi.sh,安全组只对你办公 IP 开 3000(或不开,用 Session Manager 端口转发);命令行 bash cli/03-launch-oneapi-hk.sh。装完 cd /opt/oneapi && ./add-relay-cert.sh <中继IP> && ./setup-channels.sh <中继IP> <GPT钥匙> <Claude钥匙>

记得把香港机的公网 IP 填进中继安全组(3.2.1 的 HK_CIDR)。

3.2.3 端到端验证(香港机上)

cd /opt/oneapi && ./e2e-test.sh        # 或 ./e2e-test.sh sk-<你的One API令牌>

期望四条非流式都 HTTP 200content: "PONG",两条流式都逐块出 data: 行。本次实测输出(节选):

>>> openai.gpt-5.6-sol      {"choices":[{"message":{"content":"PONG",...   HTTP 200  总耗时 1.13s
>>> gpt-5.6(重定向)        ...content":"PONG"...                          HTTP 200  总耗时 0.96s
>>> claude-sonnet-4-5       ...content":"PONG"...                          HTTP 200  总耗时 1.47s
>>> gpt-5.6-converse(备选) ...content":"PONG"...                          HTTP 200  总耗时 1.56s
>>> claude-sonnet-4-5 流式  data: {...delta":{"content":"1\n2\n3\n4"...}}  ✔
>>> openai.gpt-5.6-sol 流式 data: {...delta":{"content":"1"}...}           ✔

给业务方的两条提醒:① GPT-5.6 请求用 max_completion_tokens(发 max_tokens 会 400,与 OpenAI 官方一致);改不了的老客户端用 gpt-5.6-converse。② One API 日志会刷 model ratio not found,是计费倍率没配,去"设置 → 运营设置 → 模型倍率"给这几个模型名加倍率即可,不影响调用。

3.3 第三步:高可用(生产)

要解决的三件事:挂了不影响业务(多台 + 入口自动摘除)、挂了自动补(ASG)、升级不停机(实例刷新)。写死 IP 的 nginx LB 解决不了第二件,所以生产用 ASG + ALB。三种做法都实测过:

推荐:ASG + ALB + One API 双渠道重试 过渡:香港侧 nginx LB sidecar + 手工维护的 2 台中继 应急:只用 One API 多渠道
实例挂了 ALB 16s 摘除;ASG 3~4 分钟自动换新(实测) ALB 无,nginx 立刻换台;坏机器要人手重开并改 LB 配置 随机换渠道重试
挂掉瞬间的请求 ALB 窗口内会 502,One API 换渠道重试兜住;实测 20 个请求 19 成功,唯一失败在终止后 2 秒内 nginx 原请求重发,实测 10/10 实测 9/10
升级 实例刷新滚动替换,实测期间零失败 两台错开手工 pull/up 手工
香港 One API 多台 全部指向同一 ALB 域名,无差别 每个节点各跑一个 sidecar,各自维护 upstream 每个节点各配渠道
新增组件 启动模板、ASG、ALB、目标组、ACM 证书、2 个安全组 每个 One API 节点 1 个 nginx 容器
月成本(东京) ≈ $47(2×t3.small)+ ≈ $20(ALB)+ 流量 ≈ $47 + 流量 ≈ $47
适合 生产 没有 ALB/ASG 权限或想先跑起来的阶段 临时

3.3.1 推荐方案:ASG + ALB

命令行(一条命令建完)

export HK_CIDRS="<香港节点1 IP>/32 <香港节点2 IP>/32"   # 所有 One API 节点的出口 IP
export RELAY_GPT_KEY=sk-relay-gpt-xxx RELAY_CLAUDE_KEY=sk-relay-claude-xxx
REGION=ap-northeast-1 bash step3-高可用/create-asg-alb.sh

脚本顺序做:ALB 安全组(443 ← 香港)→ 中继安全组(80 ← ALB 安全组)→ 目标组(/healthz,5s×2)→ ALB(空闲超时 300s)→ 自签证书导入 ACM + HTTPS 监听器 → 启动模板(AMI、t3.small、角色、跃点限制 2、install-relay.sh 作用户数据)→ ASG(min 2 / max 4,跨所有默认子网,健康检查类型 ELB,宽限 180s)。最后打印 ALB 域名。

控制台(东京,按同样顺序): 1. EC2 → 安全组:建 bedrock-relay-alb-sg(入站 443 ← 各香港 IP/32);建 bedrock-relay-asg-sg(入站 80 ← 来源选安全组 bedrock-relay-alb-sg)。 2. EC2 → 目标组 → 创建:实例、HTTP 80、默认 VPC;健康检查 /healthz,高级:间隔 5、超时 3、阈值 2/2。不选任何实例(ASG 会自动注册)。 3. EC2 → 负载均衡器 → 创建 ALB:面向互联网、默认 VPC 勾全部可用区、安全组只选 bedrock-relay-alb-sg、监听器 HTTPS 443 → 上面的目标组、证书:有域名选 ACM 证书;没域名先建 HTTP 80 监听器,拿到 DNS 名后按 §0.1 ACM 行生成自签、ACM → 导入,再加 HTTPS 监听器并删 HTTP。属性 → 空闲超时 300。 4. EC2 → 启动模板 → 创建:名称 bedrock-relay-lt;AMI Ubuntu 24.04;t3.small;不选密钥对;安全组 bedrock-relay-asg-sg;存储 16G gp3;高级:IAM 实例配置文件 bedrock-relay-role、元数据 v2 仅令牌、跃点限制 2、用户数据粘 install-relay.sh(开头先写两行 export RELAY_GPT_KEY=... RELAY_CLAUDE_KEY=...)。 5. EC2 → Auto Scaling 组 → 创建:选上面的模板(版本 Latest);网络选默认 VPC 全部子网;负载均衡 → 附加到现有目标组;健康检查类型勾 ELB,宽限期 180;组大小 期望 2 / 最小 2 / 最大 4;标签 Project=bedrock-relay-poc 勾"传播到实例"。 6. 约 3 分钟后目标组两台 healthy。

香港每个 One API 节点

cd /opt/oneapi
./add-relay-cert.sh <ALB域名>                                   # 自签证书才需要;用正规证书跳过
./setup-channels.sh <ALB域名> <GPT钥匙> <Claude钥匙> alb-1
./setup-channels.sh <ALB域名> <GPT钥匙> <Claude钥匙> alb-2       # 同一 ALB 第二套渠道,给 One API 重试用

控制台等价:每个模型建两个一模一样的 OpenAI 渠道(只差名字),设置 → 运营设置 → 失败重试次数 = 3。

验证(实测记录): - ./failover-test.sh 20 全 ✅。 - 崩溃演练:aws ec2 terminate-instances --instance-ids <一台中继>(或控制台终止),同时 ./failover-test.sh 20。实测时间线:T+0 终止;T+16s ALB 标 unhealthy;T+28s 目标组只剩一台;T+2m10s ASG 判定失联、拉新机;T+3m45s 新机 healthy。请求 19/20,One API 日志 11 次 using channel #x to retry。 - 升级演练:aws autoscaling start-instance-refresh --auto-scaling-group-name bedrock-relay-asg --preferences '{"MinHealthyPercentage":50,"InstanceWarmup":90}',期间连续跑 failover-test.sh。实测约 5 分钟两台全部换成新实例,期间 120 个请求 0 失败。

日常运维: - 改中继配置 / 升级镜像:改 install-relay.shaws ec2 create-launch-template-version --launch-template-name bedrock-relay-lt --source-version 1 --launch-template-data '{"UserData":"<base64>"}' → 上面的 instance refresh。控制台:启动模板 → 操作 → 修改模板(创建新版本)→ ASG → 实例刷新。 - 换钥匙:同上发新版模板 + 刷新,再改 One API 渠道密钥。 - 扩容:改 ASG 期望容量即可,ALB 自动注册。 - 看状态:ASG 活动历史、目标组健康页;建议给 ALB UnHealthyHostCount ≥ 1HTTPCode_ELB_5XX_Count 加 CloudWatch 告警发到邮箱/企微。 - 已知限制:ALB 摘除坏节点前约 10 秒窗口,靠 One API 双渠道重试兜;极端情况(重试 3 次都落坏节点)仍会返回 502,业务侧保留客户端重试。

3.3.2 过渡方案:香港侧 nginx LB sidecar(无 ASG)

适合还没拿到 ALB/ASG 权限、或只有两台手工维护中继的阶段。在每个 One API 节点旁边起一个 relay-lb nginx 容器(docker-compose.override.yml),upstream 写死中继 IP,proxy_next_upstream ... non_idempotent 让失败的 POST 立刻换台。One API 指向 http://relay-lb:8080/...,不再需要 SSL_CERT_FILE。

cd /opt/oneapi
./add-relay-cert.sh <中继A IP>; ./add-relay-cert.sh <中继B IP>
sudo bash install-hk-lb.sh <中继A IP> <中继B IP>
./setup-channels.sh http://relay-lb:8080 <GPT钥匙> <Claude钥匙> lb

实测:停掉中继 A 的 nginx,随即 10 个请求 10/10。缺点:中继换 IP/重建后要到每个节点改 upstream 重跑脚本;坏机器不会自动补。One API 若跑在 k8s,等价做法是一个 nginx Deployment + Service。

3.3.3 EC2 崩溃后的行为(两种方案通用)


4. 安全与运维清单

做法
中继入口 安全组仅放香港机 IP 的 443(方案 B 再放 ALB 安全组的 80);不开 22(用 Session Manager)
中继鉴权 GPT 路 nginx 校验 Authorization: Bearer RELAY_GPT_KEY;Claude 路 LiteLLM 校验 master_key。错钥匙 401(实测)
AWS 凭证 只有实例角色,权限最小化(Invoke + mantle:CreateInference);机器上没有 AK/SK
传输 香港→中继 TLS(自签,One API/LB 校验);中继→Bedrock TLS
换钥匙 ASG 方案:发新版启动模板 + 实例刷新;单机:改 /opt/relay/.envdocker compose up -d。然后改 One API 渠道密钥
换证书/换 IP 中继:gen-cert.sh + restart nginx;香港:add-relay-cert.sh <IP> 追加
升级 ASG 方案:实例刷新(滚动,实测零失败);单机:cd /opt/relay && sudo docker compose pull && sudo docker compose up -d
日志 中继 docker compose logs -f nginx|litellm;LiteLLM 默认不落盘对话内容
成本 中继 t3.small 东京 ≈ $22/月/台 + 16GB 盘 ≈ $1.5;两台 ≈ $47/月;ALB ≈ $20/月 + LCU;出站流量另计(回答文本量小)
模型倍率 One API 设置里给 openai.gpt-5.6-solclaude-sonnet-4-5 等配倍率,否则日志报 model ratio not found 且按默认倍率计费

5. 实测记录(2026-09-15,账号 0808…,测试后资源已全部删除)

结果
新加坡 EC2 直连:GPT@mantle、Claude@Converse/Invoke、GPT@Converse、SSE ✅ 全 200
香港 EC2 直连:GPT@mantle、GPT@Converse ❌ 400 Access to OpenAI models is not allowed from unsupported countries…
香港 EC2 直连:Claude@Converse、Claude@InvokeModel ✅ 200
香港 Lambda(ap-east-1,出口 IP 43.199.34.64)复测(2026-09-16):GPT@mantle、GPT@runtime ❌ 400 unsupported countries(两端点都挡)
同上:Claude@runtime Converse、Claude@runtime Invoke ✅ 200
同上:Claude@mantle /anthropic/v1/messages(短 ID anthropic.claude-opus-4-8 ✅ 200 —— 证明地域门按模型判、不按端点判;⚠️ Claude 从香港能用属意外,勿依赖
mantle Claude 的模型 ID 形式(us-east-1 与香港均测):短 ID vs runtime 长版本 ID 短 ID ✅ 200;长版本 ID ❌ 404(这是此前误判"Claude 不在 mantle"的根因)
Claude 走 mantle 的 OpenAI 面 /v1/chat/completions ❌ 400 does not support the '/v1/chat/completions' API(Claude 在 mantle 只吃 /anthropic/v1/messages
bedrock-mantle .../v1/models(GET,SigV4)列模型 ✅ 200,含 6 个 Claude(opus-4-7/4-8/5、sonnet-5、haiku-4-5、fable-5);需 bedrock-mantle:ListModels 权限
中继角色只给 bedrock:Invoke* 时 GPT@mantle ❌ 403 not authorized to perform: bedrock-mantle:CreateInference → 补权限后 ✅
中继一键脚本(用户数据)→ 三容器就绪 ✅ 76~90 秒(3 台:relay-a/b、以及用最终版脚本从零重建的 relay-c)
中继自检:healthz / GPT / Claude / 错钥匙 401 ✅ 3 台全部通过
香港 One API → 中继 → Bedrock(GPT、gpt-5.6 重定向、Claude、gpt-5.6-converse) ✅ 全 200,0.9~1.9s
上述两条流式 SSE 经 One API + 中继 ✅ 逐块到达
GPT 发 max_tokens ❌ 400 unsupported_parameter(与 OpenAI 官方一致)→ 用 max_completion_tokens
模型重定向别名不在模型列表 ❌ 503 无可用渠道 → 加进列表 ✅
方案 C(4 渠道 + RetryTimes=3),停中继 A 后 10 请求 9/10(1 次三连抽到坏渠道)
方案 B(ALB,健康检查 10s×2),停中继 A 后 发现前 502(16 请求中 7 失败,全部落在坏节点),发现后 ✅
方案 B ALB 健康检查打 80 /healthz 初次 ❌ ResponseCodeMismatch(nginx 镜像默认站点抢 80)→ 覆盖 default.conf 后 ✅ healthy
方案 B 经 ALB 的 SSE
方案 A(香港 nginx LB),停中继 A 后立刻 10 请求 + SSE 10/10,0 失败;LB 日志 upstream server temporarily disabled
中继 B reboot ✅ 55 秒后三容器 Up、/healthz ok、ALB healthy
ASG + ALB 崩溃演练terminate-instances 一台中继 ALB T+16s unhealthy、T+28s 摘除;ASG T+2m10s 拉新机、T+3m45s healthy;期间 20 请求 19 成功(唯一失败在 T+2s),One API 换渠道重试 11 次
ASG 实例刷新(滚动替换 2 台,MinHealthy 50%,warmup 90s) 约 5 分钟换完两台(新实例 ID);期间 6 轮 × 20 = 120 个请求 0 失败
One API 信任自签证书(SSL_CERT_FILE) ✅(不配则 TLS 握手失败,源码无 InsecureSkipVerify)
curl 8.5(Ubuntu 24.04 自带)--aws-sigv4 对含 : 的模型 ID ❌ 签名不匹配(curl 8.18/8.21 正常)→ 改用 python 版测试脚本

6. 文件清单(deliverables/

Bedrock中继方案-修正版-2026-09-15.md      ← 本文
step1-账号权限测试/
  bedrock-test.py                          第一步:只需 python3,自动取实例角色,SigV4 签名,✅/❌ 判读
step2-中继机/
  install-relay.sh                         中继一键安装(可直接作 EC2 用户数据);顶部可预设两把钥匙
  配置参考/docker-compose.yml | relay.conf.template | litellm.yaml | gen-cert.sh | selftest.sh
step2-香港OneAPI机/
  install-oneapi.sh                        全新装 One API(含 SSL_CERT_FILE、RELAY_TIMEOUT、管理令牌)
  add-relay-cert.sh <中继IP或ALB域名>       抓中继证书加入信任并重启 One API
  setup-channels.sh <中继地址> <GPT钥匙> <Claude钥匙> [后缀]   用 API 建两个渠道 + 重试次数 + 测试令牌
  e2e-test.sh                              端到端验证(4 非流式 + 2 流式)
  docker-compose.yml.参考
step3-高可用/
  install-hk-lb.sh <中继IP...>              过渡方案:One API 节点旁起 nginx LB sidecar(docker-compose.override.yml)
  create-asg-alb.sh                        推荐方案:一键建 ALB/目标组/证书/启动模板/Auto Scaling Group
  failover-test.sh [N]                     连发 N 个请求统计成功率
cli/
  bedrock-invoke-policy.json               中继角色的 IAM 策略(含 bedrock-mantle:CreateInference)
  01-create-iam-role.sh                    建角色 + 实例配置文件
  02-launch-relay.sh                       启动一台中继(安全组、AMI、hop limit、用户数据全自动)
  03-launch-oneapi-hk.sh                   (可选)启动香港 PoC One API 机
  99-cleanup.sh                            按标签 Project=bedrock-relay-poc 删光所有测试资源(含 ASG/启动模板/ALB)

7. 仍需客户侧确认

  1. 客户现网 One API 的版本/分支(本次按 songquanpeng/one-api 最新版 justsong/one-api:latest 验证;new-api 等分支界面字段名略有不同,但"OpenAI 类型渠道 + base_url + 密钥"的逻辑一致)。
  2. 客户香港 One API 的部署形态:几台、docker 还是 k8s、各节点出口 IP 是什么(ALB 安全组要全放)。是否在 AWS 上不影响方案。
  3. 是否有公司域名:有则中继/ALB 证书可换成正规证书,省掉 add-relay-cert.sh 这一步。
  4. 业务方是否能改 max_tokens → max_completion_tokens;不能则 GPT 走 gpt-5.6-converse