概要
PSC(Private Service Connect)を利用することで、GCP上でプロジェクト・VPCをまたいだ内部通信をすることができます。今回は、プロジェクトをまたいだCloudRunからCloudRunへの通信を可能とする構成とそのTerraform定義を紹介します。
構成
今回は以下のような構成を作りたいと思います。ProjectAにAPIを提供するCloudRunを配置し、ProjectBにそれを参照するClientアプリのCloudRunを配置する構成となっています。
CloudRunからVPCへ通信する場合はVPC Connectorを利用するか、Direct VPCを利用する方法があります。今回はVPC Connectorを利用する構成とします。また、PSCのクライアント側の構成としてはPSC Backendと PSC Endpointがありますが、今回はPSC Endpointを利用した構成とします。
CloudRunからVPCへの接続については以下をご参照ください。
またPSCについては以下をご参照ください。
構成についての補足
ProjectAにおけるサブネット
今回ProjectAにおいて、3つのSubnetを用意しています。1つはLoadBalancerを配置しているSubnetです。このSubnetは普通のSubnetなのですが、その他2つは特殊なサブネットとなります。Subnetを作成するときにpurposeを指定することで特殊なSubnetを作ることができます。どんな種類があるかは以下のTerraformプロバイダのページが参考になります。
今回はProjectBにおいて、2つの特殊なサブネットを用意しています。1つはPSC Attachment専用のSubnetです。これはAttachmentにつき1つ作る必要があります。purposeは PRIVATE_SERVICE_CONNECT
を指定します。
もう1つはregional managed proxy用のサブネットになります。これは、EnvoyベースのLBを利用する場合に用意しておく必要があります。purposeは REGIONAL_MANAGED_PROXY
を指定します。詳しくは以下をご参照ください。
プロジェクトBにおけるサブネット
また、ProjectBにおいても2つのSubnetを用意しています。1つはPSC Endpointを配置するためのサブネットです。このSubnetは普通のSubnetです。もう1つはVPC Connector専用のサブネットです。purposeは PRIVATE_SERVICE_CONNECT
を指定する必要があり、CIDRレンジは/28に固定する必要があります。
実装
それでは、具体的な実装を紹介します。基本的にはすべてTerraformでリソースを作成しますが、Cloud Runはgcloudコマンドでリリースする構成としました。これは、Cloud Runはインフラとしての管理ではなくアプリケーション側での管理としたいためです。Terraformで定義をした場合、アプリケーション側でデプロイを行うとTerraformで定義した構成と差分が出てしまうため、Terraformでの定義自体をしないようにしています。
プロジェクトA
Terraform
# Networ
resource "google_compute_network" "project_a_network" {
name = "project-a-network"
project = "project-a"
auto_create_subnetworks = false
}
# Subnet
resource "google_compute_subnetwork" "project_a_subnet" {
name = "project-a-subnet"
project = "project-a"
region = "asia-northeast1"
ip_cidr_range = "x.x.x.x/xx"
network = google_compute_network.project_a_network.name
}
resource "google_compute_subnetwork" "project_a_subnet_for_psc" {
name = "project-a-subnet-for-psc"
project = "project-a"
region = "asia-northeast1"
ip_cidr_range = "x.x.x.x/xx"
network = google_compute_network.project_a_network.name
purpose = "PRIVATE_SERVICE_CONNECT"
}
resource "google_compute_subnetwork" "project_a_subnet_for_regional_proxy" {
name = "project-a-subnet-for-regional-proxy'
project = "project-a"
region = "asia-northeast1"
ip_cidr_range = "x.x.x.x/xx"
network = google_compute_network.project_a_network.name
purpose = "REGIONAL_MANAGED_PROXY"
role = "ACTIVE"
}
# load balancer
resource "google_compute_region_network_endpoint_group" "neg_cloud_run_sample_api" {
name = "neg-cloudrun-sample-api"
network_endpoint_type = "SERVERLESS"
region = "asia-northeast1"
cloud_run {
service = "sample-api"
}
}
resource "google_compute_region_backend_service" "backend_to_service_sample_api_internal" {
name = "backend-service-to-sample-api-internal"
region = "asia-northeast1"
load_balancing_scheme = "INTERNAL_MANAGED"
protocol = "HTTP"
backend {
balancing_mode = "UTILIZATION"
group = google_compute_region_network_endpoint_group.neg_cloud_run_sample_api.self_link
}
}
resource "google_compute_region_url_map" "url_map_to_sample_api" {
name = "url-map-to-sample-api-internal"
region = "asia-northeast1"
default_service = google_compute_region_backend_service.backend_to_service_sample_api.self_link
}
resource "google_compute_region_target_http_proxy" "target_http_proxy_to_sample_app_internal" {
name = "target-http-proxy-to-sample-app-internal"
description = "target-http-proxy-to-sample-app-internal"
region = "asia-northeast1"
url_map = google_compute_region_url_map.url_map_to_sample_api.self_link
}
resource "google_compute_address" "sample_app_internal_load_balancer_address" {
name = "sample-app-internal-balancer-address"
subnetwork = google_compute_subnetwork.project_a_subnet.id
address_type = "INTERNAL"
address = "x.x.x.x"
region = "asia-northeast1"
}
resource "google_compute_forwarding_rule" "forwarding_rule_to_sample_app_internal" {
provider = google-beta
name = "forwarding-rule-to-sample-app-internal"
region = "asia-northeast1"
ip_protocol = "TCP"
load_balancing_scheme = "INTERNAL_MANAGED"
port_range = "80"
network = google_compute_network.project_a_network.id
subnetwork = google_compute_subnetwork.project_a_subnet.id
target = google_compute_region_target_http_proxy.target_http_proxy_to_sample_app_internal.self_link
ip_address = google_compute_address.sample_app_internal_load_balancer_address.address
network_tier = "PREMIUM"
}
# PSC Attachment
resource "google_compute_service_attachment" "service_attachment_to_ma_manager" {
name = "service-attachment-to-sample-api"
region = "asia-northeast1"
enable_proxy_protocol = false
connection_preference = "ACCEPT_MANUAL"
nat_subnets = [google_compute_subnetwork.project_a_subnet_for_psc.id]
target_service = google_compute_forwarding_rule.forwarding_rule_to_sample_app_internal.id
consumer_accept_lists {
project_id_or_num = "project-b"
connection_limit = 1
}
}
CloudRun
gcloud run deploy sample-api \
--region asia-northeast1 \
--image "image" \
--service-account "sa@xxx.iam.gserviceaccount.com" \
--ingress internal-and-cloud-load-balancing \
--concurrency 10 \
--min-instances 1 \
--max-instances 3
補足
CloudRunのdeployについて
Cloud Runにおいて、--ingress
オプションで internal-and-cloud-load-balancing
を指定することで、内部またはLBからの通信のみを許可することができます。gcloud run deploy
コマンドの詳細は以下のドキュメントをご参照ください。
プロジェクトB
Terraform
# Network
resource "google_compute_network" "project_b_network" {
name = local.project
project = local.project
auto_create_subnetworks = false
}
# Subnet
resource "google_compute_subnetwork" "project_b_subnet" {
name = "project-b-subnet"
project = "project-b"
region = "asia-northeast1"
ip_cidr_range = "x.x.x.x/xx"
network = google_compute_network.project_b_network.name
}
resource "google_compute_subnetwork" "project_b_subnet_for_sample_app_vpc_connector" {
name = "${local.project}-for-vpc-connector-subnet-asia-northeast1"
project = local.project
region = "asia-northeast1"
ip_cidr_range = "x.x.x.x/28"
network = google_compute_network.project_b_network.id
private_ip_google_access = true
}
# VPCConnector
resource "google_vpc_access_connector" "sample_client_app_vpc_connector" {
name = "sample-client-app-vpc-connector"
region = "asia-northeast1"
subnet {
name = google_compute_subnetwork.project_b_subnet_for_sample_app_vpc_connector.name
project_id = "project-b"
}
machine_type = "e2-micro"
min_instances = 2
max_instances = 3
}
# PSC Endpoint
resource "google_compute_address" "sample_api_psc_endpoint_address" {
address_type = "INTERNAL"
ip_version = "IPV4"
name = "sample-api-psc-endpoint-address"
project = "project-b"
purpose = "GCE_ENDPOINT"
region = "asia-northeast1"
subnetwork = google_compute_subnetwork.project_b_network.id
address = "x.x.x.x" # project_b_subnetのレンジから指定
}
resource "google_compute_forwarding_rule" "sample_api_psc_endpoint" {
load_balancing_scheme = ""
ip_address = google_compute_address.sample_api_psc_endpoint_address.id
name = "sample-api-psc-endpoint"
subnetwork = google_compute_subnetwork.project_b_network.id
project = "project-b"
region = "asia-northeast1"
target = "https://www.googleapis.com/compute/v1/projects/project-b/regions/asia-northeast1/serviceAttachments/service-attachment-to-sample-api"
"
service_directory_registrations {
namespace = "goog-psc-default"
}
}
CloudRun
gcloud run deploy sample-client-app \
--region asia-northeast1 \
--image "image" \
--service-account "sa@xxx.iam.gserviceaccount.com" \
--vpc-connector sample-client-app-vpc-connector \
--vpc-egress private-ranges-only \
--concurrency 10 \
--min-instances 1 \
--max-instances 1
補足
PSC Endpointのアドレスに付いて
PSC Endpointでは、指定したサブネット内のアドレスを紐づけることができます。そして、VPC内から紐づけたアドレスに対して通信をすることでPSC Endpointに紐づいたPSC Attachmentを経由し、Attachmentに紐づいたリソースへアクセスすることができます。つまり、自分たちで管理しているVPC内のアドレスにアクセスすることで、他のプロジェクト・VPC内のリソースのアクセスが可能となります。これにより、他のVPCのCIDRレンジがどうなっているかなどを気にする必要がありません。
PSC Endpointのtargetについて
PSC Endpointでは、ターゲットとなるPSC Attachmentをlink形式で指定する必要があります。基本的には以下の形式のリンクとなります。
https://www.googleapis.com/compute/v1/projects/アタッチメントのプロジェクト名/regions/アタッチメントのリージョン/serviceAttachments/アタッチメント名
CloudRunのdeployについて
Cloud Runにおいて、VPC Connectorを経由した通信をしたい場合は、deployコマンドでどのVPC Connectorを利用するかを --vpc-connector
オプションで指定する必要があります。また、すべての通信をVPC Connector経由にするか、Private IPのみをVPC Connectorを経由させるようにするかを選択ができます。今回は --vpc-egress private-ranges-only
とすることで、Private IPのみをVPC Connector経由で通信するように設定しています。こちらも以下のドキュメントをご参照ください。
まとめ
今回は、PSCを利用してプロジェクトをまたいだCloudRunからCloudRunへの通信を内部の経路で行う構成について紹介しました。PSCを利用することで、VPCピアリングを利用せずにVPCをまたいだ通信ができるため、CIDRかぶりの問題を考えたりといった頭が痛い問題から開放されてかなり便利だなと感じています。今回の構成を参考にしていただけたら幸いです。