内部ALB経由の Cloud Run へ PSC と VPC Peering でつないで接続元IPを比べてみた
別のVPCから内部アプリケーションロードバランサ(内部ALB)の後ろのCloud Runにアクセスしたいことがありました。
VPCをまたぐ方法としてPrivate Service Connect(PSC)と VPC Network Peeringを候補としました。
それぞれの方法でCloud Runが受け取る接続元IPがどう変わるのか気になり検証してみました。
同じ形の構成を別々に作り、Cloud Run が受け取る X-Forwarded-For を比べました。
結論として、1つ目のIPは PSC では PSC の NAT サブネットのIP、Peering では接続元 VM のIPでした。
やってみた
共通部分は、リージョン内部ALB(HTTP)、サーバーレス NEG、Cloud Run です。
Cloud Run の受信設定は「内部」にしています。
Cloud Run のアプリには、受け取ったヘッダーと送信元IPを JSON で返す mendhak/http-https-echo を使いました。
イメージは Artifact Registry のリモートリポジトリ(Docker Hub)経由で取得しています。
接続元は、Consumer VPC に置いた外部IPなしの VM です。IAP 経由で SSH します。
Cloud Run の起動元ロールは VM のサービスアカウントだけに付けたので、VM から ID トークンを付けてリクエストします。
PSC では、サービスを公開する側を Producer、接続する側を Consumer と呼びます。Peering の構成も同じ呼び名にそろえています。
PSC は「Consumer VPC の PSC エンドポイント → サービスアタッチメント → 内部ALB」の順に届きます。
Peering は、Consumer VPC と Producer VPC をつなぎ、VM から内部ALBのIPへ直接届きます。

