Claude apps gatewayをGCPにデプロイする — Cloud RunとCloud SQLの構成例
Claude apps gatewayをCloud Run/GKE・Cloud SQL・Secret Manager・サービスアカウント認証でGoogle Cloudに構築する実践例をまとめます。
この構成で何が組み上がるか
基本のセットアップを終えたあと、実際にGoogle Cloud(GCP)でどこまでのリソースを組むかは、AWSやオンプレのKubernetesとは細部が違います。本記事はGoogle Cloud's Agent Platform(旧称Vertex AI)をモデルの上流としたGCP上の構成例を、実際のgcloudコマンドで組み立てます。
でき上がるのは次の構成です。
- Cloud RunサービスまたはGKE Deploymentでゲートウェイコンテナを稼働
- Artifact Registryリポジトリにゲートウェイイメージを保管
- Cloud SQL for PostgreSQLをプライベートIPのみで配置し、ゲートウェイのstoreとして使用
- Secret Managerに
gateway.yaml本体・JWT署名鍵・OIDCクライアントシークレット・Postgres接続文字列を保管 - サービスアカウントに
roles/aiplatform.userを付与し、Cloud Runなら直接、GKEならWorkload Identityで束縛 - 前段のHTTPS終端は自分で用意する。Cloud Run前段の内部Application Load Balancer、またはGKEの
gce-internalクラスの内部Ingress
IdPの例はGoogle Workspaceですが、oidcブロックだけ差し替えればOIDC準拠の他IdPでも同じ構成が通用します。
前提条件
| 項目 | 内容 |
|---|---|
| GCPプロジェクト | 内容課金有効化済みで、上記リソースを作成できる権限 |
| ツール | 内容gcloud CLI(gcloud auth login済み)とDockerがローカルにあること |
| GKEを使う場合 | 内容kubectlと、本記事で作るVPC上のGKEクラスター |
| モデルアクセス | 内容必要なClaudeモデルがModel Gardenでそのリージョンに公開されていること |
| IdP | 内容リダイレクトURIをhttps://<ゲートウェイのホスト>/oauth/callbackとしたGoogle WorkspaceのOAuth 2.0 Webアプリケーションクライアント |
| TLS | 内容ロードバランサー用の内部DNSホスト名 |
IdP側のOAuthクライアント登録やシークレットローテーションのようなクラウドに依存しない運用手順はデプロイと運用で扱っているので、本記事ではGCP固有の手順に絞ります。
PROJECT_IDと、Claude Codeが必要とするモデルがModel Gardenで公開されているリージョンを指すREGIONを先に決めます。
export PROJECT_ID=<your-project>
export REGION=us-east5
gcloud config set project "$PROJECT_ID"手順1: APIを有効化する
gcloud services enable \
aiplatform.googleapis.com \
artifactregistry.googleapis.com \
sqladmin.googleapis.com \
secretmanager.googleapis.com \
iamcredentials.googleapis.com \
iam.googleapis.com \
compute.googleapis.com \
servicenetworking.googleapis.com \
run.googleapis.com \
container.googleapis.comcomputeとservicenetworkingはCloud SQLをプライベートIPで置くために必要、runはCloud Run、containerはGKEのみに要る、という組み合わせなので、どちらか一方のトラックしか使わないなら不要なAPIは省略できます。
手順2: サービスアカウントを作りIAMを付与する
ゲートウェイはGoogle Cloud's Agent Platformを呼び出す専用のサービスアカウントで動きます。Cloud SQLへはVPC経由のパスワード認証で到達するため、Cloud SQL側にIAMロールは要りません。
gcloud iam service-accounts create claude-gateway --display-name="Claude apps gateway"
SA="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com"
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="serviceAccount:${SA}" --role="roles/aiplatform.user" --condition=None続けて、プロジェクトで使うClaudeモデルをModel Gardenで有効にします。モデルはリージョンごとに個別公開されるため、使うモデルカードで対象リージョンを確認してください。
手順3: Artifact Registryへイメージをビルド&プッシュする
gcloud artifacts repositories create claude-gateway \
--repository-format=docker --location="$REGION"
gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet
docker build --platform=linux/amd64 --provenance=false \
-t "${REGION}-docker.pkg.dev/${PROJECT_ID}/claude-gateway/gateway:<version>" .
docker push "${REGION}-docker.pkg.dev/${PROJECT_ID}/claude-gateway/gateway:<version>"Cloud Runはlinux/amd64必須です。--provenance=falseを付けないと、buildxが作るOCIイメージインデックスをCloud Runが拒否します。
手順4: Cloud SQL for PostgreSQLを用意する
Private Services Access経由でVPCに紐づけ、公開IPを持たせません。この構成はconstraints/sql.restrictPublicIp組織ポリシーが強制されているプロジェクトでも通ります。
VPC=cc-gateway-vpc
gcloud compute networks create "$VPC" --subnet-mode=custom
gcloud compute networks subnets create cc-gateway-subnet \
--network="$VPC" --region="$REGION" --range=10.0.0.0/24
gcloud compute addresses create "google-managed-services-${VPC}" \
--global --purpose=VPC_PEERING --prefix-length=16 --network="$VPC"
gcloud services vpc-peerings connect \
--service=servicenetworking.googleapis.com \
--ranges="google-managed-services-${VPC}" --network="$VPC"
gcloud sql instances create claude-gateway-db \
--database-version=POSTGRES_16 --tier=db-g1-small --region="$REGION" \
--network="projects/${PROJECT_ID}/global/networks/${VPC}" --no-assign-ip
gcloud sql databases create claude_gateway --instance=claude-gateway-db
PGPASS="$(openssl rand -hex 24)"
gcloud sql users create gateway --instance=claude-gateway-db --password="$PGPASS"Private Services Accessの2つのコマンド(managed servicesアドレスの確保とVPCピアリング)はVPC単位で1回だけ実行します。GKEを使う場合、クラスターは必ずこのステップで作った$VPC上に置く必要があります。VPCピアリングだけでは届きません。Cloud SQLのプライベートIP自体がピアリングされたネットワークであり、ピアリングは推移的でないためです。このVPC上に新規クラスターを作るなら、gcloud container clusters createに--network="$VPC" --subnetwork=cc-gateway-subnetを渡します。
手順5: gateway.yamlを書く
upstreamsはGoogle Cloud's Agent Platformをauth: {}で指すだけで、ランタイムのサービスアカウントからApplication Default Credentialsで認証します。trusted_proxiesは前段の構成によって値が変わり、Google由来のid_tokenにはgroupsクレームが無いため、グループベースのポリシーを使うならAdmin SDK Directory APIを呼ぶoidc.google_groupsの設定か、email_domainでのマッチに頼ります。
| 前段 | trusted_proxies |
|---|---|
| Cloud Runを直接叩く(ロードバランサーなし) | trusted_proxies169.254.0.0/16 |
| Cloud Run前段の内部Application Load Balancer | trusted_proxies169.254.0.0/16とプロキシオンリーサブネットのCIDR |
GKEの内部Ingress(gce-internalクラス) | trusted_proxiesプロキシオンリーサブネットのCIDR |
外部向けのgceクラスGKE Ingressはここには含めていません。公開のforwarding-ruleアドレスを払い出すため、/loginのプライベートネットワークチェックが拒否します。
listen:
host: 0.0.0.0
port: 8080
public_url: https://claude-gateway.internal.example.com
trusted_proxies: [169.254.0.0/16, <your-proxy-only-subnet-cidr>]
oidc:
issuer: https://accounts.google.com
client_id: <your-oauth-client-id>
client_secret: ${OIDC_CLIENT_SECRET}
allowed_email_domains: [example.com]
scopes: [openid, profile, email]
extra_auth_params: { access_type: offline, prompt: consent }
session:
jwt_secret: ${GATEWAY_JWT_SECRET}
store:
postgres_url: ${GATEWAY_POSTGRES_URL}
upstreams:
- provider: vertex
region: <your-region>
project_id: <your-project>
auth: {}scopesにoffline_accessを含めていないのは、Googleがそのスコープを無視するためです。代わりにextra_auth_paramsでaccess_type: offlineとprompt: consentを指定すると、リフレッシュトークンが発行されます。必須5セクション以外の項目まで含めた全項目はgateway.yamlリファレンスにまとめてあり、upstreamsとtrusted_proxiesまわりだけがGCP固有の書き方になります。
手順6: Secret Managerにシークレットを置く
4つのシークレットを作り、claude-gatewayサービスアカウントにroles/secretmanager.secretAccessorを付与します。
| シークレット | 由来 |
|---|---|
gateway-jwt-secret | 由来openssl rand -base64 32 |
gateway-oidc-client-secret | 由来Google Cloud ConsoleのOAuthクライアント |
gateway-postgres-url | 由来手順4で組み立てた接続文字列 |
gateway-config | 由来手順5のgateway.yaml本体 |
シークレットの届き方はトラックによって違います。GKEではSecret Manager CSIドライバーがファイルとしてマウントし、gateway.yamlは${file:/secrets/...}で参照します。Cloud Runは1つのディレクトリに複数シークレットをマウントできないため、gateway.yamlだけファイルとしてマウントし、残り3つは環境変数として注入し、gateway.yaml側は${GATEWAY_JWT_SECRET}のような環境変数参照にします。
手順7: デプロイする
Cloud RunとGKEでは前段の構築責任が変わります。
| 観点 | Cloud Run | GKE |
|---|---|---|
| モデル認証 | Cloud Runランタイムに直接付けたサービスアカウント | GKEWorkload Identity |
| ロードバランサー | Cloud Run自前で内部ALBを用意(このページのgateway.yamlが前提) | GKEgce-internalクラスのIngressをAWS同様に自分で作成 |
| 起動時のOIDCディスカバリー遅延対策 | Cloud Runmin-instances=1 | GKE不要(常駐Pod) |
| コネクション数の上限 | Cloud Runインスタンス数×store.max_connections(既定5)がCloud SQLの上限を超えないようmax-instancesを調整 | GKEノード数に応じて別途調整 |
Cloud Runは次のコマンドで、内部ロードバランサー前提の本番向け設定にします。
gcloud run deploy claude-gateway \
--image="${REGION}-docker.pkg.dev/${PROJECT_ID}/claude-gateway/gateway:<version>" \
--region="$REGION" \
--service-account="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com" \
--min-instances=1 --max-instances=8 --timeout=3600 --ingress=internal \
--network="$VPC" --subnet=cc-gateway-subnet --vpc-egress=private-ranges-only \
--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 \
--no-invoker-iam-check--network・--subnet・--vpc-egress=private-ranges-onlyによるDirect VPC egressが、Cloud SQLのプライベートIPへ直接到達する経路を作ります。Google Cloud's Agent Platformのエンドポイントとaccounts.google.comへの公開向けの通信はVPCを介さず直接インターネットへ出るため、Cloud NATは不要です。
invoker IAMチェックは開放するか無効化する必要があります。ゲートウェイは自前でOIDCサインインを行い、クライアントはGCPトークンを持たないため、Cloud Run側の認証チェックが未認証リクエストを通す設定になっていないと、ゲートウェイ自身のサインインフローにすら到達できません。--no-invoker-iam-checkはallUsersバインディングを作らずにチェックを無効化でき、Domain Restricted Sharingの下でも機能します。それが使えない組織ポリシーなら--allow-unauthenticatedでallUsersにrun.invokerを付与します。--ingressによるアクセス制限はこれとは独立したレイヤーで、社内ネットワークに絞るために別途設定します。
既定の*.run.appドメインは公開アドレスに解決されるため、/loginのプライベートネットワークチェックに拒否されます。プライベートに解決できるホスト名を用意するトポロジーは2つあり、どちらもCloud Run自体は作ってくれません。1つはこのページのgateway.yamlが前提とする内部Application Load Balancerで、public_urlをそのホスト名にします。もう1つは*.run.appのまま、社内ネットワークにPrivate Service ConnectエンドポイントとCloud DNSプライベートゾーンを別途構築してプライベート解決させる方法です。
初回サインイン前に、OAuthクライアントの承認済みリダイレクトURIを<public_url>/oauth/callbackに更新してください。public_urlを変更したら再デプロイも必要です。ゲートウェイは公開オリジンをこの設定値だけから組み立て、X-Forwarded-HostやX-Forwarded-Protoは無視するためです。
GKEでは、クラスターとノードプールでWorkload Identityを有効化し、GoogleサービスアカウントとKubernetesサービスアカウントを紐づけます。
gcloud container clusters update <cluster> --region="$REGION" \
--workload-pool="${PROJECT_ID}.svc.id.goog"
# Standardクラスターでは既存のノードプールにもGKE_METADATAが必要です。
# Autopilotは既定で有効になっています。
gcloud container node-pools update <pool> --cluster=<cluster> \
--region="$REGION" --workload-metadata=GKE_METADATA
kubectl create namespace claude-gateway
kubectl create serviceaccount gateway -n claude-gateway
gcloud iam service-accounts add-iam-policy-binding \
"claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com" \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:${PROJECT_ID}.svc.id.goog[claude-gateway/gateway]"
kubectl annotate serviceaccount gateway -n claude-gateway \
iam.gke.io/gcp-service-account="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com"GKE Ingressの背後にあるロードバランサーバックエンドは既定30秒でタイムアウトし、長いストリーミング応答を切ってしまうため、timeoutSecを引き上げたBackendConfigをゲートウェイのServiceに付けます。Workload Identityクラスターではメタデータサーバー(169.254.169.254)への到達をegress NetworkPolicyで塞がないでください。Podの認証情報取得に必要で、ゲートウェイ内蔵のSSRFガードがその面の防御を別に担っています。ゲートウェイは起動時に「メタデータエンドポイントに到達可能」という警告ログを出しegress NetworkPolicyの適用を勧めてきますが、Workload Identityクラスターではその到達性自体が必要なので、この警告は想定内であり設定ミスではありません。
手順8: ゲートウェイURLを開発者マシンへ配信する
ゲートウェイが稼働しても、forceLoginMethod・forceLoginGatewayUrl・parentSettingsBehavior: "merge"がmanaged settingsの設定キー一覧からMDM経由で各デバイスに届くまで、/loginの選択肢にゲートウェイは出てきません。
Terraformでコード化する
Cloud Runトラックを自動化した参照アセット一式があります。setup.shはAPI有効化から初回デプロイまでを冪等なgcloudスクリプトで通し、terraform/は同じ構成を宣言的に、Artifact Registryリポジトリの部分apply→ビルド&プッシュ→全体applyという2段階で構築します。gateway.yaml.exampleとDockerfileはCloud Run・GKEどちらのトラックにも共通で使えます。既定のCloud Runのingressはこのページのデプロイコマンドと同じinternalで、invoker層は--no-invoker-iam-checkではなくallUsersへのrun.invoker付与を既定にしています。どちらも動きますが、組織のポリシー制約次第でどちらを選ぶかが決まります。
よくあるつまずき
| 症状 | 原因 | 対処 |
|---|---|---|
Cloud Runがコンテナに到達する前に403 Forbidden | 原因invoker IAMチェックが有効なまま | 対処--no-invoker-iam-checkでデプロイするか、--allow-unauthenticatedでallUsersにrun.invokerを付与する |
--no-invoker-iam-checkがinvoker_iam_disabled is not currently availableで拒否される | 原因constraints/run.managed.requireInvokerIam組織ポリシーでブロックされている | 対処--allow-unauthenticatedを使う。Domain Restricted Sharingでそれも塞がれているならGKEトラックを使う(allUsersバインディング無しでネットワーク層で完結する) |
デプロイ時にContainer manifest type … must support amd64/linuxと出る | 原因非amd64ホストでビルドしたか、buildxがOCIイメージインデックスを出した | 対処--platform=linux/amd64 --provenance=falseでビルドする |
| Cloud RunでPostgres接続タイムアウトエラーになる | 原因サービスがVPCに繋がっていないか、Cloud SQLがそのVPCでプライベートIPを持っていない | 対処--networkと--subnetでDirect VPC egressを設定し、Cloud SQLは--no-assign-ipとそのVPCを指す--networkで作成する |
Google Cloud's Agent Platformへのリクエストが403 PERMISSION_DENIED | 原因ランタイムがclaude-gatewayサービスアカウントを使っていないか、対象リージョンのModel Gardenでモデルが未有効 | 対処Cloud Runは--service-account、GKEはWorkload Identityを設定し、各Claudeモデルを対象リージョンのModel Gardenで有効にする |
| ストリーミング応答が一定時間で打ち切られる | 原因前段のリクエストタイムアウト(GKE Ingressのバックエンドは既定30秒、Cloud Runは既定300秒) | 対処GKEはBackendConfigのtimeoutSecを引き上げ、Cloud Runは--timeout=3600でデプロイする |
まとめ
GCP上のClaude apps gatewayは、Cloud RunかGKEかという計算基盤の選択と、invokerチェックをどう開放するかという認証層の選択で構成の大半が決まります。Cloud SQLのプライベート接続とSecret Managerの配線はどちらのトラックでも共通で、GCP特有の落とし穴はほぼ*.run.appの公開解決とinvoker IAMチェックの2点に集約されます。IAM設定の考え方は個々の開発者端末向けのVertex AI IAM設定(チーム展開ガイド)とも重なる部分があるので、あわせて確認しておくと理解が早くなります。上流のGoogle Cloud's Agent Platformで使える機能がBedrockやMicrosoft Foundryとどう違うかは、プロバイダー別の機能比較で個別に整理しています。
関連する記事
Claude Code をもっと見る →Claude Code(クロードコード)とは — できること・料金・使い方・CLIから8つの拡張機構まで
Claude apps gatewayをAWSにデプロイする — ECS FargateとRDSの構成例
Claude apps gatewayをTerraformで構築する — AWS/GCP共通の勘所
Claude apps gatewayのデプロイと運用 — IdP登録からアップグレードまで
Claude CodeでVertex AIのリージョンを設定する — CLOUD_ML_REGIONの挙動
Claude apps gatewayのテレメトリ設定 — クライアントとゲートウェイをOTLPで追跡する