Cloud Run, Cloud SQL, VPC の設定を Terraform でする

毎回しらべていてしんどいので。

参考資料 ここが一番わかりやすい https://codelabs.developers.google.com/connecting-to-private-cloudsql-from-cloud-run

用語の整理

"Private service access" を設定する一貫で、 "private service connection" をつくる。Google の docs だと "private connection" といってることもあるけど、こっちは「パブリックでない接続」という意味の一般用語ぽい。

つくるもの

注: ここでは defaultVPC を使うよ。要件的に微妙な場合は別途 google_compute_networkgoogle_compute_subnetwork を作ってね。

注: CLI のコマンドは Code Labs からコピペ、 terraform はわたしが書いたコード

つくるものはこういうイメージ↓

services

これらのサービスが有効になっている必要がある

gcloud services enable \
    sqladmin.googleapis.com \
    run.googleapis.com \
    vpcaccess.googleapis.com \
    servicenetworking.googleapis.com

VPC peering 用の IP address

gcloud compute addresses create google-managed-services-default \
    --global \
    --purpose=VPC_PEERING \
    --prefix-length=20 \
    --network=projects/$PROJECT_ID/global/networks/default
resource "google_compute_global_address" "google_managed_services_default" {
  name          = "google-managed-services-default"
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  prefix_length = 20
  network       = data.google_compute_network.default.id
}

private connection

gcloud services vpc-peerings connect - connect to a service via VPC peering for a project network

ref: https://cloud.google.com/sdk/gcloud/reference/services/vpc-peerings/connect

gcloud services vpc-peerings connect \
    --service=servicenetworking.googleapis.com \
    --ranges=google-managed-services-default \
    --network=default \
    --project=$PROJECT_ID
# CLI だと名前を指定していないけど、ここでは設定している
resource "google_service_networking_connection" "private_service_access_connector_default" {
  network                 = data.google_compute_network.default.id
  service                 = "servicenetworking.googleapis.com"
  reserved_peering_ranges = [google_compute_global_address.google_managed_services_default.name]
}

Cloud SQL instance, db, user

gcloud sql instances create $DB_INSTANCE_NAME \
    --project=$PROJECT_ID \
    --network=projects/$PROJECT_ID/global/networks/default \
    --no-assign-ip \
    --database-version=POSTGRES_12 \
    --cpu=2 \
    --memory=4GB \
    --region=$REGION \
    --root-password=${DB_INSTANCE_PASSWORD}

# db
gcloud sql databases create $DB_DATABASE --instance=$DB_INSTANCE_NAME

# user
gcloud sql users create ${DB_USER} \
    --password=$DB_PASSWORD \
    --instance=$DB_INSTANCE_NAME
resource "google_sql_database_instance" "main" {
  name             = "main"
  region           = var.region
  database_version = "POSTGRES_16"

  settings {
    tier              = "db-f1-micro"
    edition           = "ENTERPRISE"
    availability_type = "ZONAL"
    ip_configuration {
      ipv4_enabled                                  = false
      private_network                               = var.network_default_id
      enable_private_path_for_google_cloud_services = true
    }
    backup_configuration {
      enabled                        = true
      point_in_time_recovery_enabled = true
    }
  }
  
  # terraform だと private service connection が先に存在する必要がある
  depends_on = [var.private_service_access_connector_default_id]
}

resource "google_sql_database" "main_db" {
  name     = var.db_database_name
  instance = google_sql_database_instance.main.name
}

resource "google_sql_user" "main_user" {
  name     = var.db_username
  instance = google_sql_database_instance.main.name
  password = var.db_password
}

Serverless VPC Access connector

紛らわしいのが、 private service connection とは別に Cloud Run にもコネクターが必要になる。というのも、 Cloud Run はサーバレスなやつなので、 VPC の概念がない。

gcloud compute networks vpc-access connectors - manage Serverless VPC Access Service connectors

https://cloud.google.com/sdk/gcloud/reference/compute/networks/vpc-access/connectors

