6 6
7> 在 Google Cloud 上运行 Claude apps gateway 的实际示例:Cloud Run 或 GKE、Cloud SQL for PostgreSQL、Secret Manager 和 Google Cloud 的 Agent Platform 的服务账户身份验证。7> 在 Google Cloud 上运行 Claude apps gateway 的实际示例:Cloud Run 或 GKE、Cloud SQL for PostgreSQL、Secret Manager 和 Google Cloud 的 Agent Platform 的服务账户身份验证。
8 8
9<Note>9> [!NOTE]
10 本页介绍在 Google Cloud 上运行 Claude apps gateway 的一种方式。该配置是客户管理基础设施的工作示例,而不是受支持的生产部署;使用它来了解各个部分如何组合在一起,然后再根据您自己的环境进行调整。有关平台无关的要求,请参阅[部署指南](/docs/zh-CN/claude-apps-gateway-deploy)。10>
11</Note>11> 本页介绍在 Google Cloud 上运行 Claude apps gateway 的一种方式。该配置是客户管理基础设施的工作示例,而不是受支持的生产部署;使用它来了解各个部分如何组合在一起,然后再根据您自己的环境进行调整。有关平台无关的要求,请参阅[部署指南](/docs/zh-CN/claude-apps-gateway-deploy)。
12 12
13此示例在 Google Cloud 上配置 Claude apps gateway,使用 Google Cloud 的 Agent Platform 作为模型上游,使用 Cloud Run 或 GKE 进行计算。Google Workspace 是示例身份提供商 (IdP),但任何符合 OpenID Connect (OIDC) 的 IdP 都可以工作;只有 `oidc` 块会改变。有关每个 IdP 的详细信息,请参阅[身份提供商设置](/docs/zh-CN/claude-apps-gateway-deploy#identity-provider-setup)。13此示例在 Google Cloud 上配置 Claude apps gateway,使用 Google Cloud 的 Agent Platform 作为模型上游,使用 Cloud Run 或 GKE 进行计算。Google Workspace 是示例身份提供商 (IdP),但任何符合 OpenID Connect (OIDC) 的 IdP 都可以工作;只有 `oidc` 块会改变。有关每个 IdP 的详细信息,请参阅[身份提供商设置](/docs/zh-CN/claude-apps-gateway-deploy#identity-provider-setup)。
14 14
15<h2 id="what-you’ll-build">15## 您将构建的内容
16 您将构建的内容
17</h2>
18 16
19<Frame>17
20 <img src="https://mintcdn.com/claude-code/-uq-4JE0W_JO5Er5/images/claude-gateway-gcp-architecture.svg?fit=max&auto=format&n=-uq-4JE0W_JO5Er5&q=85&s=cb705151c69128ac0da235852d5600ab" alt="Google Cloud 上 Claude apps gateway 的图表:Claude Code 客户端通过 HTTPS 连接到网关(Cloud Run 或 GKE),网关在 VPC 内运行,旁边是用于会话状态的私有 IP Cloud SQL 数据库。网关通过 OIDC 针对 Google Workspace 对用户进行签名,从 Secret Manager 读取配置和机密,将模型请求转发到 Google Cloud 的 Agent Platform,并在部署时从 Artifact Registry 拉取其镜像。" width="760" height="400" data-path="images/claude-gateway-gcp-architecture.svg" />
21</Frame>
22 18
23部署包括:19部署包括:
24 20
29* **服务账户**,具有 `roles/aiplatform.user`,直接附加到 Cloud Run 或通过 Workload Identity 绑定到 GKE25* **服务账户**,具有 `roles/aiplatform.user`,直接附加到 Cloud Run 或通过 Workload Identity 绑定到 GKE
30* **HTTPS 前端**,由您提供:Cloud Run 前面的内部应用负载均衡器,本演练为网关配置但不创建,或 GKE 上类别为 `gce-internal` 的内部 **GKE Ingress**26* **HTTPS 前端**,由您提供:Cloud Run 前面的内部应用负载均衡器,本演练为网关配置但不创建,或 GKE 上类别为 `gce-internal` 的内部 **GKE Ingress**
31 27
32<h2 id="prerequisites">28## 前置条件
33 前置条件
34</h2>
35 29
36* 启用了计费的 GCP 项目,以及创建上述资源的权限30* 启用了计费的 GCP 项目,以及创建上述资源的权限
37* `gcloud` CLI,使用 `gcloud auth login` 进行身份验证,并在本地安装了 Docker31* `gcloud` CLI,使用 `gcloud auth login` 进行身份验证,并在本地安装了 Docker
48gcloud config set project "$PROJECT_ID"42gcloud config set project "$PROJECT_ID"
49```43```
50 44
51<h2 id="deploy-the-gateway">45## 部署网关
52 部署网关
53</h2>
54 46
55以下步骤使用 `gcloud` 命令配置完整部署。47以下步骤使用 `gcloud` 命令配置完整部署。
56 48
57<Steps>49**1. 启用 API**
58 <Step title="启用 API">
59 启用演练使用的服务 API:
60 50
61 ```bash theme={null}51启用演练使用的服务 API:
62 gcloud services enable \52
53```bash theme={null}
54gcloud services enable \
63 aiplatform.googleapis.com \55 aiplatform.googleapis.com \
64 artifactregistry.googleapis.com \56 artifactregistry.googleapis.com \
65 sqladmin.googleapis.com \57 sqladmin.googleapis.com \
70 servicenetworking.googleapis.com \62 servicenetworking.googleapis.com \
71 run.googleapis.com \63 run.googleapis.com \
72 container.googleapis.com64 container.googleapis.com
73 ```65```
74 66
75 所需的 API 取决于部署路径:67所需的 API 取决于部署路径:
76 68
77 * `compute` 和 `servicenetworking`:私有 IP Cloud SQL 路径所需69* `compute` 和 `servicenetworking`:私有 IP Cloud SQL 路径所需
78 * `run`:仅限 Cloud Run70* `run`:仅限 Cloud Run
79 * `container`:仅限 GKE71* `container`:仅限 GKE
80 </Step>
81 72
82 <Step title="创建服务账户并授予 IAM">73**2. 创建服务账户并授予 IAM**
83 网关作为专用服务账户运行,有权调用 Google Cloud 的 Agent Platform。它通过 VPC 使用密码用户访问 Cloud SQL,因此不需要 Cloud SQL IAM 角色:
84 74
85 ```bash theme={null}75网关作为专用服务账户运行,有权调用 Google Cloud 的 Agent Platform。它通过 VPC 使用密码用户访问 Cloud SQL,因此不需要 Cloud SQL IAM 角色:
86 gcloud iam service-accounts create claude-gateway --display-name="Claude apps gateway"
87 SA="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com"
88 76
89 gcloud projects add-iam-policy-binding "$PROJECT_ID" \77```bash theme={null}
78gcloud iam service-accounts create claude-gateway --display-name="Claude apps gateway"
79SA="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com"
80
81gcloud projects add-iam-policy-binding "$PROJECT_ID" \
90 --member="serviceAccount:${SA}" --role="roles/aiplatform.user" --condition=None82 --member="serviceAccount:${SA}" --role="roles/aiplatform.user" --condition=None
91 ```83```
92 84
93 然后在 Model Garden 中为项目启用 Claude 模型;模型发布到特定区域,因此请检查每个模型卡。85然后在 Model Garden 中为项目启用 Claude 模型;模型发布到特定区域,因此请检查每个模型卡。
94 </Step>
95 86
96 <Step title="构建镜像并将其推送到 Artifact Registry">87**3. 构建镜像并将其推送到 Artifact Registry**
97 根据[容器镜像要求](/docs/zh-CN/claude-apps-gateway-deploy#container-image)构建镜像,使用 `linux-x64` glibc 二进制文件,然后推送:
98 88
99 ```bash theme={null}89根据[容器镜像要求](/docs/zh-CN/claude-apps-gateway-deploy#container-image)构建镜像,使用 `linux-x64` glibc 二进制文件,然后推送:
100 gcloud artifacts repositories create claude-gateway \90
91```bash theme={null}
92gcloud artifacts repositories create claude-gateway \
101 --repository-format=docker --location="$REGION"93 --repository-format=docker --location="$REGION"
102 gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet94gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet
103 95
104 # Cloud Run 需要 linux/amd64。--provenance=false 避免 buildx OCI96# Cloud Run 需要 linux/amd64。--provenance=false 避免 buildx OCI
105 # 镜像索引被 Cloud Run 拒绝。97# 镜像索引被 Cloud Run 拒绝。
106 docker build --platform=linux/amd64 --provenance=false \98docker build --platform=linux/amd64 --provenance=false \
107 -t "${REGION}-docker.pkg.dev/${PROJECT_ID}/claude-gateway/gateway:<version>" .99 -t "${REGION}-docker.pkg.dev/${PROJECT_ID}/claude-gateway/gateway:<version>" .
108 docker push "${REGION}-docker.pkg.dev/${PROJECT_ID}/claude-gateway/gateway:<version>"100docker push "${REGION}-docker.pkg.dev/${PROJECT_ID}/claude-gateway/gateway:<version>"
109 ```101```
110 </Step>
111 102
112 <Step title="为 PostgreSQL 配置 Cloud SQL">103**4. 为 PostgreSQL 配置 Cloud SQL**
113 通过私有服务访问在 VPC 上创建实例,使其没有公共 IP;这也满足强制执行 `constraints/sql.restrictPublicIp` 的项目:
114 104
115 ```bash theme={null}105通过私有服务访问在 VPC 上创建实例,使其没有公共 IP;这也满足强制执行 `constraints/sql.restrictPublicIp` 的项目:
116 VPC=cc-gateway-vpc106
117 gcloud compute networks create "$VPC" --subnet-mode=custom107```bash theme={null}
118 gcloud compute networks subnets create cc-gateway-subnet \108VPC=cc-gateway-vpc
109gcloud compute networks create "$VPC" --subnet-mode=custom
110gcloud compute networks subnets create cc-gateway-subnet \
119 --network="$VPC" --region="$REGION" --range=10.0.0.0/24111 --network="$VPC" --region="$REGION" --range=10.0.0.0/24
120 112
121 # 私有服务访问:每个 VPC 一次113# 私有服务访问:每个 VPC 一次
122 gcloud compute addresses create "google-managed-services-${VPC}" \114gcloud compute addresses create "google-managed-services-${VPC}" \
123 --global --purpose=VPC_PEERING --prefix-length=16 --network="$VPC"115 --global --purpose=VPC_PEERING --prefix-length=16 --network="$VPC"
124 gcloud services vpc-peerings connect \116gcloud services vpc-peerings connect \
125 --service=servicenetworking.googleapis.com \117 --service=servicenetworking.googleapis.com \
126 --ranges="google-managed-services-${VPC}" --network="$VPC"118 --ranges="google-managed-services-${VPC}" --network="$VPC"
127 119
128 gcloud sql instances create claude-gateway-db \120gcloud sql instances create claude-gateway-db \
129 --database-version=POSTGRES_16 --tier=db-g1-small --region="$REGION" \121 --database-version=POSTGRES_16 --tier=db-g1-small --region="$REGION" \
130 --network="projects/${PROJECT_ID}/global/networks/${VPC}" --no-assign-ip122 --network="projects/${PROJECT_ID}/global/networks/${VPC}" --no-assign-ip
131 gcloud sql databases create claude_gateway --instance=claude-gateway-db123gcloud sql databases create claude_gateway --instance=claude-gateway-db
132 PGPASS="$(openssl rand -hex 24)"124PGPASS="$(openssl rand -hex 24)"
133 gcloud sql users create gateway --instance=claude-gateway-db --password="$PGPASS"125gcloud sql users create gateway --instance=claude-gateway-db --password="$PGPASS"
134 126
135 PRIVATE_IP="$(gcloud sql instances describe claude-gateway-db \127PRIVATE_IP="$(gcloud sql instances describe claude-gateway-db \
136 --format='value(ipAddresses[0].ipAddress)')"128 --format='value(ipAddresses[0].ipAddress)')"
137 GATEWAY_POSTGRES_URL="postgres://gateway:${PGPASS}@${PRIVATE_IP}:5432/claude_gateway?sslmode=require"129GATEWAY_POSTGRES_URL="postgres://gateway:${PGPASS}@${PRIVATE_IP}:5432/claude_gateway?sslmode=require"
138 ```130```
139 131
140 Cloud Run 或 GKE 运行时必须在此 VPC 上或路由到此 VPC。132Cloud Run 或 GKE 运行时必须在此 VPC 上或路由到此 VPC。
141 </Step>
142 133
143 <Step title="编写 gateway.yaml">134**5. 编写 gateway.yaml**
144 `upstreams` 块使用 `auth: {}` 指向 Google Cloud 的 Agent Platform,因此网关通过运行时服务账户的应用默认凭证进行身份验证。有关每个字段,请参阅[配置参考](/docs/zh-CN/claude-apps-gateway-config)。
145 135
146 两个 `listen` 字段描述网关的前端:136`upstreams` 块使用 `auth: {}` 指向 Google Cloud 的 Agent Platform,因此网关通过运行时服务账户的应用默认凭证进行身份验证。有关每个字段,请参阅[配置参考](/docs/zh-CN/claude-apps-gateway-config)。
147 137
148 * `public_url`:外部 `https://` 源,对于任何非环回绑定都是必需的;请参阅 [`listen` 参考](/docs/zh-CN/claude-apps-gateway-config#listen)。网关仅从此值构建 IdP `redirect_uri` 和其发现文档,从不从 `X-Forwarded-*` 标头构建。138两个 `listen` 字段描述网关的前端:
149 * `trusted_proxies`:前端的源范围。网关仅在 TCP 对等体在此列表中时才遵守 `X-Forwarded-For`,然后遍历链越过受信任的跳跃,因此按 IP 登录速率限制和审计事件记录开发者 IP 而不是负载均衡器的 IP。
150 139
151 设置 `trusted_proxies` 以匹配您的前端。类 `gce` 的外部 GKE Ingress 未列出:它配置一个公共转发规则地址,`/login` [私有网络检查](/docs/zh-CN/claude-apps-gateway#prerequisites)拒绝该地址。140* `public_url`:外部 `https://` 源,对于任何非环回绑定都是必需的;请参阅 [`listen` 参考](/docs/zh-CN/claude-apps-gateway-config#listen)。网关仅从此值构建 IdP `redirect_uri` 和其发现文档,从不从 `X-Forwarded-*` 标头构建。
141* `trusted_proxies`:前端的源范围。网关仅在 TCP 对等体在此列表中时才遵守 `X-Forwarded-For`,然后遍历链越过受信任的跳跃,因此按 IP 登录速率限制和审计事件记录开发者 IP 而不是负载均衡器的 IP。
152 142
153 | 前端 | `trusted_proxies` |143设置 `trusted_proxies` 以匹配您的前端。类 `gce` 的外部 GKE Ingress 未列出:它配置一个公共转发规则地址,`/login` [私有网络检查](/docs/zh-CN/claude-apps-gateway#prerequisites)拒绝该地址。
154 | - | - |
155 | 直接访问的 Cloud Run,无负载均衡器 | `[169.254.0.0/16]` |
156 | Cloud Run 前面的内部应用负载均衡器 | `169.254.0.0/16` 加上您的仅代理子网的 CIDR |
157 | GKE 内部 Ingress,类 `gce-internal` | 您的仅代理子网的 CIDR |
158 144
159 下面的示例使用内部负载均衡器前置 Cloud Run 的值。145| 前端 | `trusted_proxies` |
146| - | - |
147| 直接访问的 Cloud Run,无负载均衡器 | `[169.254.0.0/16]` |
148| Cloud Run 前面的内部应用负载均衡器 | `169.254.0.0/16` 加上您的仅代理子网的 CIDR |
149| GKE 内部 Ingress,类 `gce-internal` | 您的仅代理子网的 CIDR |
160 150
161 ```yaml gateway.yaml theme={null}151下面的示例使用内部负载均衡器前置 Cloud Run 的值。
162 listen:152
153```yaml gateway.yaml theme={null}
154listen:
163 host: 0.0.0.0155 host: 0.0.0.0
164 port: 8080156 port: 8080
165 public_url: https://claude-gateway.internal.example.com157 public_url: https://claude-gateway.internal.example.com
166 trusted_proxies: [169.254.0.0/16, <your-proxy-only-subnet-cidr>]158 trusted_proxies: [169.254.0.0/16, <your-proxy-only-subnet-cidr>]
167 159
168 oidc:160oidc:
169 issuer: https://accounts.google.com161 issuer: https://accounts.google.com
170 client_id: <your-oauth-client-id>162 client_id: <your-oauth-client-id>
171 client_secret: ${OIDC_CLIENT_SECRET} # GKE: ${file:/secrets/oidc-client-secret}163 client_secret: ${OIDC_CLIENT_SECRET} # GKE: ${file:/secrets/oidc-client-secret}
174 scopes: [openid, profile, email]166 scopes: [openid, profile, email]
175 extra_auth_params: { access_type: offline, prompt: consent }167 extra_auth_params: { access_type: offline, prompt: consent }
176 168
177 session:169session:
178 jwt_secret: ${GATEWAY_JWT_SECRET} # GKE: ${file:/secrets/jwt-secret}170 jwt_secret: ${GATEWAY_JWT_SECRET} # GKE: ${file:/secrets/jwt-secret}
179 171
180 store:172store:
181 postgres_url: ${GATEWAY_POSTGRES_URL} # GKE: ${file:/secrets/postgres-url}173 postgres_url: ${GATEWAY_POSTGRES_URL} # GKE: ${file:/secrets/postgres-url}
182 # readiness_grace_seconds: 300 # 在 Cloud SQL 故障转移期间174 # readiness_grace_seconds: 300 # 在 Cloud SQL 故障转移期间
183 # 保持通过就绪探针175# 保持通过就绪探针
184 176
185 upstreams:177upstreams:
186 - provider: vertex178 - provider: vertex
187 region: <your-region> # 必须匹配 $REGION179 region: <your-region> # 必须匹配 $REGION
188 project_id: <your-project>180 project_id: <your-project>
189 auth: {} # 通过运行时服务账户的 ADC181 auth: {} # 通过运行时服务账户的 ADC
190 ```182```
183
184> [!NOTE]
185>
186> Google id\_tokens 不包含 `groups` 声明。要在 [`managed.policies`](/docs/zh-CN/claude-apps-gateway-config#managed) 中使用基于组的策略,以 Google Workspace 作为 IdP,请配置 [`oidc.google_groups`](/docs/zh-CN/claude-apps-gateway-config#oidc),它使用具有域范围委派的服务账户通过 Admin SDK Directory API 查找每个用户的组。没有它,改为匹配 `email_domain`。
191 187
192 <Note>188**6. 在 Secret Manager 中存储密钥**
193 Google id\_tokens 不包含 `groups` 声明。要在 [`managed.policies`](/docs/zh-CN/claude-apps-gateway-config#managed) 中使用基于组的策略,以 Google Workspace 作为 IdP,请配置 [`oidc.google_groups`](/docs/zh-CN/claude-apps-gateway-config#oidc),它使用具有域范围委派的服务账户通过 Admin SDK Directory API 查找每个用户的组。没有它,改为匹配 `email_domain`。
194 </Note>
195 </Step>
196 189
197 <Step title="在 Secret Manager 中存储密钥">190创建四个密钥并向 `claude-gateway` 服务账户授予 `roles/secretmanager.secretAccessor`:
198 创建四个密钥并向 `claude-gateway` 服务账户授予 `roles/secretmanager.secretAccessor`:
199 191
200 | 密钥 | 源 |192| 密钥 | 源 |
201 | - | - |193| - | - |
202 | `gateway-jwt-secret` | `openssl rand -base64 32` |194| `gateway-jwt-secret` | `openssl rand -base64 32` |
203 | `gateway-oidc-client-secret` | Google Cloud 控制台 → OAuth 客户端 |195| `gateway-oidc-client-secret` | Google Cloud 控制台 → OAuth 客户端 |
204 | `gateway-postgres-url` | Cloud SQL 步骤中的 `$GATEWAY_POSTGRES_URL` |196| `gateway-postgres-url` | Cloud SQL 步骤中的 `$GATEWAY_POSTGRES_URL` |
205 | `gateway-config` | 上一步中的完整 `gateway.yaml` |197| `gateway-config` | 上一步中的完整 `gateway.yaml` |
206 198
207 密钥到达容器的方式因路径而异:199密钥到达容器的方式因路径而异:
208 200
209 * 在 GKE 上,它们通过 Secret Manager CSI 驱动程序作为文件挂载,`gateway.yaml` 引用 `${file:/secrets/...}`。201* 在 GKE 上,它们通过 Secret Manager CSI 驱动程序作为文件挂载,`gateway.yaml` 引用 `${file:/secrets/...}`。
210 * 在 Cloud Run 上,它无法将多个密钥挂载到一个目录中,`gateway.yaml` 作为文件挂载,其他三个作为环境变量注入,因此 `gateway.yaml` 改为引用 `${GATEWAY_JWT_SECRET}`、`${OIDC_CLIENT_SECRET}` 和 `${GATEWAY_POSTGRES_URL}`。202* 在 Cloud Run 上,它无法将多个密钥挂载到一个目录中,`gateway.yaml` 作为文件挂载,其他三个作为环境变量注入,因此 `gateway.yaml` 改为引用 `${GATEWAY_JWT_SECRET}`、`${OIDC_CLIENT_SECRET}` 和 `${GATEWAY_POSTGRES_URL}`。
211 </Step>
212 203
213 <Step title="部署">204**7. 部署**
214 <Tabs>
215 <Tab title="Cloud Run">
216 下面的命令在内部负载均衡器后面部署用于生产。
217 205
218 ```bash theme={null}206**Cloud Run**
219 gcloud run deploy claude-gateway \207
208下面的命令在内部负载均衡器后面部署用于生产。
209
210```bash theme={null}
211gcloud run deploy claude-gateway \
220 --image="${REGION}-docker.pkg.dev/${PROJECT_ID}/claude-gateway/gateway:<version>" \212 --image="${REGION}-docker.pkg.dev/${PROJECT_ID}/claude-gateway/gateway:<version>" \
221 --region="$REGION" \213 --region="$REGION" \
222 --service-account="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com" \214 --service-account="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com" \
227 --network="$VPC" --subnet=cc-gateway-subnet --vpc-egress=private-ranges-only \219 --network="$VPC" --subnet=cc-gateway-subnet --vpc-egress=private-ranges-only \
228 --set-secrets=/etc/claude/gateway.yaml=gateway-config:latest,GATEWAY_JWT_SECRET=gateway-jwt-secret:latest,OIDC_CLIENT_SECRET=gateway-oidc-client-secret:latest,GATEWAY_POSTGRES_URL=gateway-postgres-url:latest \220 --set-secrets=/etc/claude/gateway.yaml=gateway-config:latest,GATEWAY_JWT_SECRET=gateway-jwt-secret:latest,OIDC_CLIENT_SECRET=gateway-oidc-client-secret:latest,GATEWAY_POSTGRES_URL=gateway-postgres-url:latest \
229 --no-invoker-iam-check221 --no-invoker-iam-check
230 ```222```
223
224直接 VPC 出口,通过 `--network`、`--subnet` 和 `--vpc-egress=private-ranges-only`,让服务直接到达 Cloud SQL 私有 IP。每个实例最多持有 [`store.max_connections`](/docs/zh-CN/claude-apps-gateway-config#store) 个 Postgres 连接,默认为 5 个,因此保持最大实例数 × `store.max_connections` 低于您的 Cloud SQL 层的连接限制;[参考资产](#terraform-reference)为 `db-g1-small` 层将实例上限设为 8 是出于这个原因。到 Google Cloud 的 Agent Platform 端点和 `accounts.google.com` 的公共出口直接进入互联网,而不是通过 VPC,因此不需要 Cloud NAT。
231 225
232 直接 VPC 出口,通过 `--network`、`--subnet` 和 `--vpc-egress=private-ranges-only`,让服务直接到达 Cloud SQL 私有 IP。每个实例最多持有 [`store.max_connections`](/docs/zh-CN/claude-apps-gateway-config#store) 个 Postgres 连接,默认为 5 个,因此保持最大实例数 × `store.max_connections` 低于您的 Cloud SQL 层的连接限制;[参考资产](#terraform-reference)为 `db-g1-small` 层将实例上限设为 8 是出于这个原因。到 Google Cloud 的 Agent Platform 端点和 `accounts.google.com` 的公共出口直接进入互联网,而不是通过 VPC,因此不需要 Cloud NAT。226调用者 IAM 检查必须打开或禁用。网关运行自己的 OIDC,其客户端不携带 GCP 令牌,因此 Cloud Run 的调用者检查必须允许未经身份验证的请求。网关的 OIDC 登录在请求到达容器后对其进行身份验证,`allowed_email_domains` 限制哪些域可以登录。
233 227
234 调用者 IAM 检查必须打开或禁用。网关运行自己的 OIDC,其客户端不携带 GCP 令牌,因此 Cloud Run 的调用者检查必须允许未经身份验证的请求。网关的 OIDC 登录在请求到达容器后对其进行身份验证,`allowed_email_domains` 限制哪些域可以登录。228两个标志允许未经身份验证的请求:
235 229
236 两个标志允许未经身份验证的请求:230* `--no-invoker-iam-check`:禁用检查,无需管理 `allUsers` 绑定,并在域受限共享下工作
231* `--allow-unauthenticated`:向 `allUsers` 授予 `run.invoker` 角色;如果您的组织不允许 `--no-invoker-iam-check`,请使用它
237 232
238 * `--no-invoker-iam-check`:禁用检查,无需管理 `allUsers` 绑定,并在域受限共享下工作233通过 `--ingress` 的入口限制是与调用者检查分离的独立层;保持设置以将服务限制在您的公司网络。
239 * `--allow-unauthenticated`:向 `allUsers` 授予 `run.invoker` 角色;如果您的组织不允许 `--no-invoker-iam-check`,请使用它
240 234
241 通过 `--ingress` 的入口限制是与调用者检查分离的独立层;保持设置以将服务限制在您的公司网络。235默认情况下,Cloud Run `*.run.app` URL 解析为公共地址,`/login` [私有网络检查](/docs/zh-CN/claude-apps-gateway#prerequisites)拒绝该地址。两种拓扑为开发者提供私有可解析的主机名,Cloud Run 都不为您配置:
242 236
243 默认情况下,Cloud Run `*.run.app` URL 解析为公共地址,`/login` [私有网络检查](/docs/zh-CN/claude-apps-gateway#prerequisites)拒绝该地址。两种拓扑为开发者提供私有可解析的主机名,Cloud Run 都不为您配置:237* **内部应用负载均衡器**,此页面的 `gateway.yaml` 假设的拓扑:在服务前面配置一个内部应用负载均衡器,具有内部 DNS 名称和证书,并将 `listen.public_url` 设置为该主机名。`internal` 入口设置已允许来自内部应用负载均衡器的流量;`internal-and-cloud-load-balancing` 还允许外部应用负载均衡器,其公共地址 `/login` 私有网络检查拒绝,因此此页面上的任何拓扑都不需要它。
238* **仅内部入口,无负载均衡器**:保持部署命令不变,将 `listen.public_url` 保留为 `*.run.app` URL,下面[参考资产](#terraform-reference)中的默认值。为了让 `*.run.app` 私有解析,您的网络团队必须已经运行 Google API 的私有服务连接端点、将 `*.run.app` 解析到它的 Cloud DNS 私有区域,以及到该端点的本地路由。
244 239
245 * **内部应用负载均衡器**,此页面的 `gateway.yaml` 假设的拓扑:在服务前面配置一个内部应用负载均衡器,具有内部 DNS 名称和证书,并将 `listen.public_url` 设置为该主机名。`internal` 入口设置已允许来自内部应用负载均衡器的流量;`internal-and-cloud-load-balancing` 还允许外部应用负载均衡器,其公共地址 `/login` 私有网络检查拒绝,因此此页面上的任何拓扑都不需要它。240Google 的 [Cloud Run 私有网络指南](https://cloud.google.com/run/docs/securing/private-networking)涵盖了两个选项都需要的基础设施。一旦网关在私有主机名上提供服务,验证登录;在此之前,从 Cloud Run 中的日志确认容器已启动。
246 * **仅内部入口,无负载均衡器**:保持部署命令不变,将 `listen.public_url` 保留为 `*.run.app` URL,下面[参考资产](#terraform-reference)中的默认值。为了让 `*.run.app` 私有解析,您的网络团队必须已经运行 Google API 的私有服务连接端点、将 `*.run.app` 解析到它的 Cloud DNS 私有区域,以及到该端点的本地路由。
247 241
248 Google 的 [Cloud Run 私有网络指南](https://cloud.google.com/run/docs/securing/private-networking)涵盖了两个选项都需要的基础设施。一旦网关在私有主机名上提供服务,验证登录;在此之前,从 Cloud Run 中的日志确认容器已启动。242在首次登录前更新 OAuth 客户端的授权重定向 URI 为 `<public_url>/oauth/callback`。更改 `public_url` 后重新部署,因为网关仅从该设置构建其公共源,忽略 `X-Forwarded-Host` 和 `X-Forwarded-Proto`。仅当设置 `listen.trusted_proxies` 时,才会为客户端 IP 遵守 `X-Forwarded-For`。
249 243
250 在首次登录前更新 OAuth 客户端的授权重定向 URI 为 `<public_url>/oauth/callback`。更改 `public_url` 后重新部署,因为网关仅从该设置构建其公共源,忽略 `X-Forwarded-Host` 和 `X-Forwarded-Proto`。仅当设置 `listen.trusted_proxies` 时,才会为客户端 IP 遵守 `X-Forwarded-For`。244**GKE**
251 </Tab>
252 245
253 <Tab title="GKE">246集群必须在 Cloud SQL 步骤中创建的 `$VPC` 上,以便 Pod 可以到达数据库的私有 IP;仅 VPC 对等不起作用,因为 Cloud SQL 私有 IP 本身是对等网络,对等是非传递的。要在该 VPC 上创建新集群,请将 `--network="$VPC" --subnetwork=cc-gateway-subnet` 传递给 `gcloud container clusters create`。
254 集群必须在 Cloud SQL 步骤中创建的 `$VPC` 上,以便 Pod 可以到达数据库的私有 IP;仅 VPC 对等不起作用,因为 Cloud SQL 私有 IP 本身是对等网络,对等是非传递的。要在该 VPC 上创建新集群,请将 `--network="$VPC" --subnetwork=cc-gateway-subnet` 传递给 `gcloud container clusters create`。
255 247
256 在集群及其节点池上启用 Workload Identity,然后将 Google 服务账户绑定到 Kubernetes 服务账户,以便 Pod 继承其凭证:248在集群及其节点池上启用 Workload Identity,然后将 Google 服务账户绑定到 Kubernetes 服务账户,以便 Pod 继承其凭证:
257 249
258 ```bash theme={null}250```bash theme={null}
259 gcloud container clusters update <cluster> --region="$REGION" \251gcloud container clusters update <cluster> --region="$REGION" \
260 --workload-pool="${PROJECT_ID}.svc.id.goog"252 --workload-pool="${PROJECT_ID}.svc.id.goog"
261 # 在标准集群上,现有节点池也需要 GKE_METADATA;253# 在标准集群上,现有节点池也需要 GKE_METADATA;
262 # Autopilot 默认启用此功能。254# Autopilot 默认启用此功能。
263 gcloud container node-pools update <pool> --cluster=<cluster> \255gcloud container node-pools update <pool> --cluster=<cluster> \
264 --region="$REGION" --workload-metadata=GKE_METADATA256 --region="$REGION" --workload-metadata=GKE_METADATA
265 257
266 kubectl create namespace claude-gateway258kubectl create namespace claude-gateway
267 kubectl create serviceaccount gateway -n claude-gateway259kubectl create serviceaccount gateway -n claude-gateway
268 260
269 gcloud iam service-accounts add-iam-policy-binding \261gcloud iam service-accounts add-iam-policy-binding \
270 "claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com" \262 "claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com" \
271 --role roles/iam.workloadIdentityUser \263 --role roles/iam.workloadIdentityUser \
272 --member "serviceAccount:${PROJECT_ID}.svc.id.goog[claude-gateway/gateway]"264 --member "serviceAccount:${PROJECT_ID}.svc.id.goog[claude-gateway/gateway]"
273 265
274 kubectl annotate serviceaccount gateway -n claude-gateway \266kubectl annotate serviceaccount gateway -n claude-gateway \
275 iam.gke.io/gcp-service-account="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com"267 iam.gke.io/gcp-service-account="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com"
276 ```268```
269
270将网关部署为标准 Deployment 加上 Service 和内部 Ingress,类 `gce-internal`,如 [Kubernetes 部署](/docs/zh-CN/claude-apps-gateway-deploy#kubernetes)中所述,具有:
277 271
278 将网关部署为标准 Deployment 加上 Service 和内部 Ingress,类 `gce-internal`,如 [Kubernetes 部署](/docs/zh-CN/claude-apps-gateway-deploy#kubernetes)中所述,具有:272* `serviceAccountName: gateway`
273* Secret Manager CSI 驱动程序在 `/secrets` 处挂载密钥
274* 就绪探针指向 `GET /readyz`
279 275
280 * `serviceAccountName: gateway`276将具有提高的 `timeoutSec` 的 BackendConfig 附加到网关 Service:GKE Ingress 后面的负载均衡器后端服务默认为 30 秒超时,这会切断长流式响应。
281 * Secret Manager CSI 驱动程序在 `/secrets` 处挂载密钥
282 * 就绪探针指向 `GET /readyz`
283 277
284 将具有提高的 `timeoutSec` 的 BackendConfig 附加到网关 Service:GKE Ingress 后面的负载均衡器后端服务默认为 30 秒超时,这会切断长流式响应。278不要在 Workload Identity 集群上应用阻止 `169.254.169.254` 的出口 NetworkPolicy;Pod 必须到达元数据服务器以获取凭证。网关的内置 [SSRF 防护](/docs/zh-CN/claude-apps-gateway-deploy#threat-model-summary)是那里的防御。
285 279
286 不要在 Workload Identity 集群上应用阻止 `169.254.169.254` 的出口 NetworkPolicy;Pod 必须到达元数据服务器以获取凭证。网关的内置 [SSRF 防护](/docs/zh-CN/claude-apps-gateway-deploy#threat-model-summary)是那里的防御。280网关记录一个启动警告,指出元数据端点可达,并建议应用出口 NetworkPolicy。在 Workload Identity 下,该警告是预期的,因为 Pod 需要端点。
287 281
288 网关记录一个启动警告,指出元数据端点可达,并建议应用出口 NetworkPolicy。在 Workload Identity 下,该警告是预期的,因为 Pod 需要端点。282**8. 将网关 URL 推送到开发者机器**
289 </Tab>
290 </Tabs>
291 </Step>
292 283
293 <Step title="将网关 URL 推送到开发者机器">284网关现在正在运行,但开发者在网关 URL 在其机器上之前无法从 `/login` 到达它。通过 MDM 将完整的[托管设置片段](/docs/zh-CN/claude-apps-gateway#set-the-gateway-url)部署到每个设备,具有 `forceLoginMethod`、`forceLoginGatewayUrl` 和 `parentSettingsBehavior: "merge"` 选择加入。登录选择器中没有网关选项供开发者手动选择。
294 网关现在正在运行,但开发者在网关 URL 在其机器上之前无法从 `/login` 到达它。通过 MDM 将完整的[托管设置片段](/docs/zh-CN/claude-apps-gateway#set-the-gateway-url)部署到每个设备,具有 `forceLoginMethod`、`forceLoginGatewayUrl` 和 `parentSettingsBehavior: "merge"` 选择加入。登录选择器中没有网关选项供开发者手动选择。
295 </Step>
296</Steps>
297 285
298<h2 id="terraform-reference">286## Terraform 参考
299 Terraform 参考
300</h2>
301 287
302[参考部署资产](https://github.com/anthropics/claude-code/tree/main/examples/gateway/gcp)自动化本页的 Cloud Run 路径;配置和镜像资产适用于两条路径:288[参考部署资产](https://github.com/anthropics/claude-code/tree/main/examples/gateway/gcp)自动化本页的 Cloud Run 路径;配置和镜像资产适用于两条路径:
303 289
309 295
310资产作为工作示例提供,而不是受支持的生产工件;查看并根据您的环境调整它们。296资产作为工作示例提供,而不是受支持的生产工件;查看并根据您的环境调整它们。
311 297
312<h2 id="troubleshooting">298## 故障排除
313 故障排除
314</h2>
315 299
316有关网关启动和登录错误,请参阅平台无关的[故障排除表](/docs/zh-CN/claude-apps-gateway-deploy#troubleshooting)。下面的条目特定于 Google Cloud。300有关网关启动和登录错误,请参阅平台无关的[故障排除表](/docs/zh-CN/claude-apps-gateway-deploy#troubleshooting)。下面的条目特定于 Google Cloud。
317 301
324| Google Cloud 的 Agent Platform 请求返回 `403 PERMISSION_DENIED` | 运行时未使用 `claude-gateway` 服务账户,或模型未在 Model Garden 中为项目启用 | 在 Cloud Run 上设置 `--service-account` 或在 GKE 上绑定 Workload Identity,并在 Model Garden 中为目标区域启用每个 Claude 模型 |308| Google Cloud 的 Agent Platform 请求返回 `403 PERMISSION_DENIED` | 运行时未使用 `claude-gateway` 服务账户,或模型未在 Model Garden 中为项目启用 | 在 Cloud Run 上设置 `--service-account` 或在 GKE 上绑定 Workload Identity,并在 Model Garden 中为目标区域启用每个 Claude 模型 |
325| 流式响应在固定持续时间后切断 | 前端请求超时:GKE Ingress 后面的负载均衡器后端服务默认为 30 秒,Cloud Run 默认为 300 秒 | 在 GKE 上附加具有提高的 `timeoutSec` 的 BackendConfig,或在 Cloud Run 上使用 `--timeout=3600` 部署 |309| 流式响应在固定持续时间后切断 | 前端请求超时:GKE Ingress 后面的负载均衡器后端服务默认为 30 秒,Cloud Run 默认为 300 秒 | 在 GKE 上附加具有提高的 `timeoutSec` 的 BackendConfig,或在 Cloud Run 上使用 `--timeout=3600` 部署 |
326 310
327<h2 id="next-steps">311## 后续步骤
328 后续步骤
329</h2>
330 312
331* [配置参考](/docs/zh-CN/claude-apps-gateway-config):每个 `gateway.yaml` 选项,包括 `managed.policies` 和 `telemetry`313* [配置参考](/docs/zh-CN/claude-apps-gateway-config):每个 `gateway.yaml` 选项,包括 `managed.policies` 和 `telemetry`
332* [部署和操作](/docs/zh-CN/claude-apps-gateway-deploy):IdP 设置、健康检查、JWT 密钥轮换、升级和安全模型314* [部署和操作](/docs/zh-CN/claude-apps-gateway-deploy):IdP 设置、健康检查、JWT 密钥轮换、升级和安全模型