CloudRunからAWS環境にプライベートな経路でアクセス
はじめに
こんにちは。AI Shiftの開発チームの大長です。
この記事は AI Shift Advent Calender 2024 の12日目の記事になります。
私は今年の6月にAI Shiftにジョインし、インフラ担当としてプロダクトが稼働するGKE基盤を含むクラウド関連、Arog CDなどCI/CD基盤の運用を行っています。
最近、Cloud RunからAWS環境のAPIサーバにセキュアにアクセスしたいという要望があり、インフラ構成を検討していました。
当初、Google Cloud環境とAWS環境をVPN接続後、Cloud RunからDirect VPC Egress経由でVPCに接続し、VPN経由でAWS環境のAPIサーバに直接接続する構成を検討していました。
ただ、要件として相互通信の必要はなかったため、構成を再検討したところ、ハイブリッド接続ネットワークエンドポイントグループ(ハイブリッド接続NEG)を利用することで、ロードバランサーのバックエンドにAWS環境のサーバのプライベートIPアドレスを指定できることを知りました。
今回の記事では、ハイブリッド接続NEGを利用して、Cloud RunからAWS環境のAPIサーバにプライベートでアクセスする環境の構築手順をご紹介します!
ハイブリッド接続ネットワークエンドポイントグループ(NEG)とは
ハイブリッド接続NEGは、オンプレミス環境、他社クラウドなどGoogle Cloud以外の環境をエンドポイントとして定義できるネットワークエンドポイント(NEG)の1つです。
ハイブリッド接続NEGを利用することで、プライベートなIPアドレスをエンドポイントとして設定することができるため、パブリックなエンドポイントを別途用意する必要はありません。ロードバランサーのバックエンドにCloud VPN、Cloud Interconnectなどのプライベートに接続した環境のIPアドレスを直接指定することができ、ロードバランサー経由でアクセスすることができます。
ハイブリッド接続NEGのユースケースとしては、下記のケースがあります。
- ロードバランサー経由でオンプレミス環境、他社クラウド環境で稼働するアプリケーションにトラフィックを転送することで、既存アプリケーションを有効活用しつつ、Google Cloud環境と併用したハイブリッドなインフラ構成を実現できる
- ロードバランサー経由でオンプレミス環境、他社クラウド環境で稼働するアプリケーションにトラフィックを転送することで、既存リソースにGoogle Cloudの最新のネットワークセキュリティサービス製品を適用できる
- Google Cloud環境、オンプレミス環境、他社クラウド環境にトラフィック分割を行うことで、オンプレミス環境からGoogle Cloud環境に段階的にインフラを移行できる
構築手順
本記事で構築する環境は下記になります。
ファイルの構成
今回作成したファイル群、ディレクトリ構成は下記になります。
$ tree .
.
├── misc
│ ├── tst-vpn-connect-ec2-user.key
│ └── tst-vpn-connect-ec2-user.key.pub
├── src
│ └── curl-golang
│ ├── Dockerfile
│ ├── go.mod
│ └── main.go
└── terraform
├── aws_ec2.tf
├── aws_vpc.tf
├── aws_vpn.tf
├── gcp_cloud_run.tf
├── gcp_compute_engine.tf
├── gcp_load_balancer.tf
├── gcp_vpc.tf
├── gcp_vpn.tf
├── locals.tf
├── provider.tf
├── terraform.tf
├── terraform.tfstate
├── terraform.tfstate.backup
└── variables.tf
5 directories, 19 files
Terraformを実行すると、下記のリソースが作成されます。
- AWS
- Amazon VPC、パブリックサブネット、プライベートサブネット、インターネットゲートウェイ
- Site-to-Site VPN接続、カスタマーゲートウェイ、仮想プライベートゲートウェイ
- Amazon EC2、キーペア、セキュリティグループ、IAMロール
- Google Cloud
- VPCネットワーク、サブネット(インスタンス用サブネット、プロキシ専用サブネット)
- Cloud VPN、Cloud Router、ピアVPNゲートウェイ、VPNトンネル
- 内部ロードバランサー、ハイブリッド接続NEG
- Compute Engine、Cloud Run、サービスアカウント
Terraformの詳細は下記のリポジトリをご確認下さい。実行する際は locals.tf を適切な値に書き換えて下さい。
事前準備
Terraformを実行すると上記の全てのリソースが作成されます。ただ、TerraformでCloud Run + Artifact Registryを同時に作成しようとするとエラーが発生するため、Artifact Registryの作成は事前にgcloud CLIで行い、Terraformではデータソースで読み込む形にしています。
それでは、Cloud Runで実行する簡易的なプログラム(環境変数 TARGET_IP
に対してHTTPリクエストを行うプログラム)をgcloud CLIを利用して、Artifact Regisotryにアップロードします。
gcloud CLIの利用方法について公式ドキュメントをご確認下さい。
下記のコマンドでArtifact Regisotryにリポジトリを作成し、イメージをアップロードします。
$ GCP_PROJECT_ID='XXXXXX'
$ GCP_REGION='asia-northeast1'
$ GCP_AR_REPOSITORY_ID='tst-vpn-connect-sample-app-repo'
$ cd src/curl-golang/
$ go mod init curl-golang
# Artifact Registry にリポジトリ作成
$ gcloud artifacts repositories create ${GCP_AR_REPOSITORY_ID} \
--project=${GCP_PROJECT_ID} --location=${GCP_REGION} --repository-format=docker
# Cloud Build を利用して、Artifact Registry にイメージをアップロードする
$ gcloud builds submit --project=${GCP_PROJECT_ID} \
--tag ${GCP_REGION}-docker.pkg.dev/${GCP_PROJECT_ID}/${GCP_AR_REPOSITORY_ID}/curl-golang
環境構築
事前準備が完了したので、Terraformを実行して、実際に検証環境を構築します。
$ cd ../../terraform/
$ terraform init
$ terraform plan
$ terraform apply
検証環境の構築自体はこれで完了です。主要な部分について軽く解説をさせていただきます。
Cloud VPN / Site-to-Site VPNの作成
最初に、Google Cloud環境にCloud VPN(HA-VPN)、AWS環境にSite-to-Site VPNを作成して、IPsec VPN接続を行いました。Terraformを実行すると一括でリソースが作成されますが、下記の順番でリソースが作成されています。
- GCP) Cloud VPN(HA-VPN)/Cloud Routerを作成
- AWS) カスタマーゲートウェイ/仮想プライベートゲートウェイ/Site-to-Site VPN接続を作成
- GCP) ピアVPNゲートウェイ/VPNトンネル/BGPセッションを作成
Cloud VPNを作成後に自動的に割り当てられたグローバルIPを利用してカスタマーゲートウェイを作成する、Site-to-Site VPN接続で作成したIP情報を利用してトンネルを作成するなど、手動で作成する場合、作成順序が重要になります。
コマンドラインインターフェース(gcloud/AWS CLI)で構築を行う場合、下記のページをご確認下さい。
Cloud VPN(HA-VPN)とSite-to-Site VPNでVPN接続を行う場合、VPNトンネルは4本作成されます。Terraform実行後、VPN経由でAWS環境で稼働するインスタンスのプライベートIPに接続できるか確認を実施します。
<Google Cloud>
ブラウザから管理コンソールにログインし、CloudShellを起動。IAP(Identity-Aware Proxy)経由でインスタンスにログイン後、AWS側のインスタンス ( 10.0.10.10
) へのPING疎通できることを確認します。
XXXXX@cloudshell:~ $ gcloud compute ssh tst-vpn-connect-gcp-vm-01 --zone asia-northeast1-a --tunnel-through-iap
XXXXX@tst-vpn-connect-gcp-vm-01:~$ ping 10.0.10.10
PING 10.0.10.10 (10.0.10.10) 56(84) bytes of data.
64 bytes from 10.0.10.10: icmp_seq=1 ttl=253 time=3.63 ms
64 bytes from 10.0.10.10: icmp_seq=2 ttl=253 time=2.91 ms
64 bytes from 10.0.10.10: icmp_seq=3 ttl=253 time=2.77 ms
64 bytes from 10.0.10.10: icmp_seq=4 ttl=253 time=2.62 ms
64 bytes from 10.0.10.10: icmp_seq=5 ttl=253 time=3.56 ms
^C
--- 10.0.10.10 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4007ms
rtt min/avg/max/mdev = 2.619/3.098/3.633/0.417 ms
<AWS>
ブラウザから管理コンソールにログインし、セッションマネージャーで接続後、Google Cloud側のインスタンス ( 10.10.10.10
) へのPING疎通できることを確認します。
sh-4.2$ ping 10.10.10.10
PING 10.10.10.10 (10.10.10.10) 56(84) bytes of data.
64 bytes from 10.10.10.10: icmp_seq=1 ttl=63 time=3.46 ms
64 bytes from 10.10.10.10: icmp_seq=2 ttl=63 time=2.65 ms
64 bytes from 10.10.10.10: icmp_seq=3 ttl=63 time=2.77 ms
64 bytes from 10.10.10.10: icmp_seq=4 ttl=63 time=2.96 ms
64 bytes from 10.10.10.10: icmp_seq=5 ttl=63 time=2.91 ms
^C
--- 10.10.10.10 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4006ms
rtt min/avg/max/mdev = 2.652/2.955/3.467/0.278 ms
内部ロードバランサー / ハイブリッド接続NEGの作成
次に、内部アプリケーションロードバランサー、ハイブリッド接続NEGを作成しました。内部ロードバランサーを作成後、バックエンドにハイブリッド接続NEGを作成しています。
ハイブリッド接続NEGは、 IP:PORT
の形で指定できるため、AWS環境で稼働するインスタンスのプライベートIPであるc10.0.10.10:80
を指定しています。
これにより、内部ロードバランサーにアクセスした際、AWS環境で稼働するインスタンスのレスポンスが返却されます。先ほどと同様、ブラウザからCloudShellを起動し、IAP(Identity-Aware Proxy)経由でインスタンスにログイン後、内部アプリケーションロードバランサーに割り当てられたIPアドレスに80ポートでアクセスできることを確認します。
XXXXX@cloudshell:~ $ gcloud compute ssh tst-vpn-connect-gcp-vm-01 --zone asia-northeast1-a --tunnel-through-iap
XXXXX@tst-vpn-connect-gcp-vm-01:~$ curl 10.10.20.3
Hello World from tst-vpn-connect-aws-public-vm-01
AWS環境のインスタンスに80ポートでアクセスした際にレスポンスが返却されるのは、EC2作成時にユーザデータでApacheのインストールを実施しています。
※ Apacheをインストールしただけですと no healthy upstream
が返却されてしまうため、ドキュメントルートに適当なファイル index.html
を配置して下さい。
Cloud Run / Direct VPC Egressの作成
最後に、Cloud Run、Direct VPC Egressを作成しました。Cloud RunからDirect VPC Egress経由でVPC内部の内部ロードバランサーにアクセスできるようにしています。
TerraformでDirect VPC Egressを設定している箇所は下記になります。とても簡単ですね。
Cloud RunからVPC内のリソースにアクセスする場合、以前であればサーバーレスVPCアクセスコネクタがありました。ただ、2024年04月にDirect VPC Egressの一般提供が開始したため、現在であればパフォーマンス性能の高いDirect VPC Egressを利用するのが推奨になります。
Direct VPC EgressとサーバーレスVPCアクセスコネクタの違いについては、下記の記事がわかりやすいためご確認下さい。
動作確認
動作確認として、Cloud Runにアクセスし、Cloud Runから内部ロードバランサー経由でAWS環境で稼働するインスタンスにアクセスできることを確認します。今回、Cloud Runの認証を「認証が必要」にしているため、下記の通りIDトークンをリクエストヘッダーに付与してアクセスしています。
$ curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://tst-vpn-connect-sample-run-app-XXXXXXXXXXX.asia-northeast1.run.app
{"status_code": 200, "body": "Hello World from tst-vpn-connect-aws-public-vm-01\n"}
ちなみに、ご存じの方も多いかもしれませんが、内部ロードバランサー経由でAWS環境で稼働するインスタンスにアクセスした場合、プロキシ専用サブネットからのアクセスになるため、FW等でIP制限を行う際はプロキシ専用サブネットのネットワークCIDRを許可する必要があります。下記がAWS環境で稼働するインスタンスのアクセスログの一部になります。
[root@ip-10-0-10-10 ~]# tail -f /var/log/httpd/access_log
10.10.30.4 - - [10/Dec/2024:20:37:20 +0000] "GET / HTTP/1.1" 200 50 "-" "Envoy/HC"
10.10.30.3 - - [10/Dec/2024:20:37:22 +0000] "GET / HTTP/1.1" 200 50 "-" "Go-http-client/1.1"
10.10.30.2 - - [10/Dec/2024:20:37:25 +0000] "GET / HTTP/1.1" 200 50 "-" "Envoy/HC"
10.10.30.3 - - [10/Dec/2024:20:37:25 +0000] "GET / HTTP/1.1" 200 50 "-" "Go-http-client/1.1"
10.10.30.4 - - [10/Dec/2024:20:37:25 +0000] "GET / HTTP/1.1" 200 50 "-" "Envoy/HC"
クリーンアップ
下記のコマンドを実行し、Terraformで作成したリソースを全て削除します。
$ terraform destroy
上記コマンドでリソースの削除を確認後、下記のコマンドを実行して、Artifact Registryを削除します。
$ GCP_PROJECT_ID='XXXXXX'
$ GCP_REGION='asia-northeast1'
$ GCP_AR_REPOSITORY_ID='tst-vpn-connect-sample-app-repo'
# Artifact Registryのリポジトリ削除
$ gcloud artifacts repositories list --project=${GCP_PROJECT_ID}
$ gcloud artifacts repositories delete ${GCP_AR_REPOSITORY_ID} \
--project=${GCP_PROJECT_ID} --location=${GCP_REGION}
$ gcloud artifacts repositories list --project=${GCP_PROJECT_ID}
<罠1: Artifact Registry削除後にterraform destoryを実行すると下記のエラーが発生する>
...
│ Error: Requested image was not found.
│
│ with data.google_artifact_registry_docker_image.sample,
│ on gcp_cloud_run.tf line 66, in data "google_artifact_registry_docker_image" "sample":
│ 66: data "google_artifact_registry_docker_image" "sample" {
Terraformからデータソースで読み込んでいる都合上、Artifact Registryを先に削除すると、terraform destryができなくなります。先にリポジトリを削除してしまった場合、データソースの記述を削除しましょう。
<罠2: サブネット削除時に下記のエラーが発生し、削除ができない>
│ Error: Error when reading or editing Subnetwork: googleapi: Error 400: The subnetwork resource 'projects/XXXXXX/regions/asia-northeast1/subnetworks/tst-vpn-connect-gcp-subnet-ane1' is already being used by 'projects/XXXXXX/regions/asia-northeast1/addresses/serverless-ipv4-1111111111111111111', resourceInUseByAnotherResource
Cloud RunがIPアドレスを開放していないのが原因です。1〜2時間経過後に、再度、リソースの削除を試して下さい。
注: Cloud Run リソースを削除または移動した後、Cloud Run が IP を解放するまで 1~2 時間待ってからサブネットを削除してください。
まとめ
今回、内部ロードバランサー、ハイブリッド接続NEGを経由して、Cloud RunからVPN接続したAWS環境で稼働するインスタンスにプライベートな経路でアクセスすることができました。ハイブリッド接続NEGを活用することで、既存の資産を活かしつつ、ハイブリッドなインフラを簡単に構築することができるので、今後も機会があれば活用していきたいです。
おわりに
ここまで読んでいただきありがとうございました。今回は全く生成AIが関係ない記事でしたが、AI ShiftはAI最前線の会社なので、来年はAIっぽい記事を書けるように精進します。
AI Shiftの開発チームでは、AIチームと連携してAI/LLMを活用したプロダクト開発を通し、日々ユーザのみなさまにより素晴らしい価値・体験を届けるべく開発に取り組んでいます。
AI Shiftではエンジニアの採用に力を入れています!この分野に少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか?(オンライン・19時以降の面談も可能です!)
【面談フォームはこちら】
Discussion