Cloud Run から内向きが「内部」なCloud Run にアクセスしたい
はじめに
Cloud Run から 別の Cloud Run へアクセスするときに、その Cloud Run って「すべてのインターネットからアクセスさせる」必要ってあるのかな?という疑問がありました🤔
公式ドキュメント[1]を読むと Cloud Run からのアクセスをInternal (内部)として扱う方法について記載がありました。
3つの方法があるようですが、どうも理解が進まないので実際に検証してみたいと思います。
やりたいこと
検証プロセスの概観
Cloud Run を2つ作成します。以下はそれぞれのコードです。
- client-node : server-node に対して、HTTPリクエストを投げる Cloud Run。この Cloud Run は常に 内向きは「すべて」とする。
- リクエスト先 Cloud Run の公開URLを環境変数として設定している
- server-node : client-node から HTTP リクエストを受け取る Cloud Run。てきとうなレスポンスを返す。この Cloud Run の内向きを「内部」に設定したい。
- Dockerfile : node18 の image をベースとして、
dig
を使うために bind-tools をインストールしている
事前準備
client-node, server-node のそれぞれを Cloud Run にデプロイします。この状態では、どちらの内向きも「すべて」にしておきます。デプロイができたら、自動発行されるURLにアクセスしてレスポンスを確認します。
gcloud run deploy Google Cloud CLI Documentation
# 1.1 server-node のデプロイ
gcloud run deploy server-node --source . \
--project $PROJECT_ID \
--region asia-northeast1 \
--ingress=all \
--allow-unauthenticated
# 1.2 アクセス確認
curl -XGET https://server-node-xxxxxxx-an.a.run.app
Hello World! from Server
# 2.1 client-node のデプロイ
# ※DOMAIN にデプロイした server-node の公開URLを設定します
gcloud run deploy client-node --source . \
--project $PROJECT_ID \
--region asia-northeast1 \
--set-env-vars=DOMAIN=server-node-xxxxxxx-an.a.run.app \
--ingress=all \
--allow-unauthenticated
# 2.2 アクセス確認
curl -XGET https://client-node-xxxxxxx-an.a.run.app
Hello World! from Server
アクセスイメージ図
正常にレスポンスを取得することができたら、server-nodeの Cloud Run の内向きを「内部」にしましょう。レスポンスが取得できないことを確認します。これで準備は整いました。
gcloud run services update Google Cloud CLI Documentation
# 「内部」に変更
gcloud run services update server-node \
--project $PROJECT_ID \
--region asia-northeast1 \
--ingress=internal
# アクセス確認
curl -XGET https://client-node-xxxxxxx-an.a.run.app
<html><head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>404 Page not found</title>
</head>
<body text=#000000 bgcolor=#ffffff>
<h1>Error: Page not found</h1>
<h2>The requested URL was not found on this server.</h2>
<h2></h2>
</body></html>
最終的なアクセスのイメージ図
1つ目の検証:すべての外向きトラフィックをVPCにする
VPC ネットワーク経由ですべてのトラフィックを送信するようにソースサービスを構成し、ダイレクト VPC 下り(外向き)またはコネクタに関連付けられたサブネットで限定公開の Google アクセスを有効にします。
VPCネットワークを経由したアクセスのイメージ図
1. VPC で使うサブネットの設定を変更する
今回は、作成済みの default VPC ネットワーク内の default (asia-northeast1 ) サブネットを利用します。サブネットで限定公開の Google アクセスを有効[2]にします。
gcloud compute networks subnets update Google Cloud CLI Documentation
# 1. 有効化する
gcloud compute networks subnets update default \
--region=asia-northeast1 \
--enable-private-ip-google-access
# 2. 確認する
gcloud compute networks subnets describe default \
--region=asia-northeast1 \
--format="get(privateIpGoogleAccess)"
True
2. すべての外向きトラフィックをVPCに送信する
client-node の外向きトラフィックの設定を変更します。このコマンドで、すべての外向きトラフィックは、Direct VPC egress を経由するようになります。
gcloud beta run services update Google Cloud CLI Documentation
# beta を使っています
gcloud beta run services update client-node \
--project $PROJECT_ID \
--region asia-northeast1 \
--network=default \
--subnet=default \
--vpc-egress=all-traffic
3. アクセス確認をする
正常にレスポンスを取得することができました。server-node へのリクエストログを確認すると、リモートIPが 0.0.0.0/32
になっています。これはインターネットゲートウェイを経由して、Cloud Run へアクセスしていることに起因しています。ただ、トラフィックは Google のネットワーク内に留まっているため、内部トラフィックとして扱われています[3]。
resource.type = "cloud_run_revision"
resource.labels.service_name = "server-node"
resource.labels.location = "asia-northeast1"
severity>=DEFAULT
2つ目の検証:run.app URL をプライベートなIP範囲で解決する
ソースサービスに関連付けられたサブネットで限定公開の Google アクセスを有効にし、DNS を構成して、run.app URL を private.googleapis.com(199.36.153.8/30)または restricted.googleapis.com(199.36.153.4/30)範囲に解決します。これらの範囲へのリクエストは、VPC ネットワーク経由で転送されます。
プライベートなIPをつかったアクセスのイメージ図
0. 事前準備
2つ目の検証をわかりやすくするために、事前に外向きのネットワーク設定を「プライベート IP へのリクエストのみを VPC にルーティングする」に変更します。
gcloud run services update client-node \
--project $PROJECT_ID \
--region asia-northeast1 \
--vpc-egress=private-ranges-only
ふたたび、レスポンスが取得できないことを確認します。これで準備は整いました。
1. Cloud DNS を設定する
今回は、Cloud Run の自動発行された URL が restricted.googleapis.com
(199.36.153.4/30
)の範囲で名前解決されるようにします[4]。
DNSの反映に少し時間がかかるので気長に待ってください。
gcloud dns managed-zones create Google Cloud CLI Documentation
gcloud dns record-sets Google Cloud CLI Documentation
# 1. Cloud DNS 限定公開ゾーンを作成します
gcloud dns managed-zones create run-app \
--project=$PROJECT_ID \
--description="" \
--dns-name="run.app." \
--visibility="private" \
--networks="default"
# 2. IP範囲を指す run.app のAレコードを追加します
gcloud dns record-sets create run.app \
--project=$PROJECT_ID \
--type="A" \
--zone="run-app" \
--rrdatas="199.36.153.4,199.36.153.5,199.36.153.6,199.36.153.7" \
--ttl="60"
# 3. run.app を指す *.run.app に CNAME レコードを作成します
gcloud dns record-sets create "*.run.app" \
--project=$PROJECT_ID \
--type="CNAME" \
--zone="run-app" \
--rrdatas="run.app." \
--ttl="60"
# このとき自分のPC端末から名前解決を確認することで限定公開であることを確認できます
# e.g.)
dig server-node-xxxxxxxx-an.a.run.app +short
216.239.36.53
216.239.38.53
216.239.34.53
216.239.32.53
2. アクセス確認をする
正常にレスポンスを取得することができました。client-node の名前解決をしたログを確認します。DNSに設定したIPアドレスがログに出力されているはずです。
resource.type = "cloud_run_revision"
resource.labels.service_name = "client-node"
resource.labels.location = "asia-northeast1"
名前解決をした結果のログ
3つ目の検証:Private Service Connect (PSC) を使う
Private Service Connect または内部アプリケーション ロードバランサを設定して、宛先の Cloud Run サービスと通信するようにします。この構成では、内部 IP アドレスを使用して Cloud Run にアクセスするため、リクエストは VPC ネットワーク経由で転送されます。
PSCをつかったアクセスのイメージ図
0. 事前準備
検証結果をわかりやすくするため、設定済みのDNSレコードを削除します。
gcloud dns record-sets delete run.app \
--type="A" \
--zone="run-app"
ふたたび、レスポンスが取得できないことを確認します。これで準備は整いました。
1. Private Service Connect (PSC) を作成します
対象のAPIバンドルは、今回はどちらでもいいので VPC-SC
を指定します。
また、Service Directory はリージョンのみ指定して、名前空間を自動生成します[5]。
# 1. 新しい静的 IP アドレスを作成します
gcloud compute addresses create run-app-psc-ip \
--global \
--purpose=PRIVATE_SERVICE_CONNECT \
--addresses="10.10.10.10" \
--network="default"
# 2. Private Service Connect の接続エンドポイントを作成します
gcloud compute forwarding-rules create runapp \
--global \
--network=default \
--address=run-app-psc-ip \
--target-google-apis-bundle=vpc-sc \
--service-directory-registration=projects/$PROJECT_ID/locations/asia-northeast1
PSC エンドポイントの確認
自動生成された Service Directory の確認
ちなみに、PSC はロードバランサーの「フロントエンド」と「転送ルール」から確認できます。
Private Service Connect エンドポイント: エンドポイントは、Private Service Connect サービスにマッピングされた IP アドレスをコンシューマーに提供する転送ルールを使用してデプロイされます[6]。
と記述があるので、おそらくリソース的には同じ種類のものなんだと思います。
コンソール > フロントエンド
コンソール > 転送ルール
2. PSCエンドポイントを使用する
PSCを作成したので、p.googleapis.com
DNSゾーンが作成されています。
しかし、今回はデフォルトのDNS名を使用するようにDNSレコードを設定します[7]。
DNSゾーン
# PSC への名前解決のため、DNSレコードを追加します
gcloud dns record-sets create run.app \
--project=$PROJECT_ID \
--type="A" \
--zone="run-app" \
--rrdatas="10.10.10.10" \
--ttl="60"
3. アクセス確認する
正常にレスポンスを取得することができました。client-node の名前解決をしたログを確認します。DNSに設定したIPアドレスがログに出力されているはずです。
名前解決をした結果のログ
番外編:VPCのシステム生成のデフォルトルート (0.0.0.0/0)って必要ですか?🧐
VPC を作成すると、デフォルト ルート(0.0.0.0/0)が自動的に作成されます。そのため、特にルーティングの設定なく外部と通信を行うことができます。これは、ネットワークを完全にインターネットから隔離したい場合には都合が悪いです。これを削除することは可能でしょうか?🧐
答えは、削除可能です。ただし、検証2 のrestricted.googleapis.com
またはprivate.googleapis.com
を利用する場合は、削除後に追加のルートを設定する必要があります。検証3 の PSC を利用する場合はこの設定は必要ありません。
追加のルートは以下のように作成します。
- デフォルトルート (0.0.0.0/0) を消してしまいましょう
デフォルトルートの削除
- Google API を利用するためのカスタムルートを構成する[8] ※PSCを利用する場合は不要です
gcloud beta compute routes create Google Cloud CLI Documentation
# 1. restricted.googleapis.com を利用するようにするルートを作成する
gcloud beta compute routes create default-internet-gateway \
--project=$PROJECT_ID \
--network=default \
--priority=1000 \
--destination-range=199.36.153.4/30 \
--next-hop-gateway=default-internet-gateway
# 2. 作成したルートを確認する
gcloud compute routes list \
--filter="default-internet-gateway default"
さいごに
なんとなくインターネットに公開したくないなと思いつつも、内部にしたらどうやってアクセスするんだっけ?といった疑問が解消されました。より安全に Cloud Run を利用できるようになり、枕を高くして眠ることができそうです💤
PSC を利用するにはコストが発生してしまいます。そのため、特別な利用がない限り「2つ目の検証:run.app URL をプライベートなIP範囲で解決する」を利用することがよさそうだと感じました。
その他参考資料
- オンプレから Cloud Run にプライベートな経路でアクセスする
- 限定公開の Google アクセスの仕組みと手順をきっちり解説 - G-gen Tech Blog
- 初めての Private Service Connect #1 PSCってなに? 編 by Takao Setaka google-cloud-jp Medium
Discussion