まだVPCピアリングで消耗してるの?GKEからPSC経由でAWSのプライベートAPIに安全に接続する方法
こんにちは。AI Shiftのプラットフォームチームに所属している大長です。
普段は AI Worker VoiceAgentというプロダクトのインフラ基盤の管理・運用を行っています。
この記事は AI Shift Advent Calendar 2025 の14日目の記事です。
はじめに
昨年の AI Shift Advent Calendar 2024 では、Cloud Run から AWS 上のAPIサーバへHybrid NEG + Cloud VPN を利用したプライベート接続構成を紹介しました。
今年はその "続編" として、接続元が Cloud Run から GKE に変わった場合の構成を紹介します。
Cloud Run と GKE はネットワークモデルが大きく異なります。Cloud Run は VPC への接続がプラットフォーム側で抽象化されているのに対し、GKE は Compute Engine ベースの「VPC 直結型ワークロード」のため、ルーティングやサブネット、Egress、Firewall などの L3/L4 設計を明示的に行う必要があります。そのため、Cloud Run で利用できた接続方式は GKE ではそのまま適用できず、GKE のネットワーク特性を踏まえた別アプローチが必要になります。
今回の記事では、GKE から AWS の Private API へ安全に接続する構成について、VPC Peering ではなく PSC を選択した理由と、Terraform による構築手順、CIDR 重複時の動作検証結果を説明します。
やりたいこと
今回達成したい要件は次の通りです。
「GKE 上のアプリケーションから、AWS 上のプライベート API サーバに安全にアクセスしたい」
ただ、顧客ごとに VPC Peering / VPN を増やす運用は現実的ではなく、CIDR が重複する可能性があるためVPC 間を直接接続する方式は成立しません。さらに、マルチテナント環境でもスケールする方式が必要でした。
これらの理由から、「ネットワークそのものを繋がず、サービスだけをプライベートに公開する仕組み」が必要でした。その要件を満たす技術が今回の主役である「 Private Service Connect(PSC) 」です。
Private Service Connect(PSC)とは?
Private Service Connect (PSC) はネットワークを接続せず、サービス単位でプライベートアクセスを提供する Google Cloud の機能です。

従来の VPC Peering や VPN は、ネットワーク単位での接続を前提としているため、複数環境を安全かつシンプルに接続したい場面では、接続元・接続先のネットワーク設計に依存し、ネットワーク規模に応じてルート管理やアクセス制御の複雑性が増すなどの課題がありました。
特にマルチテナント環境では、ネットワークを直接つなぐ方式ではスケールしづらく、「サービス単位で接続を提供する」アプローチが求められます。
PSC は、こういった課題を解消するために、次の 2 つのリソースで構成されています。
- PSC Endpoint (Consumer) : Consumer VPC に内部 IP として作られる入口
- Service Attachment (Producer) : ILB を PSC 経由で公開する窓口
これにより、GKE から見れば内部IPを叩くだけで ILB → AWS に到達できます。

PSC は CIDR 重複の影響を受けず[1]、VPC 間で L3 の到達性を共有しないため、マルチ顧客環境・マルチクラウド構成、異なる組織間連携に特に強い接続方式です。今回の構成では、この性質を検証するためにあえて、GCP側、AWS側のネットワークを被らせてネットワーク設計を行っています。
前提と構成概要
全体構成

