6 6
7> Google Cloud에서 Claude 앱 게이트웨이를 실행하는 실제 예제: Cloud Run 또는 GKE, Cloud SQL for PostgreSQL, Secret Manager, 그리고 Google Cloud의 Agent Platform에 대한 서비스 계정 인증.7> Google Cloud에서 Claude 앱 게이트웨이를 실행하는 실제 예제: Cloud Run 또는 GKE, Cloud SQL for PostgreSQL, Secret Manager, 그리고 Google Cloud의 Agent Platform에 대한 서비스 계정 인증.
8 8
9<Note>9> [!NOTE]
10 이 페이지는 Google Cloud에서 Claude 앱 게이트웨이를 실행하는 한 가지 방법을 안내합니다. 이 구성은 지원되는 프로덕션 배포가 아닌 고객 관리 인프라에 대한 작동 예제입니다. 이를 사용하여 자신의 환경에 맞게 조정하기 전에 각 부분이 어떻게 함께 작동하는지 확인하세요. 플랫폼 독립적인 요구 사항은 [배포 가이드](/docs/ko/claude-apps-gateway-deploy)를 참조하세요.10>
11</Note>11> 이 페이지는 Google Cloud에서 Claude 앱 게이트웨이를 실행하는 한 가지 방법을 안내합니다. 이 구성은 지원되는 프로덕션 배포가 아닌 고객 관리 인프라에 대한 작동 예제입니다. 이를 사용하여 자신의 환경에 맞게 조정하기 전에 각 부분이 어떻게 함께 작동하는지 확인하세요. 플랫폼 독립적인 요구 사항은 [배포 가이드](/docs/ko/claude-apps-gateway-deploy)를 참조하세요.
12 12
13이 예제는 Google Cloud의 Agent Platform을 모델 업스트림으로 사용하여 Google Cloud에 Claude 앱 게이트웨이를 프로비저닝합니다. 컴퓨팅을 위해 Cloud Run 또는 GKE를 사용합니다. Google Workspace는 예제 ID 공급자(IdP)이지만, OpenID Connect(OIDC) 호환 IdP는 모두 작동합니다. `oidc` 블록만 변경됩니다. IdP별 세부 사항은 [ID 공급자 설정](/docs/ko/claude-apps-gateway-deploy#identity-provider-setup)을 참조하세요.13이 예제는 Google Cloud의 Agent Platform을 모델 업스트림으로 사용하여 Google Cloud에 Claude 앱 게이트웨이를 프로비저닝합니다. 컴퓨팅을 위해 Cloud Run 또는 GKE를 사용합니다. Google Workspace는 예제 ID 공급자(IdP)이지만, OpenID Connect(OIDC) 호환 IdP는 모두 작동합니다. `oidc` 블록만 변경됩니다. IdP별 세부 사항은 [ID 공급자 설정](/docs/ko/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 앱 게이트웨이 다이어그램: Claude Code 클라이언트가 HTTPS를 통해 게이트웨이(Cloud Run 또는 GKE)에 연결되며, 게이트웨이는 VPC 내에서 세션 상태를 위한 프라이빗 IP Cloud SQL 데이터베이스와 함께 실행됩니다. 게이트웨이는 Google Workspace에 대해 OIDC를 통해 사용자를 로그인하고, 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에 직접 연결되거나 GKE에서 Workload Identity를 통해 바인딩됨25* `roles/aiplatform.user`가 있는 **서비스 계정**으로, Cloud Run에 직접 연결되거나 GKE에서 Workload Identity를 통해 바인딩됨
30* 사용자가 제공하는 **HTTPS 프론트 엔드**: Cloud Run 앞의 내부 Application Load Balancer(이 연습에서는 게이트웨이를 이에 맞게 구성하지만 Application Load Balancer는 생성하지 않음) 또는 GKE의 `gce-internal` 클래스의 내부 **GKE Ingress**26* 사용자가 제공하는 **HTTPS 프론트 엔드**: Cloud Run 앞의 내부 Application Load Balancer(이 연습에서는 게이트웨이를 이에 맞게 구성하지만 Application Load Balancer는 생성하지 않음) 또는 GKE의 `gce-internal` 클래스의 내부 **GKE Ingress**
31 27
32<h2 id="prerequisites">28## 전제 조건
33 전제 조건
34</h2>
35 29
36* 청구가 활성화된 GCP 프로젝트 및 위의 리소스를 생성할 권한30* 청구가 활성화된 GCP 프로젝트 및 위의 리소스를 생성할 권한
37* `gcloud auth login`으로 인증된 `gcloud` CLI 및 로컬에 설치된 Docker31* `gcloud auth login`으로 인증된 `gcloud` CLI 및 로컬에 설치된 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 Run만 해당70* `run`: Cloud Run만 해당
79 * `container`: GKE만 해당71* `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/ko/claude-apps-gateway-deploy#container-image)에 따라 `linux-x64` glibc 바이너리를 사용하여 이미지를 빌드하고 푸시합니다:
98 88
99 ```bash theme={null}89[컨테이너 이미지 요구사항](/docs/ko/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 requires linux/amd64. --provenance=false avoids a buildx OCI96# Cloud Run requires linux/amd64. --provenance=false avoids a buildx OCI
105 # image index that Cloud Run rejects.97# image index that Cloud Run rejects.
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 Private Services Access를 통해 VPC에 인스턴스를 생성하여 공개 IP가 없도록 합니다. 이는 `constraints/sql.restrictPublicIp`가 적용되는 프로젝트도 만족합니다:
114 104
115 ```bash theme={null}105Private Services Access를 통해 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 # Private Services Access: one-time per VPC113# Private Services Access: one-time per 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을 가리키므로 게이트웨이는 런타임 서비스 계정의 Application Default Credentials를 통해 인증합니다. 모든 필드는 [구성 참조](/docs/ko/claude-apps-gateway-config)를 참조합니다.
145 135
146 두 개의 `listen` 필드는 게이트웨이 앞에 있는 것을 설명합니다:136`upstreams` 블록은 `auth: {}`를 사용하여 Google Cloud의 Agent Platform을 가리키므로 게이트웨이는 런타임 서비스 계정의 Application Default Credentials를 통해 인증합니다. 모든 필드는 [구성 참조](/docs/ko/claude-apps-gateway-config)를 참조합니다.
147 137
148 * `public_url`: 외부 `https://` 원본으로, 루프백이 아닌 바인드에 필요합니다. [`listen` 참조](/docs/ko/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/ko/claude-apps-gateway#prerequisites)에서 거부합니다.140* `public_url`: 외부 `https://` 원본으로, 루프백이 아닌 바인드에 필요합니다. [`listen` 참조](/docs/ko/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/ko/claude-apps-gateway#prerequisites)에서 거부합니다.
154 | - | - |
155 | 로드 밸런서 없이 직접 도달하는 Cloud Run | `[169.254.0.0/16]` |
156 | Cloud Run 앞의 내부 Application Load Balancer | `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 앞의 내부 Application Load Balancer | `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 # keep passing the readiness probe174 # readiness_grace_seconds: 300 # keep passing the readiness probe
183 # through a Cloud SQL failover175# through a Cloud SQL failover
184 176
185 upstreams:177upstreams:
186 - provider: vertex178 - provider: vertex
187 region: <your-region> # must match $REGION179 region: <your-region> # must match $REGION
188 project_id: <your-project>180 project_id: <your-project>
189 auth: {} # ADC via the runtime service account181 auth: {} # ADC via the runtime service account
190 ```182```
183
184> [!NOTE]
185>
186> Google id\_tokens는 `groups` 클레임을 포함하지 않습니다. Google Workspace를 IdP로 사용하여 [`managed.policies`](/docs/ko/claude-apps-gateway-config#managed)에서 그룹 기반 정책을 사용하려면 [`oidc.google_groups`](/docs/ko/claude-apps-gateway-config#oidc)를 구성합니다. 이는 도메인 전체 위임이 있는 서비스 계정을 사용하여 Admin SDK Directory API를 통해 각 사용자의 그룹을 조회합니다. 이 없이는 `email_domain`으로 일치시킵니다.
191 187
192 <Note>188**6. Secret Manager에 비밀 저장**
193 Google id\_tokens는 `groups` 클레임을 포함하지 않습니다. Google Workspace를 IdP로 사용하여 [`managed.policies`](/docs/ko/claude-apps-gateway-config#managed)에서 그룹 기반 정책을 사용하려면 [`oidc.google_groups`](/docs/ko/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 Console → OAuth 클라이언트 |195| `gateway-oidc-client-secret` | Google Cloud Console → 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`--network`, `--subnet`, `--vpc-egress=private-ranges-only`를 통한 직접 VPC 이그레스를 사용하면 서비스가 Cloud SQL 프라이빗 IP에 직접 도달할 수 있습니다. 각 인스턴스는 최대 [`store.max_connections`](/docs/ko/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 `--network`, `--subnet`, `--vpc-egress=private-ranges-only`를 통한 직접 VPC 이그레스를 사용하면 서비스가 Cloud SQL 프라이빗 IP에 직접 도달할 수 있습니다. 각 인스턴스는 최대 [`store.max_connections`](/docs/ko/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` 바인딩 없이 확인을 비활성화하며, Domain Restricted Sharing에서 작동합니다
231* `--allow-unauthenticated`: `allUsers`에 `run.invoker` 역할을 부여합니다. 조직에서 `--no-invoker-iam-check`를 허용하지 않으면 사용합니다
237 232
238 * `--no-invoker-iam-check`: 관리할 `allUsers` 바인딩 없이 확인을 비활성화하며, Domain Restricted Sharing에서 작동합니다233`--ingress`를 통한 Ingress 제한은 호출자 확인과 별개의 독립적인 계층입니다. 서비스를 회사 네트워크로 제한하도록 설정된 상태로 유지합니다.
239 * `--allow-unauthenticated`: `allUsers`에 `run.invoker` 역할을 부여합니다. 조직에서 `--no-invoker-iam-check`를 허용하지 않으면 사용합니다
240 234
241 `--ingress`를 통한 Ingress 제한은 호출자 확인과 별개의 독립적인 계층입니다. 서비스를 회사 네트워크로 제한하도록 설정된 상태로 유지합니다.235기본적으로 Cloud Run `*.run.app` URL은 공개 주소로 확인되며, 이는 `/login` [프라이빗 네트워크 확인](/docs/ko/claude-apps-gateway#prerequisites)에서 거부합니다. 두 가지 토폴로지는 개발자에게 프라이빗하게 확인 가능한 호스트명을 제공하며, Cloud Run은 둘 다 프로비저닝하지 않습니다:
242 236
243 기본적으로 Cloud Run `*.run.app` URL은 공개 주소로 확인되며, 이는 `/login` [프라이빗 네트워크 확인](/docs/ko/claude-apps-gateway#prerequisites)에서 거부합니다. 두 가지 토폴로지는 개발자에게 프라이빗하게 확인 가능한 호스트명을 제공하며, Cloud Run은 둘 다 프로비저닝하지 않습니다:237* **내부 Application Load Balancer**, 이 페이지의 `gateway.yaml`이 가정하는 토폴로지: 서비스 앞에 내부 Application Load Balancer를 프로비저닝하고 내부 DNS 이름 및 인증서를 사용하며, `listen.public_url`을 해당 호스트명으로 설정합니다. `internal` ingress 설정은 이미 내부 Application Load Balancer의 트래픽을 허용합니다. `internal-and-cloud-load-balancing`은 추가로 외부 Application Load Balancer를 허용하며, 이들의 공개 주소는 `/login` 프라이빗 네트워크 확인에서 거부하므로 이 페이지의 토폴로지는 이를 필요로 하지 않습니다.
238* **로드 밸런서 없는 내부 전용 ingress**: 배포 명령어를 그대로 유지하고 `listen.public_url`을 `*.run.app` URL로 유지합니다. 아래 [참조 자산](#terraform-reference)의 기본값입니다. `*.run.app`이 프라이빗하게 확인되려면 네트워크 팀이 이미 Google API에 대한 Private Service Connect 엔드포인트를 운영하고, `*.run.app`을 이에 확인하는 Cloud DNS 프라이빗 영역을 운영하며, 해당 엔드포인트로의 온프레미스 라우팅을 운영해야 합니다.
244 239
245 * **내부 Application Load Balancer**, 이 페이지의 `gateway.yaml`이 가정하는 토폴로지: 서비스 앞에 내부 Application Load Balancer를 프로비저닝하고 내부 DNS 이름 및 인증서를 사용하며, `listen.public_url`을 해당 호스트명으로 설정합니다. `internal` ingress 설정은 이미 내부 Application Load Balancer의 트래픽을 허용합니다. `internal-and-cloud-load-balancing`은 추가로 외부 Application Load Balancer를 허용하며, 이들의 공개 주소는 `/login` 프라이빗 네트워크 확인에서 거부하므로 이 페이지의 토폴로지는 이를 필요로 하지 않습니다.240Google의 [Cloud Run용 프라이빗 네트워킹 가이드](https://cloud.google.com/run/docs/securing/private-networking)는 두 옵션이 필요로 하는 인프라를 다룹니다. 게이트웨이가 프라이빗 호스트명에서 제공되면 로그인을 확인합니다. 그때까지 Cloud Run의 로그에서 컨테이너가 부팅되었는지 확인합니다.
246 * **로드 밸런서 없는 내부 전용 ingress**: 배포 명령어를 그대로 유지하고 `listen.public_url`을 `*.run.app` URL로 유지합니다. 아래 [참조 자산](#terraform-reference)의 기본값입니다. `*.run.app`이 프라이빗하게 확인되려면 네트워크 팀이 이미 Google API에 대한 Private Service Connect 엔드포인트를 운영하고, `*.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`를 무시합니다. `X-Forwarded-For`는 `listen.trusted_proxies`가 설정되었을 때만 클라이언트 IP에 대해 인정됩니다.
249 243
250 첫 번째 로그인 전에 OAuth 클라이언트의 승인된 리디렉션 URI를 `<public_url>/oauth/callback`으로 업데이트합니다. `public_url`을 변경한 후 다시 배포합니다. 게이트웨이는 해당 설정에서만 공개 원본을 빌드하고 `X-Forwarded-Host` 및 `X-Forwarded-Proto`를 무시합니다. `X-Forwarded-For`는 `listen.trusted_proxies`가 설정되었을 때만 클라이언트 IP에 대해 인정됩니다.244**GKE**
251 </Tab>
252 245
253 <Tab title="GKE">246클러스터는 Cloud SQL 단계에서 생성한 `$VPC`에 있어야 하므로 포드가 데이터베이스의 프라이빗 IP에 도달할 수 있습니다. VPC 피어링만으로는 작동하지 않습니다. Cloud SQL 프라이빗 IP 자체가 피어링된 네트워크이고 피어링은 비전이적이기 때문입니다. 해당 VPC에 새 클러스터를 생성하려면 `gcloud container clusters create`에 `--network="$VPC" --subnetwork=cc-gateway-subnet`을 전달합니다.
254 클러스터는 Cloud SQL 단계에서 생성한 `$VPC`에 있어야 하므로 포드가 데이터베이스의 프라이빗 IP에 도달할 수 있습니다. VPC 피어링만으로는 작동하지 않습니다. Cloud SQL 프라이빗 IP 자체가 피어링된 네트워크이고 피어링은 비전이적이기 때문입니다. 해당 VPC에 새 클러스터를 생성하려면 `gcloud container clusters create`에 `--network="$VPC" --subnetwork=cc-gateway-subnet`을 전달합니다.
255 247
256 클러스터 및 노드 풀에서 Workload Identity를 활성화한 다음 Google 서비스 계정을 Kubernetes 서비스 계정에 바인드하여 포드가 해당 자격증명을 상속하도록 합니다:248클러스터 및 노드 풀에서 Workload Identity를 활성화한 다음 Google 서비스 계정을 Kubernetes 서비스 계정에 바인드하여 포드가 해당 자격증명을 상속하도록 합니다:
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 # On a Standard cluster, existing node pools also need GKE_METADATA;253# On a Standard cluster, existing node pools also need GKE_METADATA;
262 # Autopilot enables this by default.254# Autopilot enables this by default.
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[Kubernetes 배포](/docs/ko/claude-apps-gateway-deploy#kubernetes)에 설명된 대로 표준 Deployment, Service 및 내부 Ingress(클래스 `gce-internal`)로 게이트웨이를 배포합니다:
277 271
278 [Kubernetes 배포](/docs/ko/claude-apps-gateway-deploy#kubernetes)에 설명된 대로 표준 Deployment, Service 및 내부 Ingress(클래스 `gce-internal`)로 게이트웨이를 배포합니다:272* `serviceAccountName: gateway`
273* `/secrets`에 비밀을 마운트하는 Secret Manager CSI 드라이버
274* `GET /readyz`를 가리키는 준비 상태 프로브
279 275
280 * `serviceAccountName: gateway`276게이트웨이 Service에 높은 `timeoutSec`을 가진 BackendConfig를 연결합니다. GKE Ingress 뒤의 로드 밸런서 백엔드 서비스는 기본적으로 30초 타임아웃으로 설정되어 있으며, 이는 긴 스트리밍 응답을 차단합니다.
281 * `/secrets`에 비밀을 마운트하는 Secret Manager CSI 드라이버
282 * `GET /readyz`를 가리키는 준비 상태 프로브
283 277
284 게이트웨이 Service에 높은 `timeoutSec`을 가진 BackendConfig를 연결합니다. GKE Ingress 뒤의 로드 밸런서 백엔드 서비스는 기본적으로 30초 타임아웃으로 설정되어 있으며, 이는 긴 스트리밍 응답을 차단합니다.278Workload Identity 클러스터에서 `169.254.169.254`를 차단하는 이그레스 NetworkPolicy를 적용하지 마십시오. 포드는 자격증명을 위해 메타데이터 서버에 도달해야 합니다. 게이트웨이의 내장 [SSRF 가드](/docs/ko/claude-apps-gateway-deploy#threat-model-summary)가 방어입니다.
285 279
286 Workload Identity 클러스터에서 `169.254.169.254`를 차단하는 이그레스 NetworkPolicy를 적용하지 마십시오. 포드는 자격증명을 위해 메타데이터 서버에 도달해야 합니다. 게이트웨이의 내장 [SSRF 가드](/docs/ko/claude-apps-gateway-deploy#threat-model-summary)가 방어입니다.280게이트웨이는 메타데이터 엔드포인트에 도달 가능하고 이그레스 NetworkPolicy를 적용할 것을 제안하는 부팅 경고를 기록합니다. Workload Identity에서 해당 경고는 예상되며, 포드가 엔드포인트를 필요로 하기 때문입니다.
287 281
288 게이트웨이는 메타데이터 엔드포인트에 도달 가능하고 이그레스 NetworkPolicy를 적용할 것을 제안하는 부팅 경고를 기록합니다. Workload Identity에서 해당 경고는 예상되며, 포드가 엔드포인트를 필요로 하기 때문입니다.282**8. 게이트웨이 URL을 개발자 머신에 푸시**
289 </Tab>
290 </Tabs>
291 </Step>
292 283
293 <Step title="게이트웨이 URL을 개발자 머신에 푸시">284게이트웨이가 이제 실행 중이지만 게이트웨이 URL이 머신에 있을 때까지 개발자는 `/login`에서 도달할 수 없습니다. 전체 [관리 설정 스니펫](/docs/ko/claude-apps-gateway#set-the-gateway-url)을 `forceLoginMethod`, `forceLoginGatewayUrl`, `parentSettingsBehavior: "merge"` 옵트인과 함께 MDM을 통해 각 디바이스에 배포합니다. 개발자가 수동으로 선택할 수 있는 로그인 선택기의 게이트웨이 옵션은 없습니다.
294 게이트웨이가 이제 실행 중이지만 게이트웨이 URL이 머신에 있을 때까지 개발자는 `/login`에서 도달할 수 없습니다. 전체 [관리 설정 스니펫](/docs/ko/claude-apps-gateway#set-the-gateway-url)을 `forceLoginMethod`, `forceLoginGatewayUrl`, `parentSettingsBehavior: "merge"` 옵트인과 함께 MDM을 통해 각 디바이스에 배포합니다. 개발자가 수동으로 선택할 수 있는 로그인 선택기의 게이트웨이 옵션은 없습니다.
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/ko/claude-apps-gateway-deploy#troubleshooting)를 참조하세요. 아래 항목은 Google Cloud에 특정합니다.300게이트웨이 부팅 및 로그인 오류는 플랫폼 독립적인 [문제 해결 표](/docs/ko/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를 바인딩하고 각 Claude 모델을 대상 지역의 Model Garden에서 활성화 |308| Google Cloud의 Agent Platform 요청이 `403 PERMISSION_DENIED`를 반환 | 런타임이 `claude-gateway` 서비스 계정을 사용하지 않거나 모델이 프로젝트의 Model Garden에서 활성화되지 않음 | Cloud Run에서 `--service-account`를 설정하거나 GKE에서 Workload Identity를 바인딩하고 각 Claude 모델을 대상 지역의 Model Garden에서 활성화 |
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/ko/claude-apps-gateway-config): 모든 `gateway.yaml` 옵션(예: `managed.policies` 및 `telemetry`)313* [구성 참조](/docs/ko/claude-apps-gateway-config): 모든 `gateway.yaml` 옵션(예: `managed.policies` 및 `telemetry`)
332* [배포 및 운영](/docs/ko/claude-apps-gateway-deploy): IdP 설정, 상태 확인, JWT 비밀 로테이션, 업그레이드, 보안 모델314* [배포 및 운영](/docs/ko/claude-apps-gateway-deploy): IdP 설정, 상태 확인, JWT 비밀 로테이션, 업그레이드, 보안 모델