検証に使った Terraform コードは、次のリポジトリに置いています。
以降はそれぞれのコードを使って環境を構築した前提で進めます。
PSC 経由
サービスアタッチメントは内部ALBの転送ルールを対象にし、NAT 用のサブネット(purpose = "PRIVATE_SERVICE_CONNECT")を 10.20.2.0/28 で用意しました。
Consumer VPC には、エンドポイント(転送ルール)を作りました。
VM から、ID トークンを付けて PSC エンドポイントのIPにリクエストを送ります。
TOKEN=$(curl -s -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=<CLOUD_RUN_URL>")
curl -sS -H "Authorization: Bearer $TOKEN" http://10.10.0.2/
HTTP 200 が返り、Cloud Run のアプリが受け取った内容は次のとおりでした(抜粋)。
{
"headers": {
"x-forwarded-proto": "http",
"x-forwarded-for": "10.20.2.2,10.20.0.2",
"forwarded": "for=\"[<IPV6_PREFIX>:a14:202]\";proto=http"
},
"ip": "10.20.2.2",
"ips": [
"10.20.2.2",
"10.20.0.2"
]
}
X-Forwarded-For の1つ目は 10.20.2.2 で、PSC の NAT サブネット(10.20.2.0/28)のIPです。
2つ目は内部ALBの転送ルールのIP(10.20.0.2)です。
VM のIP(10.10.0.3)はどこにも含まれていません。
VPC Peering 経由
Peering 側は、Consumer VPC と Producer VPC を双方向に Peering でつなぎます。
カスタムルートのエクスポートとインポートは設定していません。
今回の通信先は内部ALBの転送ルールのIPです。
Peering でつなぐと、お互いのサブネットのルートが自動で交換されるため、設定を足さなくても VM から届きます(VPC Network Peering)。
宛先を内部ALBのIP(http://10.20.0.2/)にして同じようにリクエストすると、HTTP 200 が返りました(抜粋)。
{
"headers": {
"x-forwarded-proto": "http",
"x-forwarded-for": "10.10.0.2,10.20.0.2",
"forwarded": "for=\"[<IPV6_PREFIX>:a0a:2]\";proto=http"
},
"ip": "10.10.0.2",
"ips": [
"10.10.0.2",
"10.20.0.2"
]
}
1つ目は VM のIP(10.10.0.2)で、接続元がそのまま残りました。
2つ目は PSC のときと同じ、内部ALBの転送ルールのIPです。
送信元IPが変わる理由
結果を並べると、次のとおりです。
| PSC | VPC Peering | |
|---|---|---|
X-Forwarded-For |
10.20.2.2,10.20.0.2 |
10.10.0.2,10.20.0.2 |
| 1つ目 | PSC の NAT サブネットのIP | 接続元 VM のIP |
| 2つ目 | 内部ALBの転送ルールのIP | 内部ALBの転送ルールのIP |
PSC の公式ドキュメント(About published services)には、PSC は Consumer 側の送信元IPを、Producer VPC にある PSC サブネットのアドレスに変換するとあります。
PROXY protocol を有効にすると元の送信元IPを見られる、という説明も同じページにあります。
ただし、リージョン内部アプリケーションロードバランサは、同じページの対応表で PROXY protocol の列が「No」です。
対応しているのは、内部パススルーネットワークロードバランサなどです。
Peering は、2つの VPC がルートを交換して、内部IPで直接通信できるようにする仕組みです(VPC Network Peering)。
今回の結果でも、接続元 VM のIPは書き換わらずに残りました。
Cloud Run のログの remoteIp
Cloud Run のリクエストログの httpRequest.remoteIp は、PSC でも Peering でも IPv6 の形で記録され、IPv4 では確認できませんでした。
PSC 経由の remoteIp が IPv6 になる点は、次の記事でも触れられています。
今回の2つの結果は次のとおりです。
| PSC | VPC Peering | |
|---|---|---|
remoteIp |
<IPV6_PREFIX>:a14:202 |
<IPV6_PREFIX>:a0a:2 |
どちらも IPv4 ではなく IPv6 の形で、forwarded ヘッダーの for= と同じ値でした。
今回の構成では、Peering でも IPv6 が記録されました。
末尾32bitは、X-Forwarded-For の1つ目のIPv4と一致しています。
PSC は a14:202 が 10.20.2.2、Peering は a0a:2 が 10.10.0.2 です。
接続元のIPv4が必要なら、remoteIp ではなく、アプリが受け取る X-Forwarded-For ヘッダーを見ます。
今回のリクエストログには X-Forwarded-For は出ていなかったので、ログに残したい場合はアプリ側で出力します。
接続元のIPが必要なときの選び方
アプリで接続元のIPを使う要件があるなら、今回の構成では Peering が向いています。
PSC では NAT サブネットのIPに置き換わるので、X-Forwarded-For から接続元は分かりません。
PROXY protocol も、内部アプリケーションロードバランサは公式ドキュメントで非対応です。
Peering で読むのは、内部ALBのIPの直前にある値です。
クライアントが付けた X-Forwarded-For は左側に残るので、IP制限に1つ目を使うのは避けます。
実際に curl -H "X-Forwarded-For: 192.0.2.1" を付けてリクエストすると、PSC は 192.0.2.1,10.20.2.2,10.20.0.2、Peering は 192.0.2.1,10.10.0.2,10.20.0.2 になりました。
接続元のIPが不要なら、PSC が選べます。
Peering には、サブネットのIP範囲が重なってはいけないこと、推移的なルーティングがないことといった制約があります(VPC Network Peering)。
PSC は、公開するサービスを1つに絞れます。
接続できる Consumer も、サービスアタッチメントの ACCEPT_MANUAL で、Producer 側が承認したプロジェクトや VPC に限れます。
今回の検証は、VM から直接接続した場合です。
実環境の接続元IPが届くかは、間に NAT する機器があるかにも左右されます。
VPN などを経由した経路は再現していません。
おわりに
VPN などを経由した場合に接続元IPがどうなるかは、今回は確認できていません。
機会があれば試してみます。