本構成は以下の 3 層で構成されています。
- Consumer(GKE)
- Producer(Internal Load Balancer)
- Backend(Amazon EC2:ILB の backend として接続)
Consumer(GKE)
GKE 側はシンプルです。GKE クラスタと、PSC Endpoint という内部 IP アドレスが1つあるだけ。GKE の Pod から見ると、この PSC Endpoint の IP を叩くだけで AWS の API に到達できます。その裏側で何が起きているか(PSC → ILB → VPN → AWS という経路)は、GKE 側からは見えません。まるで同じ VPC 内のサービスにアクセスしているような感覚です。
Producer(Internal Load Balancer)
ここが少し複雑なポイントです。AWS の EC2 インスタンスは、Hybrid NEG という仕組みで GCP の Internal Load Balancer(ILB)の backend として登録されています。つまり、ILB から見ると「AWS の EC2 も自分の backend の1つ」という扱いになります。そして、この ILB を PSC 経由で公開するために Service Attachment を作成します。
ここで重要なのは、PSC が直接接続するのは AWS ではなく、GCP 内の ILB だということです。 AWS は ILB の backend として存在しているだけで、PSC からは見えません。この設計により、PSC は「GCP 内のサービス」として扱えるため、CIDR の重複などに影響されないのです。
リポジトリ構成(Terraform)
今回の構成は以下のリポジトリで公開しています。
色々と検証がしやすいようにコンポーネント単位に tfファイル を分割しています。
.
├── misc
├── scripts
│ └── test-psc-connection.sh
└── terraform
├── aws_ec2.tf
├── aws_vpc.tf
├── aws_vpn.tf
├── gcp_gke.tf
├── gcp_load_balancer.tf
├── gcp_psc.tf
├── gcp_vpc.tf
├── gcp_vpn.tf
├── locals.tf
├── outputs.tf
├── provider.tf
└── variables.tf
※ 検証時のネットワーク設計と CIDR のポイント
今回の検証では、PSC が CIDR の影響を受けないことを確認するため、GCP側のサブネットとAWS側のサブネットのCIDR をあえて同一にしています。実際の CIDR は terraform/locals.tf で定義していますが、具体的には次のようにネットワークを設計しています。
| レイヤ / 種別 | Subnet Name | Subnet CIDR | 用途・補足 |
|---|---|---|---|
| GCP (Consumer) |
consumer-gke-node | 10.0.10.0/24 |
GKE Node Subnet(★重複) |
| consumer-psc-endpoint | 10.0.20.0/24 |
PSC Endpoint Subnet(★重複) | |
| gke-pod-range | 10.0.100.0/24 |
Pod Secondary Range | |
| gke-svc-range | 10.0.200.0/24 |
Service Secondary Range | |
| GCP (Producer) |
producer-psc-nat | 10.10.10.0/24 |
PSC NAT Subnet[2] |
| producer-ilb-frontend | 10.10.20.0/24 |
Internal LB Frontend | |
| producer-ilb-proxy | 10.10.30.0/24 |
ILB Proxy-only Subnet | |
| AWS (Backend) |
aws-private | 10.0.20.0/24 |
Backend EC2(★重複) |
| aws-public | 10.0.10.0/24 |
Bastion / VPN GW(★重複) |
また、既存のGKE環境に影響を与えたくないなどの要件がなければ PSC Endpoint Subnet は GKE 用サブネットと同一でも問題はありませんが、PSC NAT Subnet は専用サブネット[2:1]である必要があるのでご注意下さい。
構築手順
まずはクラウド環境(GCP / AWS)の認証情報を設定します。
<Google Cloud>
gcloud auth login
gcloud auth application-default login
<AWS>
aws login
※ aws login コマンドは 2025/11 に新しくリリースされた機能で、GCPと同様、ブラウザベースで認証が行えるようになりました。詳細は「AWS CLIの 新機能 aws login コマンドを試してみた@Developers IO」をご確認下さい
次に、Terraform を実行して全リソースを構築します。
➜ git clone https://github.com/daicho-takuma/sample-gke2vpn-tf.git
➜ cd sample-gke2vpn-tf/terraform/
➜ terraform init
➜ terraform plan
➜ terraform apply
terraform apply が完了すると下記のリソースが作成されます。
<Google Cloud>
- Consumer
- VPC Network、Subnet (GKE用、PSC Endpoint用)
- Google Kubernetes Engine (Autopilot)
- PSC Endpoint
- Producer
- VPC Network、Subnet(PSC NATサブネット、ILBFront用、プロキシ用)
- Cloud VPN、Cloud Router、ピアVPNゲートウェイ、VPNトンネル
- 内部ロードバランサー、ハイブリッド接続NEG
- Service Attachment
<AWS>
- Amazon VPC、パブリックサブネット、プライベートサブネット、インターネットゲートウェイ
- Site-to-Site VPN接続、カスタマーゲートウェイ、仮想プライベートゲートウェイ
- Amazon EC2、キーペア、セキュリティグループ、IAMロール
GKE の Pod から叩くプライベートAPIの内部IPは terraform output から取得ができます。
➜ terraform output psc_endpoint_ip
"10.0.20.2"
コードの説明
PSC の主要コンポーネントは次の2つです。
PSC Endpoint (Consumer)
Consumer VPC 内に内部IPとして Forwarding Rule を作成し、Service Attachment と紐付けます。これにより、GKE の Pod が PSC Endpoint に送るパケットは GCP Control Plane により ILB に転送されます。
Service Attachment (Producer)
Service Attachment は、ILB を PSC コントロールプレーンに公開するためのメタデータです。
target_service に ILB の Forwarding Rule を指定し、connection_preferenceを ACCEPT_AUTOMATIC に設定することで、接続を自動承認します。また、nat_subnets に PSC NAT 用のサブネットを設定します。
これにより ILB は "PSC 経由で利用可能なサービス" として認識されます。
動作確認(GKE → PSC → ILB → Amazon EC2)
構築が完了したら、実際に GKE の Pod から PSC Endpoint を経由して AWS の EC2 インスタンスに到達できるか確認しましょう。検証用のスクリプトを用意しているので、これを実行するだけです。
➜ bash ./scripts/test-psc-connection.sh
このスクリプトは、Terraform の output から必要な情報(Project ID、GKE クラスタ名、PSC Endpoint の IP など)を取得し、テスト用の Pod を作成して PSC Endpoint に HTTP リクエストを送信します。レスポンスが返ってくれば、経路が正しく構築されていることが確認できます。
実行すると、こんな感じで結果が表示されます。
➜ bash ./scripts/test-psc-connection.sh
Retrieving configuration values from Terraform outputs...
...
==========================================
Connection Test Result
==========================================
📋 Connection Information:
Source: GKE Pod (IP: 10.0.100.27)
Destination: PSC Endpoint (IP: 10.0.20.2)
Target: Amazon EC2 Instance (via VPN)
✅ Connection Status: SUCCESS
📊 Response Details:
HTTP Status Code: 200
Response Time: 0.187657s
📝 Response Body:
┌─────────────────────────────────────────────────────────┐
│ Hello World from test-gke2vpn-aws-private-vm-01
└─────────────────────────────────────────────────────────┘
🔗 Connection Path:
GKE Pod (10.0.100.27)
↓
PSC Endpoint (10.0.20.2)
↓
Service Attachment
↓
Internal Load Balancer (Producer VPC)
↓
VPN Gateway (GCP → AWS)
↓
Amazon EC2 Instance ✅
✅ Test completed successfully!
HTTP 200 が返ってきて、AWS 側の EC2 インスタンスからのレスポンス( "Hello World from test-gke2vpn-aws-private-vm-01" )が確認できました。スクリプトの出力を見ると、トラフィックが以下の経路で流れていることが分かります。
GKE → PSC Endpoint → Service Attachment → ILB → VPN → Amazon EC2
動作確認で躓きやすいポイント
実際に試してみると、いくつかハマりやすいポイントがありました。
PSC Endpoint への接続は成功するのに、AWS 側の EC2 に到達しない場合は、ILB → Hybrid NEG → AWS への L3 ルートが正しく張られていない可能性があります。AWS 側の Security Group や Route Table の設定漏れもよくある原因です。
VPN トンネルは UP しているのに BGP が converged していない状態や、ILB の backend の HealthCheck がすべて Unhealthy のままになっているケースもあります。
PSC は L3 に依存しないため、PSC Endpoint への接続自体は成功して見えるのですが、その裏側の Backend 区間だけが落ちているという状態が起こりやすい点に注意が必要です。
まとめ
今回、GKE から別 VPC の API に安全にアクセスする構成を構築してみて、PSC の扱いやすさを実感しました。
ネットワークを直接つなぐ必要がなく、サービス単位で適切に境界を切れる点が、マルチテナント構成では特に有効でした。VPC Peering だと CIDR の制約に悩まされますが、PSC ならその心配がありません。
今後、もしGKEからVPCへのプライベート接続を行う際は、ぜひ VPC Peering ではなく、PSCをご検討下さい。
最後に
AI Shiftではエンジニアの採用に力を入れています!
少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか?
(オンライン・19時以降の面談も可能です!)
【面談フォームはこちら】
-
PSC が CIDR に依存しない理由は、PSC は Consumer → Producer 間の通信を GCP の service routing(L4/L7)で転送します。この経路では L3 ルーティングを共有していないため、VPC 間で CIDR が重複していても PSC Endpoint → ILB までの通信が成立します。 ↩︎
-
https://docs.cloud.google.com/vpc/docs/about-vpc-hosted-services?hl=ja#psc-subnets ↩︎ ↩︎
Discussion