gcloud compute networks vpc-access connectors create ${SERVERLESS_VPC_CONNECTOR} \
    --region=${REGION} \
    --range=10.8.0.0/28
resource "google_vpc_access_connector" "serverless_default" {
  name = "serverless-conn-default"

  network       = data.google_compute_network.default.id
  ip_cidr_range = "10.8.0.0/28"
  region        = var.region
}

Cloud Run

直前でつくった serverless VPC access service connector を割り当てて Cloud Run を deploy する

gcloud run deploy $MENU_SERVICE_NAME \
    --image=gcr.io/$PROJECT_NAME/menu-service:latest \
    --region $REGION \
    --allow-unauthenticated \
    --set-env-vars DB_USER=$DB_USER \
    --set-env-vars DB_PASS=$DB_PASSWORD \
    --set-env-vars DB_DATABASE=$DB_DATABASE \
    --set-env-vars DB_HOST=$DB_INSTANCE_IP \
    --vpc-connector $SERVERLESS_VPC_CONNECTOR \
    --project=$PROJECT_ID \
    --quiet

(自分は gcloud コマンドでデプロイするので Terraform は省略)

tips

IP アドレスの数の考慮

IP アドレスの数が足りなくなったら、

  • CIDR block を expand するか、
  • 新しい IP address range を allocate

のどちらかができる。ただし、 allocation の数には quota があるので、 expand したほうがよさそう。

Select a CIDR block that is large enough to meet your current and future needs. If you later find that the range isn't sufficient in size, expand the range if possible. Although you can assign multiple allocations to a single service producer, Google enforces a quota on the number of IP address ranges that you can allocate but not the size (netmask) of each range.

https://cloud.google.com/vpc/docs/configure-private-services-access#considerations

allocation (= IP address range) の名前

自分で private service connection を作るとき (i.e. terraform とか CLI とか) google-managed-services-[your network name] という形式にしておくとよい。この名前は Google が自動で作る名前なのにで、新しい接続をコンソールからつくるときとかに、Google 側に「もう接続があるよ」とつたえられて新しいリソースが作られるのを防ぐ。IP アドレスって結構高いからやった方が良いと思う。

If you create the allocation yourself instead of having Google do it (such as through Cloud SQL), you can use the same naming convention to signal to other users or Google services that an allocation for Google already exists. When a Google service allocates a range on your behalf, the service uses the following format to name the allocation: google-managed-services-[your network name]. If this allocation exists, Google services use the existing one instead of creating another one.

https://cloud.google.com/vpc/docs/configure-private-services-access#considerations

Cloud SQL の private IP address を変えるとき

... は、なんか考慮が必要っぽい。詳しくはここ: https://cloud.google.com/sql/docs/mysql/configure-private-services-access#change-private-ip

Private service access で複数のリソースがある場合

たとえば、同じ VPC に複数の Cloud SQL があってそれらに Private service access を設定する必要があるとき、 Private service access は1つでよい...というのが明示的に書いてある。

If a single service producer offers multiple services, you only need one private connection for all of the producer's services.

https://cloud.google.com/vpc/docs/configure-private-services-access#creating-connection

もしどの Cloud SQL がどの connection を使うかを指定したいときは、VPC network を複数立ててね、とも書いてある。

If a single service producer offers multiple services and you want to control which allocated ranges are used for different service resources, you can use multiple VPC networks each with their own private connections.

google-managed-services-[network name] となっているもの上記の説明で納得できる。

解説的な

そもそもなんで private service access を設定する必要があるかというと、Cloud SQLVPC 内に置くためには、「自分の VPC」と、「Google 側の VPC」をつなげる必要がある。結局「自分の VPC」も Google 側のリソースだからわざわざ分ける必要は何? って思ったけど、Cloud SQL がマネージドサービスなのとか責任範囲とかが関わってくるっぽい。

ちなみに、説明としてはこうあったので

The private connection enables VM instances in your VPC network and the services that you access to communicate exclusively by using internal IP addresses.

自分のプロジェクトだと VM 使わないからいらないか〜と思って、private connection を作らずに private IP つきの Cloud SQL を作ったらふつうにエラーになった。