Claude apps gateway 配置参考
gateway.yaml 的结构与字段:五个必需部分、各上游(Anthropic、Bedrock、Claude Platform on AWS、Google Cloud、Foundry)的认证与配置、managed 策略、telemetry、HTTP 调优。
本页整理 Claude apps gateway 的 YAML 配置文件结构与常用字段。本页涵盖结构、必需部分、上游的每种认证方式、managed 策略、telemetry 和 HTTP 调优;官方还附有一份很长的完整示例,这里不重复,取值与版本要求以官方原文为准。
文件结构
有五个必需部分,其他都是可选的,省略的部分取默认值。未知的键会让启动失败,所以拼写错误会表现为点名的错误,而不是被静默忽略的设置。
必需部分:listen(绑定地址、公开 URL、TLS 终止);oidc(你的身份提供商,包括 issuer、客户端、声明映射和谁可以登录);session(网关签发的 bearer 令牌,含密钥和寿命);store(PostgreSQL,用于设备授权和速率限制计数器);upstreams(推理去向:Anthropic、Amazon Bedrock、Claude Platform on AWS、Google Cloud Agent Platform 或 Microsoft Foundry)。
可选部分:admin(管理 API 认证和支出限额的保留);enforcement(支出限额放行或拒绝行为);pricing(合同费率和用于支出计量和开发者所见成本数字的倍数);models 和 auto_include_builtin_models(管理员维护的模型列表和按上游的 ID);managed(按 IdP 组的托管设置策略);telemetry(OTLP 转发到你的可观测性栈);access_control、limits、timeouts、rate_limits(IP 允许/拒绝、请求大小上限、上游首字节时间、每 IP 登录限制);load_test_mode(不调用模型提供商而对网关做负载测试)。
密钥展开
不要把 client_secret、jwt_secret、postgres_url 这类密钥直接写在 gateway.yaml 里,用下面的形式引用,网关在启动时从环境变量或文件解析值:${VAR}(环境变量 VAR,未定义则启动失败,适用于容器环境变量、通过环境注入的 AWS Secrets Manager);${file:/path}(该绝对路径文件的内容,去空白;引用必须是字段的整个值,不像 ${VAR} 那样在更长的字符串里展开,所以数据库密码要设 store.password 而不是嵌入 postgres_url)。
必需部分的字段
listen:host(绑定地址,默认 0.0.0.0);port(默认 8080);public_url(对外可见的 https:// 源,用于构建 IdP 的 redirect_uri 和发现元数据,host 不是环回地址时必需);tls.cert / tls.key(网关自己终止 TLS 时的 PEM 路径);trusted_proxies(网关前负载均衡器的 CIDR 或 IP,设置后网关只信任来自这些对端的 X-Forwarded-For 并记录真实客户端 IP 用于每 IP 速率限制)。
oidc:issuer(必填,OIDC 发现的基础,必须在 /.well-known/openid-configuration 提供发现文档,生产环境用 HTTPS);client_id / client_secret(必填,来自你的 OAuth 客户端注册);allowed_email_domains(拒绝 email 声明不在这些域名里的 id_token,不区分大小写,是防多租户 IdP 配置错误的纵深防御);allowed_groups(把登录限制为这些 IdP 组的成员,对照 groups_claim 匹配);groups_claim(哪个 id_token 声明携带组成员,默认 groups,Microsoft Entra 的应用角色在 roles 下);google_groups(因为 Google 的 id_token 不带组声明,通过 Google Workspace Admin SDK Directory API 查找登录用户的组);email_claim(携带邮箱的声明,默认 email,ADFS 和 Entra B2C 等 IdP 改发 upn 或 preferred_username);scopes(网关请求的 OIDC scope 的完整覆盖,默认 [openid, profile, email, offline_access]);userinfo_fallback(id_token 省略邮箱或组时从 /userinfo 获取,Keycloak 轻量访问令牌、Okta 组织服务器和 ADFS 最小令牌需要);use_pkce(在授权请求上发送 PKCE(S256)挑战,默认 true);clock_skew_seconds(验证 id_token 时间声明时容忍的时钟漂移,默认 0 严格);token_endpoint_auth_method、id_token_signed_response_alg(默认 RS256)、discovery_url、use_proxy、ca_cert_pem(PEM 编码的 CA 证书本身,只替换 IdP 请求的系统信任存储)等用于特殊 IdP 情形。
session:jwt_secret(必填,至少 32 字节熵,如 openssl rand -base64 32,签署网关的 HS256 bearer 令牌;接受单个字符串或用于轮换的数组,索引 0 用于签名);ttl_hours(网关 bearer 令牌寿命,默认 1,IdP 签发刷新令牌时 CLI 在到期前静默刷新;越短取消预配越快)。
store:postgres_url(必填,postgres:// 或 postgresql:// URL,设备授权的交会需要跨副本状态);username、password(把凭据放在这里而不是 URL 里,优先于 URL 凭据);max_connections(每副本的 Postgres 连接池大小,默认 5);connect_timeout_seconds(1 到 60,默认 5);readiness_grace_seconds(Postgres 停止应答后 /readyz 继续报告就绪的秒数,0 到 3600,默认 0)。
upstreams 详解
upstreams 是有序列表,网关把推理转发给第一个能解析出所请求模型的上游。上游返回 5xx、429、401、403、404 或超时时,网关故障转移到下一个;其他 4xx 不转移,因为这类错误归因于请求而不是上游。401/403 表示网关对该上游使用的凭据失败;404 表示该上游不服务所请求的模型,后面的上游仍可能服务(对 404 做故障转移需要网关 v2.1.198 及以上)。同一提供商的多个上游必须设置不同的 name:。Bedrock、Claude Platform on AWS、Google Cloud Agent Platform、Foundry 的客户端在启动时只构建一次,其 SDK 内部刷新凭据,所以轮换云凭据不需要重启;静态的 Anthropic API key 和 bearer 在启动时读取。
上游错误如何到达开发者
网关返回某个上游的错误响应,或自己的 502,取决于各上游怎么答复:
- 某上游返回了不做故障转移的状态:返回该上游的响应,不再尝试后面的上游。
- 所有尝试过的上游都以可转移的方式失败:返回最后一个
429;没有429时依次优先最后一个401/403、最后一个404、最后一个501;都没有时返回网关自己的502,消息为all upstreams failed (N attempted),N 统计upstreams里的每一项,包括因不服务该模型而被跳过的。
返回上游响应时保留上游状态码;是否保留上游消息取决于提供商。Anthropic API 上游的错误体原样到达开发者。云上游(Bedrock、Claude Platform on AWS、Agent Platform、Foundry)的错误文本可能带有账号 ID、角色 ARN 和项目 ID,网关把完整文本记入运维日志,开发者看到什么取决于拒绝类型:
- Anthropic 标准错误信封里的
400/413:上游自己的消息,如prompt is too long(Claude Platform on AWS、Agent Platform、Foundry 对模型 API 拒绝返回这种信封) - 提供商自有格式的
400/413:一个capability_rejected:标记;无法分类时,400为upstream rejected the request,413为request too large for this upstream - 其他状态:按状态给的通用文案,如
429对应upstream rate limit exceeded
例如,网关把 Bedrock 的 Input is too long for requested model. 换成 capability_rejected: prompt_too_long,Claude Code 看到该标记会像对 prompt is too long 一样自动压缩。保留云上游的 400/413 消息或替换为标记需要网关 v2.1.233 及以上。
Anthropic API
最小的 Anthropic 上游用 Claude Console 的 API key:
upstreams:
- provider: anthropic
auth:
api_key: ${ANTHROPIC_API_KEY}
# 或 OAuth bearer(例如经 Workload Identity Federation 换得的令牌):
# oauth_token: ${file:/var/run/secrets/anthropic-oauth-token}
# base_url: https://api.anthropic.com # 默认值;经正向代理时覆盖两种凭据形式发送的头不同:api_key 发送 x-api-key,在 Claude Console 轮换后更新环境变量;oauth_token 发送 Authorization: Bearer,适合组织签发短期令牌而非长期 API key 的情形,bearer 只在启动时读取一次,要刷新就重新挂载密钥并重启。
也可以不用静态 key,改用 Workload Identity Federation:按其指南创建联合规则,把工作负载的 OIDC JWT 挂成文件(如 Kubernetes projected service-account token 或 CI 平台的 id-token),网关把 JWT 换成短期 bearer 并自动刷新;令牌文件每次交换都会重新读取,所以轮换无需重启。
upstreams:
- provider: anthropic
auth:
federation_rule_id: ${ANTHROPIC_FEDERATION_RULE_ID}
organization_id: ${ANTHROPIC_ORGANIZATION_ID}
identity_token_file: /var/run/secrets/anthropic/id-token
# workspace_id: wrkspc_... # 规则覆盖多个 workspace 时必填
# service_account_id: svac_... # 可选的预期目标检查给你运行的代理传每用户身份头:可以把 provider: anthropic 上游的 base_url 指向你自己运行的代理,并在该上游设 forward_user_identity: true(默认 false),让代理知道每个请求来自哪个开发者,从而按开发者归属花费(需要网关运行 Claude Code v2.1.233 及以上)。网关会在转发到该上游的每个请求上加这些头:
| 头 | 值 |
|---|---|
x-litellm-end-user-id | 开发者的邮箱(IdP 提供时) |
x-claude-gateway-user-id | 开发者的 IdP subject,来自令牌的 sub 声明 |
x-claude-gateway-user-email | 开发者的邮箱(IdP 提供时) |
IdP 令牌没有邮箱时只发 x-claude-gateway-user-id;如果你的 IdP 把邮箱放在别的声明里,把 oidc.email_claim 设为那个声明。你的代理对带邮箱的请求返回 429 时,网关原样把该响应交给开发者而不是故障转移,所以代理的每用户预算或速率限制生效;代理的其他响应走常规故障转移。只在 base_url 是你自己运营的代理时才设它,因为网关会把开发者邮箱发给 base_url 所指的任何服务器;base_url 是 Anthropic API(默认)时网关拒绝启动。
Amazon Bedrock
upstreams:
- provider: bedrock
region: us-east-1
auth: {} # 推荐:AWS 默认凭据链
# 或显式凭据:
# auth:
# aws_access_key_id: ${AWS_AKID}
# aws_secret_access_key: ${AWS_SK}
# aws_session_token: ${AWS_ST}
# 或 Bedrock API bearer 令牌:
# auth:
# aws_bearer_token: ${AWS_BEARER_TOKEN}
# FIPS 或 VPC 端点部署时覆盖 bedrock-runtime 端点:
# base_url: https://bedrock-runtime-fips.us-east-1.amazonaws.com空的 auth 块使用 AWS SDK 的默认凭据链:环境变量、~/.aws/credentials、ECS 任务角色、EC2 实例元数据或 EKS 上的 IRSA;生产环境给网关 pod 一个 IAM 角色,不要把静态 key 嵌进镜像。显式凭据必须完整:aws_access_key_id 和 aws_secret_access_key 没有一起设,或设了 aws_session_token 而没有它们,网关启动就失败(v2.1.207 之前,不完整的 auth: 块能通过校验)。
| 设置 | 做法 |
|---|---|
| IAM 权限 | 给网关的主体在推理配置文件 ARN 和底层基础模型 ARN 上授予 bedrock:InvokeModel 和 bedrock:InvokeModelWithResponseStream;内置目录在美国区域是 arn:aws:bedrock:<region>:<account>:inference-profile/us.anthropic.* 和 arn:aws:bedrock:*::foundation-model/anthropic.*;另外在基础模型 ARN 上授予 bedrock:CountTokens |
| 模型访问 | Bedrock 在商业区域默认启用模型访问;剩下的账号级关卡是 Anthropic 的一次性用例表单:如果 AWS 账号里没人提交过,打开 Bedrock 控制台,在 Model catalog 选一个 Anthropic 模型并完成表单 |
| EKS(IRSA) | 创建带上述策略的 IAM 角色,信任策略指向集群的 OIDC 提供商并限定到网关的服务账号;给服务账号加注解 eks.amazonaws.com/role-arn: arn:aws:iam::<acct>:role/claude-gateway;auth: {} 会自动取用 |
| ECS / EC2 | 把 IAM 角色挂到任务定义或实例配置文件上;auth: {} 会自动取用 |
| 其他环境 | 用 AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、AWS_SESSION_TOKEN 环境变量,或在 auth: 里用 ${VAR} 展开显式设置 |
| 区域 | region: 是 API 端点区域;跨区域推理配置文件会在地理范围(US、EU、APAC)内路由,不论你选哪个;非美国区域或预置吞吐量 ARN 要加 models: 块写上对应的每上游 ID |
应用 Bedrock guardrail:给 Bedrock 上游加 guardrail 块,网关经该上游发出的每个推理请求都会应用它(网关服务器需 Claude Code v2.1.281 及以上)。
upstreams:
- provider: bedrock
region: us-east-1
auth: {}
guardrail:
id: gr-abc123 # guardrail ID 或完整 ARN
version: "1" # 已发布的版本号或 DRAFT
# 要加引号:裸的 1 会让启动失败注意:网关不支持 guardrail 输入标签,不会给提示加 guard 内容标签,所以只对带标签输入生效的 guardrail 过滤器不会对经网关的流量运行。还要把 guardrail 上的 bedrock:ApplyGuardrail 授予给该上游请求签名的主体(网关的 AWS 主体,用了 assume_role 时则是 role_arn 指定的角色)。guardrail 要么每个 bedrock 上游都设,要么都不设,混用时网关拒绝启动,否则故障转移可能把请求发到没有 guardrail 的 Bedrock 上游。guardrail 只覆盖 Bedrock 上游,其他提供商的请求不带它。/v1/messages 请求体里带 amazon-bedrock-* 字段(如 amazon-bedrock-guardrailConfig)到达设了 guardrail 的 Bedrock 上游时,网关回 400 而不转发。
另一个 AWS 账号里的 Bedrock:在 Bedrock 上游设 assume_role,网关只用自己的 AWS 身份去调用对你指定的角色(可在不同账号)的 sts:AssumeRole,该上游的每个 Bedrock 请求都用 STS 返回的一小时凭据签名,没有长期访问密钥跨账号(需要网关 v2.1.281 及以上,更早的网关遇到该键会拒绝启动)。
upstreams:
- name: bedrock-isolated
provider: bedrock
region: us-east-1
auth: {} # 网关自己的角色:它只调用 STS
assume_role:
role_arn: arn:aws:iam::222222222222:role/claude-gateway-bedrock
# external_id: ${BEDROCK_ROLE_EXTERNAL_ID} # 角色信任策略要求时assume_role 有三个键:role_arn 是网关承担的 IAM 角色(arn:aws:iam:: 或 arn:aws-us-gov:iam:: ARN),要给它该上游需要的 Bedrock 权限(含 bedrock:CountTokens,上游设了 guardrail 时还要 bedrock:ApplyGuardrail);external_id 可选,作为每次 sts:AssumeRole 调用的外部 ID,角色信任策略要求时设置,全是数字时要加引号;session_name 可选,设为 email 或 sub 让每个开发者有自己的会话,不设则每个请求用同一个名为 claude-apps-gateway 的会话。角色的信任策略指定网关自己的主体(如其 IRSA 或 ECS 任务角色),该主体需要对角色的 sts:AssumeRole,不需要自己的任何 Bedrock 权限;没设 external_id 就去掉 Condition:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111111111111:role/claude-gateway" },
"Action": "sts:AssumeRole",
"Condition": { "StringEquals": { "sts:ExternalId": "your-external-id" } }
}]
}- STS 拒绝或不可达时,网关不会用该上游自己的凭据发请求;它把 STS 错误和排查要点记入日志,然后试列表里的下一个上游。后面没有
assume_role的上游会用它自己的凭据服务请求,所以只有想要这样时才列它。 - 网关调用区域 STS 端点
sts.<region>.amazonaws.com,网络必须能到达;要用 FIPS 端点,在网关环境里设AWS_USE_FIPS_ENDPOINT=true,不要在 AWS 配置文件里设use_fips_endpoint。 assume_role只适用于provider: bedrock,且需要 SigV4 源凭据:与aws_bearer_token同时设置时网关拒绝启动。- 网关放行的每个开发者都能用这个上游,
managed管的是哪些开发者可用哪些模型。要让经该角色服务的模型不会同时从别的账号服务,给它一个自定义 id,其upstream_model映射只含这个上游的名字,此时网关会跳过其他所有上游。
models:
- id: claude-opus-restricted # 自定义 id,不是内置模型名
upstream_model:
bedrock-isolated: us.anthropic.claude-opus-4-8 # 唯一服务它的上游按开发者的 AWS 成本归属:默认网关用一个凭据给所有 Bedrock 请求签名,AWS 看到所有开发者在同一个 IAM 主体下。给 assume_role 加 session_name: email 后,网关每个开发者每小时调用一次 sts:AssumeRole,会话名设为该开发者的邮箱,用返回的凭据签名,这样每个开发者的请求以各自的承担角色会话到达 AWS(需要 v2.1.281 及以上)。
upstreams:
- provider: bedrock
region: us-east-1
auth: {} # 网关自己的角色:它只调用 STS
assume_role:
role_arn: arn:aws:iam::123456789012:role/claude-gateway-bedrock-user
session_name: email # 或 subsession_name 选择哪个已验证声明成为 AWS 的 RoleSessionName:email 或 sub。除 ASCII 字母、数字和 _+,.@- 之外的字符会按 UTF-8 字节写成 =XX 十六进制,超过 64 字符的结果缩短为前缀加哈希,保证每个开发者的会话名有效且唯一;令牌缺少该声明的开发者的请求不会经这个上游发送。一个活跃开发者每个网关副本每小时一次 STS 调用,并发的首次请求共享一次调用。网关自己也会在该角色上做一次调用:给客户端放弃的请求做 token 计数,以保证支出限额准确;该计数及其一 token 的回退请求用共享的 claude-apps-gateway 会话签名,所以 AWS 把回退归属到 claude-apps-gateway 而不是开发者。要严格的按开发者归属,在列出的每个 Bedrock 上游都设带 session_name 的 assume_role,没设的上游会用自己的凭据签名它所服务的请求。
Claude Platform on AWS
Claude Platform on AWS 在 aws-external-anthropic.<region>.api.aws 的 AWS 基础设施上提供第一方 Anthropic API。它使用第一方模型 ID,按原样接受 anthropic-beta 头,并提供 count_tokens,所以不涉及 Bedrock 专有的转换。anthropicAws 提供商需要 v2.1.198 及以上,更早的网关在启动时拒绝它。
upstreams:
- provider: anthropicAws
region: us-east-1
workspace_id: wrkspc_...
auth:
api_key: ${ANTHROPIC_AWS_API_KEY} # 作为 x-api-key 发送
# 或经 AWS 默认凭据链用 SigV4:
# auth: {}
# 或显式 SigV4 凭据:
# auth:
# aws_access_key_id: ${AWS_ACCESS_KEY_ID}
# aws_secret_access_key: ${AWS_SECRET_ACCESS_KEY}
# 覆盖推导出的端点:
# base_url: https://aws-external-anthropic.us-east-1.api.aws该平台运行在与 Bedrock 不同的 AWS 账号里,并对自己的服务名 aws-external-anthropic 做 SigV4 签名,所以限定到 Bedrock 的 IAM 角色不能授权它。auth.api_key 在同时设了 SigV4 凭据时优先;空 auth 块用 AWS SDK 默认凭据链,与 Bedrock 上游相同。
| 字段 | 必填 | 说明 |
|---|---|---|
region | 是 | AWS 区域(小写字母、数字和连字符),网关据此推导端点 https://aws-external-anthropic.<region>.api.aws |
workspace_id | 是 | 作为头随每个请求发送,平台要求 |
auth.api_key | 否 | 平台的 API key,作为 x-api-key 发送;不是 bearer 令牌:两种认证方式是 API key 或 SigV4 |
auth.aws_access_key_id / auth.aws_secret_access_key | 否 | 显式 SigV4 凭据,只设一个会在启动时失败;可以同时带 auth.aws_session_token |
base_url | 否 | 覆盖推导出的端点 |
因为平台解析第一方模型 ID,内置目录无需 models: 块就能路由到它;整理 models: 列表时,条目要以 anthropicAws: 为键、写第一方 ID。
Google Cloud Agent Platform
upstreams:
- provider: vertex
region: us-east5
project_id: example-prod
auth: {} # 推荐:Application Default Credentials
# 或服务账号密钥文件:
# auth: { service_account_json: /secrets/sa.json }
# Private Service Connect 时覆盖 aiplatform 端点:
# base_url: https://us-east5-aiplatform.p.googleapis.com空 auth 块使用 Application Default Credentials:GOOGLE_APPLICATION_CREDENTIALS、GCE 元数据或 GKE Workload Identity。支持服务账号 JSON 密钥文件但不推荐,优先用 Workload Identity 或给 GCE/Cloud Run 实例挂服务账号。region: global 使用 Agent Platform 的全局端点而不是区域端点,Google 会把每个请求路由到可用区域,你不必跟踪各区域的模型可用性;指定具体区域则把每个请求固定到该区域。
| 设置 | 做法 |
|---|---|
| IAM 权限 | 在项目上给网关的服务账号 roles/aiplatform.user,或含 aiplatform.endpoints.predict 的自定义角色;启用 Agent Platform API(aiplatform.googleapis.com) |
| 模型访问 | 在 Model Garden 里为项目启用 Claude 模型;它们发布在特定区域,看模型卡了解支持的区域 |
| GKE(Workload Identity) | 把 GCP 服务账号绑定到网关的 Kubernetes 服务账号,并给 KSA 加注解 iam.gke.io/gcp-service-account: claude-gateway@<proj>.iam.gserviceaccount.com;auth: {} 自动取用 |
| Cloud Run / GCE | 把服务的服务账号设为有 roles/aiplatform.user 的账号;auth: {} 自动取用 |
| 其他环境 | auth: { service_account_json: /secrets/sa.json },值是挂载为密钥的 JSON 密钥文件路径;该字段要的是文件路径而不是密钥内容,所以不涉及 ${file:…} 展开 |
Microsoft Foundry
upstreams:
- provider: foundry
resource: example-foundry # https://example-foundry.services.ai.azure.com
auth: { use_azure_ad: true } # 推荐:DefaultAzureCredential / Managed Identity
# 或 API key:
# auth:
# api_key: ${FOUNDRY_API_KEY}use_azure_ad: true 经 DefaultAzureCredential 解析:AKS、ACI 或 App Service 上的 Managed Identity、Azure CLI 或环境凭据。API key 可用,但是项目级的且不会自动轮换。Foundry 的端点由 resource: 推导,可选的 base_url 用来为主权云(如 Azure Government)覆盖。
| 设置 | 做法 |
|---|---|
| RBAC | 在 Foundry 资源上给网关的身份授予 Azure AI User 或 Cognitive Services User |
| 部署 | Foundry 使用管理员选择的部署名而不是规范的模型 ID,要加 models: 块把每个规范 ID 映射到你的部署名 |
| AKS(workload identity) | 把 User-Assigned Managed Identity 与集群的 OIDC issuer 联合并绑定到网关的服务账号;use_azure_ad: true 通过 WorkloadIdentityCredential 取用 |
| ACI / App Service | 在资源上启用系统分配或用户分配的托管身份;use_azure_ad: true 自动取用 |
| 其他环境 | auth: { api_key: "${FOUNDRY_API_KEY}" },{ } 里的 ${…} 要加引号 |
给上游请求加固定头
在某个上游设 headers: 可以给网关发往该上游的请求加固定头,适用于你在提供商前面跑一个按头路由或归属流量的代理。headers: 需要网关服务器运行 Claude Code v2.1.277 及以上,更早的网关遇到该键会拒绝启动;加这个键之前要先升级所有副本,回滚到更早版本前先去掉该键。这些头发往 base_url 所指的服务器,base_url 没设时发往提供商自己的端点,除非你的代理把它们去掉,提供商也会收到。
upstreams:
- provider: vertex
region: us-east5
project_id: example-prod
base_url: https://upstream-proxy.internal.example.com
auth: {}
headers:
x-source: claude-apps-gateway
x-proxy-token: ${PROXY_TOKEN}值是前后没有空格的可打印 ASCII 文本;数字、true、false 要加引号让 YAML 当作文本读。要把密钥挪出配置文件,用密钥展开从环境变量 ${VAR} 或文件 ${file:/path} 载入值,${VAR} 解析为空值会让网关无法启动。headers: 对每个提供商都有效,每个上游只发自己的。并不是网关发往该上游的所有请求都带:
| 网关发往该上游的请求 | 是否带 headers: |
|---|---|
/v1/messages(流式或非流式)和 /v1/messages/count_tokens | 带 |
| 从别的上游故障转移过来的请求 | 带,只带这个上游自己的 headers: |
Bedrock 为客户端放弃的请求发的 CountTokens 调用 | 不带 |
| Workload Identity Federation 令牌交换 | 不带 |
在用 AWS SigV4 签名的 Bedrock 或 Claude Platform on AWS 上游上,这些头是签名的一部分,所以你的代理必须原样透传。使用网关保留的名字会让它拒绝启动,启动错误会点出这个头。保留的名字包括 authorization、x-api-key、host、content-type、user-agent,以及以 anthropic-、x-goog-、x-amz-、x-amzn- 开头的任何名字。
多个上游
同一个提供商可以带不同的 name: 出现多次,涵盖不同区域、经不同凭据链的不同账号、预置吞吐量与按需、跨提供商回退。网关按顺序试上游;5xx、429、401、403、404、超时和缺少端点(501)会转移,其他 4xx 不会。429 是每上游的容量,所以预置吞吐量(PT)耗尽会转移到按需;若上游设了 forward_user_identity: true,带开发者邮箱的请求得到的 429 视为每用户拒绝,不转移。每个请求都从第一个上游开始,只有排在前面的上游都失败或不服务所请求的模型,请求才到达后面的上游。网关不记录失败的上游,所以上游宕机期间,到达它的每个请求仍会先试它并等它失败;对 Anthropic API 上游,timeouts.upstream_ttfb_ms 限定对宕机上游的等待,对其他提供商不适用,网关会最多等一小时让上游开始响应。404 是每上游的模型可用性,所以没启用某模型的上游不会挡住后面服务它的上游;无法解析所请求模型的上游会被直接跳过,不产生网络往返。
下面的例子先走本区域的预置吞吐量 Bedrock,溢出到按需和第二个账号,最后回退到 Anthropic API:
upstreams:
# 首选:本区域的预置吞吐量
- name: bedrock-pt
provider: bedrock
region: us-east-1
auth: {}
# 溢出:按需跨区域
- name: bedrock-od
provider: bedrock
region: us-west-2
auth: {}
# 不同账号:用静态密钥的另一份 Bedrock 配额
- name: bedrock-acct2
provider: bedrock
region: us-east-1
auth:
aws_access_key_id: ${ACCT2_AKID}
aws_secret_access_key: ${ACCT2_SK}
# 最后手段:直连 Anthropic API
- name: anthropic-fallback
provider: anthropic
auth:
api_key: ${ANTHROPIC_API_KEY}
# 每上游的模型 ID 以上游的 `name:` 为键
models:
- id: claude-opus-4-8
label: Claude Opus 4.8
upstream_model:
bedrock-pt: arn:aws:bedrock:us-east-1:111111111111:provisioned-model/abcdef
bedrock-od: us.anthropic.claude-opus-4-8
bedrock-acct2: us.anthropic.claude-opus-4-8
anthropic-fallback: claude-opus-4-8| 要做的事 | 做法 |
|---|---|
| 不同区域 | 每个区域一个 Bedrock 上游,各带自己的 region:;auto_include_builtin_models: true 时跨区域推理配置文件自动路由;固定区域的部署用 models: 块 |
| 不同账号 | 每个账号一个 Bedrock 上游;默认链(auth: {})用 pod 的身份;第二个账号可加 assume_role 以短期凭据到达,或在 auth: 里设显式凭据或 bearer 令牌 |
| 预置吞吐量 | 在 models: 里把模型映射到该上游名字对应的 PT ARN;其他上游保持按需 ID,所以先耗尽 PT 容量再故障转移 |
| VPC / FIPS 端点 | 在上游设 base_url: 为你的 VPC 或 FIPS 端点 URL |
| 按模型路由 | 只有自定义模型 id(不是内置 Claude 模型)会跳过 upstream_model: 映射里没有的上游;内置模型会按顺序在每个上游上尝试,映射里没有条目时用提供商默认 ID,所以对内置模型,映射改变的是上游收到哪个 ID,而不是是否试它 |
跨云提供商或回退到直连 Anthropic API 会改变管辖该请求的协议、地域和其他条款。CLI 对网关应用相同的功能门控,不论哪个上游服务某个请求,所以故障转移不会发送上游会拒绝的请求体字段。
可选部分的字段
admin:write_keys和read_keys({id, key}数组,密钥至少 32 字符,id唯一;写密钥可列出、设置和删除支出限额,读密钥只读);admin_groups(IdP 组名,网关 JWT 的groups声明包含其中之一即有完全管理访问,审计记为oidc:<sub>);blocked_message(原样附加到被阻止开发者看到的429 billing_error);audit_retention_days(默认365);spend_retention_months(默认13)pricing:覆盖和倍数,见「支出限额」页models:管理员维护的模型列表和每个上游的模型 ID 映射managed:按 IdP 组匹配的托管设置策略(含availableModels允许列表),网关向已登录的客户端下发telemetry:每个目的地的 OTLP 指标,日志和追踪按目的地选择启用
managed 详解
managed 块定义以 IdP 组或邮箱域为键的基于角色的访问策略。策略按顺序评估,选中第一个匹配的,再合并到 match: {} 兜底基线上;它们按用户在 GET /managed/settings 提供,带 ETag/304 缓存。
managed:
policies:
# 先写具体的组
- match: { groups: [eng-contractors] }
cli:
availableModels: [claude-sonnet-4-6]
permissions: { deny: ["WebFetch", "WebSearch"] }
# 默认兜底放最后:匹配所有已认证用户
- match: {}
cli:
availableModels: [claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5]match: {} 兜底(习惯上放最后)被当作基础层,其他每个策略都会继承它没设的键,所以各角色条目只需列出与组织默认不同的部分。合并规则取决于键的类型:
- 允许列表(
availableModels、permissions.allow):具体策略的列表完全替换基线的 - 拒绝列表和 hook 数组(
permissions.deny、permissions.ask、disabledMcpjsonServers、deniedMcpServers、blockedMarketplaces以及每个hooks事件类型数组):取基线与策略的并集,这样全组织的拒绝或审计 hook 不会被某个角色的覆盖意外丢掉 - 记录型键(
env、modelOverrides、skillOverrides):浅合并,角色的env块覆盖它设了的键,其余继承基线
availableModels 还会在 /v1/messages 上由服务端强制,所以被拒的模型不论客户端发什么都返回 400。网关在转发请求前自己校验 model 值,畸形值不会到达上游:值缺失或为空时以 model is required 回 400(需要网关 v2.1.228 及以上);值存在但不是字符串时以 model must be a string 回 400(需要 v2.1.221 及以上)。
| 匹配器 | 行为 |
|---|---|
match: {} | 匹配每个已认证用户;先写一个,之后再在它上面加按组的策略 |
match: { groups: [a, b] } | JWT 的 groups 声明含任一列出的组即匹配;区分大小写,必须与 IdP 的大小写完全一致 |
match: { email_domain: example.com } | 匹配 JWT email 声明里最后一个 @ 之后的部分,不区分大小写;每个策略只接受一个域名 |
match: { groups: [a], email_domain: example.com } | 两个条件都要匹配 |
已认证但不匹配任何策略的用户得到网关默认值:目录里的所有模型,没有托管设置。想保证有默认策略,就在最后加 match: {} 兜底。
网关自己不维护用户目录:它根据用户 IdP 令牌授权每个请求,从令牌的 groups 声明读组成员并据此评估策略。没有名册可枚举,也没有账号可预建,所以没有 SCIM 端点,因为没有东西可同步。用户和组的生命周期管理在真相源头做,也就是你的 IdP 原生 SCIM 预配或身份治理平台,那里管理的成员关系和取消预配会通过令牌自动流入网关(要对 Claude 账号本身做 SCIM 预配,那是 Claude for Enterprise 的能力)。有两个传播时钟:策略内容——编辑策略并重新部署后,已连接的客户端在下一次托管设置轮询时(一小时内)收到,仅在下次启动才应用的改动除外;组成员——改变用户的组成员会改变匹配到他的策略,在下一次会话重新签发(下一次静默刷新,受 session.ttl_hours 限制)时生效。
会让网关在启动时停止的匹配器值
启动时网关检查每个策略的 match 块和 admin_groups 列表,下列值会让网关带着点名该字段的错误停止:空的 groups 列表;groups 或 admin_groups 里的空条目;空的 email_domain;含 @、空白或逗号的 email_domain(网关在检查前会先去掉首尾空白并去掉一个前导 @,所以请写一个裸域名如 example.com)。
v2.1.232 之前,网关带着这些值也能启动,影响各不相同:空 email_domain 会跳过域检查,因此没有 groups 列表且 email_domain 为空的策略会匹配每个已认证用户;空 groups 列表使策略谁也不匹配;含 @、空白或逗号的 email_domain 使策略谁也不匹配;groups 或 admin_groups 里的空条目只在该用户的 IdP groups 声明里也含空条目时才匹配,在 admin_groups 里这会授予管理访问(如果你的 admin_groups 从没含过空条目,就没有人以此获得管理访问)。
cli 里放什么
每个 cli 值都是一份完整的 Claude Code managed-settings.json 文档,与你通过 MDM 或 /etc/claude-code/managed-settings.json 部署的 schema 相同,只是写成 YAML。CLI 在托管层应用送达的文档,优先于用户和项目设置,取代服务器托管设置,因此会忽略只限于操作系统级策略来源的设置,如 policyHelper 和 wslInheritsWindowsSettings。
网关在启动时按 CLI 的设置 schema 校验每份文档,所以无法识别的顶层键会让启动失败,错误点名每个有问题的键;schema 里刻意开放的部分仍接受任意值,因为较新的客户端可能认识网关 schema 不认识的条目,这些开放的键包括 env、pluginConfigs 和 permissions 下的嵌套键。校验用的是随网关安装版本捆绑的 schema,所以把较新 Claude Code 版本引入的顶层设置键放进托管配置,要先升级网关;新策略先在一个客户端上冒烟测试再推广。运维最先用到的键:
managed:
policies:
- match: {}
cli:
# 模型访问(在 /v1/messages 上也由服务端强制)
availableModels: [claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5]
# 权限策略
permissions:
deny:
- "WebFetch"
- "Read(./.env)"
- "Read(./secrets/**)"
disableBypassPermissionsMode: disable # 阻止 --dangerously-skip-permissions
allowManagedPermissionRulesOnly: true # 忽略用户/项目的权限规则
# 推进 CLI 进程的环境。DISABLE_UPDATES 阻止后台和手动更新;
# DISABLE_AUTOUPDATER 只停后台更新。
env:
DISABLE_UPDATES: "1" # 通过你自己的分发固定版本
# 全组织 hooks。hook 命令运行在开发者机器上,不是网关,
# 所以路径必须在策略涉及的每个客户端操作系统上存在。
hooks:
PostToolUse:
- matcher: "Edit|Write"
hooks:
- { type: command, command: /usr/local/bin/audit-edit.sh }| 键 | 由谁强制 | 作用 |
|---|---|---|
availableModels | 网关 + CLI | 模型白名单;在 /v1/messages 上也会检查,所以打过补丁的客户端绕不过去 |
permissions.allow / .deny | CLI | 工具和命令规则 |
permissions.disableBypassPermissionsMode | CLI | 设为 disable 阻止跳过权限提示的 bypassPermissions 模式和 --dangerously-skip-permissions 标志 |
allowManagedPermissionRulesOnly | CLI | 为 true 时托管设置成为权限规则唯一的设置来源 |
env | CLI | 合并进 CLI 进程的环境变量,用于遥测、自动更新和模型名覆盖 |
hooks | CLI | 全组织 hooks |
managedMcpServers | CLI | 提供给每个匹配开发者的远程 MCP 服务器,与他们自己加的并存,仅限 http 和 sse;需要网关服务器和客户端都是 Claude Code v2.1.259 及以上,更早的客户端忽略该键 |
因为这些设置经网络到达,CLI 在应用下列设置前会给每个开发者显示安全审批对话框:hooks;需要开发者批准的 env 变量(如代理和 base-URL 变量);apiKeyHelper、statusLine 这类执行 shell 的设置;沙盒二进制设置 sandbox.bwrapPath、sandbox.socatPath、sandbox.ripgrep;以及拦截流量、注入凭据或削弱隔离的沙盒设置,如 sandbox.network.tlsTerminate 和代理端口设置。Claude Code 对某些送达的 env 变量(如模型选择设置和数值上限)不显示对话框直接应用;非空的代理、base-URL 或 OTEL_EXPORTER_OTLP_ENDPOINT 值总是需要批准;需要批准时对话框会点出变量名。网关的遥测配置会推送 OTEL_EXPORTER_OTLP_ENDPOINT,所以设置 telemetry.forward_to 会在每个交互式客户端触发对话框。该对话框保护的是开发者的机器免受被攻破或恶意的网关,不是保护组织免受开发者。
非交互运行(如 claude -p 或 Agent SDK 会话)无法显示对话框,它只为该次运行应用推送的设置,不记录为已批准,所以开发者下一次交互会话仍会显示对话框(v2.1.207 之前,非交互运行会把设置存为已批准,之后的交互会话不再显示)。开发者拒绝时,Claude Code 退出该会话而不应用策略。所以当你把新 hook 或任何触发对话框的 env 变量推给范围广的策略时,每个匹配的开发者都会在交互会话里看到对话框:运行中的交互会话在下一次每小时轮询时显示,否则在开发者下次交互启动时出现。cli 键在更早版本里叫 settings,该拼写仍作为别名接受,但新部署应使用 cli。
策略里的 MCP 服务器
要给策略匹配的 Claude Code 客户端提供 MCP 服务器,在该策略的 cli 块里设 managedMcpServers(网关服务器和客户端都需要 v2.1.259 及以上)。网关在启动时用 Claude Code 在客户端应用的相同规则检查每个条目,条目不通过就拒绝启动并点出该条目。gateway.yaml 里写的 ${VAR} 引用会在启动时、条目检查之前经密钥展开从网关环境解析,所以每个匹配的客户端收到的是字面值并能读到它,对提供服务器的头的指导适用于展开后的值。网关拒绝 .mcp.json 写法 mcpServers 出现在 cli 块里,启动错误会点出应使用 managedMcpServers(v2.1.259 之前,网关拒绝 cli 块里的任何 MCP 服务器定义)。
Claude Desktop 叠加层
如果你的组织也部署 Claude Desktop,同一个网关同时服务两种客户端。把 Claude Desktop 托管配置里的 bootstrapUrl 指向 <listen.public_url>/user/bootstrap,Claude Desktop 由该 URL 推导 OAuth issuer,对这个网关跑同样的设备码登录,并从响应里取配置。需要网关服务器 Claude Code v2.1.203 及以上,且要显式选择加入:除非匹配该用户的策略带有 desktop 键,否则 /user/bootstrap 返回 404。空的 desktop: {} 即可让策略加入,match: {} 基线上的 desktop 键让继承它的每个策略都加入。审计日志把每个请求记为 desktop_bootstrap.serve 或 desktop_bootstrap.denied。
网关从匹配策略的 cli 块和顶层网关配置推导响应的大部分内容:
- 模型列表,来自
availableModels - 被禁用的工具,来自裸工具名的
permissions.deny条目;如果在策略的desktop块里设了disabledBuiltinTools,网关提供你的值与推导列表的并集,所以这样只能多禁用工具,不能把通过permissions.deny禁用的工具重新启用 - 出站白名单,来自
sandbox.network.allowedDomains;如果在desktop块里设了coworkEgressAllowedHosts,网关用该值代替推导出的列表 - 指向网关自身的 OTLP 端点和已登录用户的身份属性:网关把在该端点收到的导出转发到你的
forward_to目的地;仅当同时设置了telemetry.forward_to和listen.public_url时才包含端点和属性。Claude Desktop 每个信号用同一种编码:http/protobuf,或在策略的env里把OTEL_EXPORTER_OTLP_PROTOCOL(或其按信号的变体)设为http/json时用http/json(网关服务器在 v2.1.261 之前,响应一律设为http/json,所以只接受 protobuf 的收集器会拒绝 Claude Desktop 的导出)
要在策略的 desktop 块里设 disabledBuiltinTools、coworkEgressAllowedHosts 或 Claude Desktop 自己的 managedMcpServers,网关服务器需要 v2.1.232 及以上;Claude Desktop 的 managedMcpServers 取数组值而不是对象。网关会把没有 Claude Desktop 对应项的键(如 hooks 和 Bash(npm *) 这样限定范围的权限规则)从 bootstrap 响应里省略。在 cli 旁边加可选的 desktop 块直接设置 Claude Desktop 设置,按 Claude Desktop 托管配置参考的扁平键名书写,不要写 Claude Desktop 只从 MDM 或本地文件读取的键(如 bootstrapUrl),网关会在启动时拒绝它们。
managed:
policies:
- match: { groups: [eng-contractors] }
cli:
availableModels: [claude-sonnet-4-6]
desktop:
isLocalDevMcpEnabled: false
disableAutoUpdates: true
banner: { text: "Contractor build: internal use only" }每个键都是可选的,省略的键 Claude Desktop 用自己的默认值。网关在启动时按 Claude Desktop 自己使用的配置 schema 校验每个 desktop 块,所以错误在网关启动时以点名该键的错误浮现,而不是到达每个已连接的桌面端。块里含有下面这些时网关启动失败:未知键;Claude Desktop 会拒绝或静默丢弃其值的已识别键(如空值或嵌套条目里拼错的子键;v2.1.260 之前,网关会静默丢掉 managedMcpServers 或 orgPluginSettings 条目嵌套对象里拼错的字段,而不是启动失败);网关自己计算的键(推理连接、模型列表和 OTLP 转发,要通过 upstreams、models 和 telemetry 的 forward_to 配置);当前键的遗留别名(启动错误会点出应写的规范键)。使用已弃用的值或条目形式(如没有 transport 的 managedMcpServers 条目)时,网关能启动,并记录点名替代项的警告。desktop 块按网关安装版本捆绑的 schema 校验,所以要送达较新 Claude Desktop 版本引入的设置,要先升级网关;例如 userPluginMarketplacesEnabled 和 userPluginUploadsEnabled 需要网关服务器 v2.1.260 及以上、成员机器 Claude Desktop 1.37937.0 及以上。blockReadsOutsideWorkingDirectories、disableBypassPermissionsMode、configRecheckIntervalMinutes、sshClientPath,以及 microsoftAuthBroker 的 required 值和 Microsoft 365 managedMcpServers 条目的 continuousAccessEvaluation 字段,需要网关服务器 v2.1.281 及以上;早于该值的 Claude Desktop 版本把 required 读作 disabled,所以要等每个成员的 Claude Desktop 都支持后再设 required。如果在 desktop 块里设了 orgPluginSettings,网关以 Claude Desktop 1.15200.0 及以上读取的数组形式提供;更旧的桌面端会忽略数组,不执行任何插件工具策略,所以依赖它之前要把成员更新到 1.15200.0 或更高。
网关会用 match: {} 兜底的 desktop 块给策略 desktop 块没设的键补值,与 cli 块从基线补值的方式相同。如果在基线和角色策略里都设了 disabledBuiltinTools 或 builtinToolPolicy,网关保留基线的限制:disabledBuiltinTools 取基线列表与策略列表的并集;builtinToolPolicy 里,如果基线把某个工具设为非 allow 的值,即使角色策略对同一工具设了 allow,网关也保留基线的值。其他键只要角色策略里设了就用角色策略的值;数组或嵌套对象(如 banner)整体替换,所以在角色策略里设了 banner.text,网关会丢掉基线的 banner.backgroundColor。不部署 Claude Desktop 就完全不要在策略里写 desktop,此时网关对每个用户的 /user/bootstrap 都返回 404。
与其他托管来源的优先级
如果设备上还有 MDM 下发的策略或本地 managed-settings.json,网关送达的设置排第一。托管层内的优先级、本地来源何时生效,以及 Claude Code 不管选中哪个来源都会从每个管理员来源读取的键(如沙盒锁定键、forceRemoteSettingsRefresh 和按变量的 env 合并),见托管设置页。嵌入宿主(如 Claude Desktop)可以通过 SDK 的 managedSettings 选项提供策略。网关策略适用于机器上的每次 Claude Code 调用,包括非交互的 claude -p 运行和 Agent SDK 生成的会话;如果启动时网关不可达,已登录的会话会带着错误退出,而不是不带策略运行。
telemetry 详解
CLI 把指标、日志和(启用时)追踪发给网关,网关原样转发到每个配置的目的地,导出使用 OpenTelemetry Protocol(OTLP)over HTTP。要跳过转发让会话直接导出到你的收集器,在策略里指明收集器。CLI 发出哪些指标和事件见「监控用量」。
在经 /login 登录的会话里,CLI 用从网关签发的 JWT 读取的已认证用户身份给每次导出打标:user.id、user.email、user.groups 属性,所以按开发者的成本和用量归属无需开发者侧配置。经网关登录的 Claude Desktop 和 Cowork 会话用 user.email 和 user.groups 加 enduser.id 给遥测打标,所以一个按 user.email 或 user.groups 的查询就能覆盖终端、Desktop 和 Cowork 用量;user.groups 是逗号分隔的 IdP 组列表。Desktop 和 Cowork 遥测还带有 enduser.sub,即你的 IdP 为用户签发的 sub 声明,用户邮箱变化时它不变;终端会话把同一个值打在 user.id 下,所以把 enduser.sub 与终端 user.id 匹配的查询能把同一用户的终端、Desktop 和 Cowork 用量放在一起(在 Desktop 和 Cowork 导出里,user.id 是匿名标识而不是 subject)。和 Claude Code 的所有 OpenTelemetry 数据一样,这些属性只发给你的组织配置的目的地,从不发给 Anthropic。
用户的组列表经百分号编码后超过 255 字符,或某个组名含逗号或等号时,网关不会截断而是从该用户的 Desktop 和 Cowork 遥测里去掉 user.groups(其终端会话仍带完整列表)。subject 经百分号编码后超过 255 字符,或含空格、非可打印 ASCII 字符或 , ; = \ " % 之一时,网关去掉 enduser.sub,该用户的 Desktop 和 Cowork 遥测保留其他属性。Desktop 和 Cowork 遥测上的 user.email 和 user.groups 需要网关服务器 v2.1.265 及以上,user.groups 还要每个开发者机器上 Claude Desktop 1.24012 及以上;enduser.sub 需要网关服务器 v2.1.274 及以上。
telemetry:
forward_to:
- url: https://otel-collector.internal.example.com
headers:
Authorization: ${OTLP_TOKEN}
# 按信号选择加入。默认:只有指标。
metrics: true
logs: false
traces: false
- url: https://api.datadoghq.com/api/v2/otlp
headers:
DD-API-KEY: ${DD_API_KEY}注意:每个目的地独立选择是否启用 metrics、logs 和 traces,默认只有指标。这些信号的敏感度不同:指标是聚合计数,如 token 数、请求数和延迟;日志和追踪可能带有完整的 Bash 命令、工具输入和文件路径,涵盖 Claude Code 在开发者机器上做的任何事。只在具备相应访问控制和保留策略的目的地上启用日志和追踪。
每个 forward_to 的 URL 必须用 https://,只有网关自己环回接口上的收集器例外:http://localhost:<port> 能通过配置校验,但 SSRF 防护会以 ECONNREFUSED_SSRF 阻止每次导出,除非在网关环境里设 CLAUDE_GATEWAY_ALLOW_LOOPBACK=1;http://127.0.0.1:<port> 或 http://[::1]:<port> 在没设该变量时启动失败。对集群内的收集器,在它自己的内部地址上通过 HTTPS 暴露,或作为 sidecar 运行并设该变量。设了 HTTPS_PROXY 时,网关通过该代理发送导出;要直接访问内部收集器,按主机名或带前导点的域(如 .internal.example.com,需要网关服务器 v2.1.277 及以上)把它加入 NO_PROXY,并确保网关不经代理能到达收集器;没有前导点的条目只匹配那个确切名字,不匹配其下的名字,CIDR 范围不匹配。启用仅代理出站时,应改为在代理里放行收集器,因为任何 NO_PROXY 条目都会让仅代理出站保持关闭。
CLI 默认关闭遥测。同时设置 telemetry.forward_to 和 listen.public_url 时,网关通过 /managed/settings 推送六个环境变量为已连接的客户端启用它:CLAUDE_CODE_ENABLE_TELEMETRY=1;OTEL_METRICS_EXPORTER、OTEL_LOGS_EXPORTER、OTEL_TRACES_EXPORTER,只要至少一个 forward_to 目的地启用该信号就设为 otlp,否则设为 none;OTEL_EXPORTER_OTLP_ENDPOINT=<public_url>;OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf。添加自己的标签时,网关还会推送 OTEL_RESOURCE_ATTRIBUTES(网关服务器 v2.1.265 之前,三个导出器选择器一律推为 otlp,包括没有目的地选择加入的信号)。推送的端点由公开 URL 构成,所以指标和日志不需要开发者或策略做任何 OTEL 配置。
经 /login 登录的开发者无法用自己的 OTEL 配置重定向导出:Claude Code 在托管层应用推送的变量,所以每个变量都覆盖开发者本地设的值;启用 OTLP/HTTP 导出时,CLI 忽略任何本地配置的端点,不论网关有没有推送遥测变量,导出都去网关,除非策略把你的收集器指定为端点。某个信号没有 forward_to 目的地时,网关接收并丢弃它。如果开发者已经把 Claude Code 遥测导出到你的某个收集器,把它加为 forward_to 目的地(他们导出日志或追踪时也启用对应信号),这样他们登录后它仍能接收数据;或者改为在策略里指明收集器来跳过转发。追踪还需要每个客户端设 CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1:因为网关不推送它,要在托管策略的 env 块里设,开发者在推送端点已经触发的同一个安全审批对话框里批准。只在你想追踪其组的策略里把它设为 1;没设它的策略按合并规则从 match: {} 兜底策略继承(如果兜底设了);要让某个组的客户端即使开发者本地设了该变量也不发追踪,在该组策略里把它设为 0。protobuf 和 JSON 两种 OTLP 编码都会被转发,任何兼容 OpenTelemetry 的后端都能作为目的地。
添加自己的标签
要给经网关登录的会话的遥测加上固定标签,如 service.namespace 或 deployment.environment.name,设 telemetry.resource_attributes。每个标签是一个 OpenTelemetry 资源属性,每个目的地收到相同的标签。仅当同时设置了 telemetry.forward_to 和 listen.public_url 时会话才得到这些标签。
telemetry:
forward_to:
- url: https://otel-collector.internal.example.com
resource_attributes:
service.namespace: claude
deployment.environment.name: prod标签违反下列规则时网关拒绝启动,启动错误会点出该标签:名字只用字母、数字、.、_ 和 -;名字不是保留的(不论大小写,保留的有所有以 user.、enduser.、identity. 开头的名字,以及 service.name、service.version、claude.deployment_mode、host.arch、os.type、os.version、wsl.version);值是非空的可打印 ASCII,不含空格也不含 , ; = \ " %;值按网关在百分号编码后的计数最多 255 字符(/、:、@ 各算三个);值是文本,数字、true、false 要加引号。设置 telemetry.resource_attributes 需要网关服务器 v2.1.281 及以上,更早的网关遇到该键会拒绝启动,所以加键前要升级每个副本,回滚前要先去掉该键。经 /login 登录的终端会话把这些标签作为 OTEL_RESOURCE_ATTRIBUTES 与其他遥测变量一起推送;如果你在策略的 env 块里设了 OTEL_RESOURCE_ATTRIBUTES,该策略匹配的终端会话得到的是那个值而不是这些标签;Claude Desktop 则从网关随 user.email 等身份属性一起拿到标签。Claude Code 还把每个标签复制到每个指标数据点上,以便在不索引资源属性的后端里按它过滤;要关闭这份复制,见指标基数控制。
直接导出到你的收集器
要让经 /login 登录的会话把遥测直接发到你的收集器而不经转发,在托管策略的 env 块里把 OTEL_EXPORTER_OTLP_ENDPOINT 设为收集器的 https:// 基础 URL。Claude Code 会在你设的 URL(如 https://otel-collector.example.com:4318)后追加 /v1/metrics、/v1/logs 或 /v1/traces,并通过 OTLP/HTTP 把每个信号导出到那里;每个开发者机器需要 v2.1.265 及以上。要向收集器认证,在同一个 env 块里设 OTEL_EXPORTER_OTLP_HEADERS;会话从不把开发者的网关会话令牌发给以这种方式指定的收集器。在策略里添加或更改该端点时,Claude Code 会在交互会话应用前让每个开发者在安全审批对话框里批准。
Claude Code 在直接导出一个信号前会检查端点,检查失败就把该信号留在转发上,检查包括:端点来自网关本身(如果你在 MDM 配置或本地 managed-settings.json 里设了同一变量,导出留在转发上);URL 使用 https://,或指向环回地址的 http://;URL 解析后路径以 /v1/<signal> 结尾,没有查询或片段(Claude Code 从通用变量自己构造该路径,而按信号的变量如 OTEL_EXPORTER_OTLP_METRICS_ENDPOINT 按原样使用,所以要写全路径);URL 不是网关自己的主机(指向网关的端点保留转发路径和其会话令牌);你和开发者都没有在任何设置来源里配置 otelHeadersHelper(配置了 helper 时每个信号都留在转发上)。你指定的端点只改变导出去向,哪些信号导出仍由 OTEL_*_EXPORTER 选择器决定。仅有端点不会开启导出,所以除非网关已推送,还要设置开启它的变量:网关已推送遥测变量时,它们涵盖启用、选择器和协议,你显式的端点覆盖推送的 <public_url> 值,只有没有任何 forward_to 目的地启用的信号才需要你自己把对应 OTEL_*_EXPORTER 选择器设为 otlp;没推送时,还要设 CLAUDE_CODE_ENABLE_TELEMETRY=1、OTEL_*_EXPORTER 选择器和 OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf。开发者登出或登录到另一个网关时,到收集器的导出停止,Claude Code 丢弃剩余的批次而不是发送。
目的地失败时
网关不缓冲、不重试、不存储遥测,所以没到达目的地的导出会被丢弃而不是延迟送达。每个目的地各自成败,不论成败导出的客户端都收到成功响应,所以失败的投递只出现在网关日志里。对某个目的地连续五次投递失败后,网关以 30 秒为一段暂停向它转发(每次暂停都记录日志),直到某次投递成功。任何错误响应、超时或连接错误都算失败投递,除了 400、413、415、422 和 431,它们表示收集器认为该次导出的载荷畸形或过大而拒绝。被拒的载荷既不增加也不重置失败计数:网关继续向该目的地转发,并在该目的地第一次被拒时以及此后每第一百次记录一条点名该目的地和状态的警告。
HTTP 调优
四个可选的顶层块 access_control、limits、timeouts、rate_limits 调节 HTTP 面,默认值适合大多数部署。
| 块 | 键 | 默认 | 说明 |
|---|---|---|---|
access_control | allow_cidrs / deny_cidrs | 空 | 按客户端地址(经 trusted_proxies 解析后)做入站 IP 允许/拒绝;先检查 deny_cidrs,匹配到的客户端即使也匹配 allow_cidrs 也被拒绝;allow_cidrs 非空时网关默认拒绝;/healthz 和 /readyz 不受 allow_cidrs 限制 |
limits | max_request_bytes | 32 MiB | 入站请求体的最大值,超大请求在缓冲请求体前得到 413;大文件或图片请求时调大 |
limits | max_request_header_bytes | 未设 | 设置后,过大的头返回 431 |
limits | max_url_length | 未设 | 设置后,过长的 URL 返回 414 |
timeouts | upstream_ttfb_ms | 120000 | 等待上游响应头(首字节时间)的最长时间,之后响应体流式传输,没有挂钟上限;适用于直连 Anthropic 的上游路径,对其他每个提供商网关最多等一小时让响应开始 |
rate_limits | device_authorization.max / .window_seconds | 30 / 600 | 无需认证的设备授权端点上每 IP 的速率限制;大型组织在共享出口 IP 或 NAT 后面时调大;这些限制只适用于设备授权登录流程,不适用于 /v1/messages 推理 |
rate_limits | device_verify.max / .window_seconds | 10 / 600 | /device 上 user_code 提交的每 IP 速率限制,它是阻止有人猜别的开发者代码的东西 |
access_control 两个列表都留空(默认)时,网关为任何客户端地址服务,只有你的网络限制谁能到达它;这一点很重要,因为网关可以推送在开发者机器上执行命令的托管设置。allow_cidrs 为空时,网关在两个地方发出警告,但不改变对任何请求的应答:启动时,运维日志里的警告建议只允许私有范围 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、100.64.0.0/10、127.0.0.0/8、::1/128、fc00::/7 以及开发者连接来源的其他内部范围(如果你把网关绑定到环回地址且既没设 trusted_proxies 也没设 public_url,如本地开发,则不出现该警告);运行时,第一次有请求来自这些私有范围之外的地址时,网关记录警告并发出携带客户端 IP 的 access.public_client 审计事件,两者每个进程只触发一次(链路本地地址 169.254.0.0/16 和 fe80::/10 不算公网;网关在这项检查之前就应答 /healthz 和 /readyz,所以来自公网范围的健康探针不会触发它)。两种信号都使用网关解析出的客户端地址:如果负载均衡器、端口转发或隧道中继流量而没有列在 listen.trusted_proxies 里,网关看到的是中继的地址(通常是私有的),所以运行时警告和私有允许列表都抓不到经它中继的流量。在这样的前端后面,先设 listen.trusted_proxies 让网关看到真实客户端地址,并且不论怎样都让网关及其前面的一切无法从公网到达。
客户端侧的托管设置
开发者机器的托管设置文件里需要的键(forceLoginMethod、forceLoginGatewayUrl、可选的 gatewayInternalNetworks)见「Claude apps gateway」页的「连接开发者」。