🗂

マルチリージョン構成の Cloud Run を Terraform で構築

2023/03/01に公開

概要

Cloud Run は手軽にコンテナをデプロイできて無料枠も広く使いやすい Google Cloud のサーバーレスサービスです。マルチリージョン構成を取ることも可能です。今回はマルチリージョン構成の Cloud Run を Terraform で構築していきたいと思います。

ディレクトリ構成

.
├── credential.json
├── main.tf
├── providers.tf
└── variables.tf

サンプルなので main.tf に resource の記述はまとめています。GCP 認証のための credential.json を事前に用意する必要があります。

構成図


かなり簡素ですが、基本的には上記のような流れになります。詳しく見ると Cloud Load Balancing は複数のリソースに分かれており、そのなかの Backend Service から今回は複数の Cloud Run のサーバーレスNEG にリクエストが振り分けられる形になります。

コード

main.tf
locals {
  cloud_run_deploy_regions = [var.tokyo_region, var.osaka_region]
}

# Clood Run
resource "google_cloud_run_service" "api" {
  for_each = toset(local.cloud_run_deploy_regions)
  name     = "api"
  location = each.key

  template {
    spec {
      containers {
        image = var.cloud_run_image
        ports {
          name           = "http1"
          container_port = 8080
        }
      }
    }
  }

  metadata {
    annotations = {
      "run.googleapis.com/ingress"      = "internal-and-cloud-load-balancing"
      "run.googleapis.com/launch-stage" = "BETA"
    }
  }

  autogenerate_revision_name = true

  traffic {
    percent         = 100
    latest_revision = true
  }
}

resource "google_cloud_run_service_iam_member" "api_member" {
  for_each = toset(local.cloud_run_deploy_regions)
  location = google_cloud_run_service.api[each.key].location
  project  = google_cloud_run_service.api[each.key].project
  service  = google_cloud_run_service.api[each.key].name
  role     = "roles/run.invoker"
  member   = "allUsers"
}

# Cloud Load Balancing
resource "google_compute_global_address" "api" {
  project = var.gcp_project_id
  name    = "${var.api_name}-ip"
}

resource "google_compute_region_network_endpoint_group" "api" {
  for_each              = toset(local.cloud_run_deploy_regions)
  name                  = "${var.api_name}-region-network-endpoint-group-${each.key}"
  network_endpoint_type = "SERVERLESS"
  region                = each.key
  cloud_run {
    service = google_cloud_run_service.api[each.key].name
  }
}

resource "google_compute_backend_service" "api" {
  name     = "${var.api_name}-backend-service"
  protocol = "HTTPS"

  backend {
    group = google_compute_region_network_endpoint_group.api[var.tokyo_region].self_link
  }
  backend {
    group = google_compute_region_network_endpoint_group.api[var.osaka_region].self_link
  }
}

resource "google_compute_url_map" "api" {
  project         = var.gcp_project_id
  name            = "${var.api_name}-url-map"
  default_service = google_compute_backend_service.api.self_link
}

resource "google_compute_target_http_proxy" "api" {
  project = var.gcp_project_id
  name    = "${var.api_name}-target-http-proxy"
  url_map = google_compute_url_map.api.self_link
}

resource "google_compute_global_forwarding_rule" "api" {
  project    = var.gcp_project_id
  name       = "${var.api_name}-forwarding-rule"
  target     = google_compute_target_http_proxy.api.self_link
  port_range = "80"
  ip_address = google_compute_global_address.api.address
}

output "load_balancing_ip" {
  description = "IP of load balancing"
  value       = google_compute_global_address.api.address
}

providers.tf

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.12.0"
    }
    google-beta = {
      source  = "hashicorp/google"
      version = "~> 4.12.0"
    }
  }

  backend "local" {}
}

provider "google" {
  project     = var.gcp_project_id
  credentials = file(var.credentials_path)
}

provider "google-beta" {
  project     = var.gcp_project_id
  credentials = file(var.credentials_path)
}

variables.tf

variable "app_name" {
  type    = string
  default = "sample"
}

variable "gcp_project_id" {
  type = string
}

variable "credentials_path" {
  type    = string
  default = "./credential.json"
}

variable "tokyo_region" {
  type    = string
  default = "asia-northeast1"
}

variable "osaka_region" {
  type    = string
  default = "asia-northeast2"
}

variable "api_name" {
  type    = string
  default = "api"
}

variable "cloud_run_image" {
  type    = string
  default = "gcr.io/cloudrun/hello"
}

main.tf は前半で Cloud Run を、後半で Cloud Load Balancing を扱っています。Cloud Run で使用するイメージは公開されたサンプルイメージ(gcr.io/cloudrun/hello)です。変数の gcp_project_id に関しては環境変数で渡してあげる想定です。TF_VAR_gcp_project_id=[gcp_project_id] で設定すると、実行時に変数として扱ってくれます。

ポイント

for_each と to_set() を使って 1度の記述で resource を複数作成しています。ここでは東京と大阪の2リージョンぶんの Cloud Run を作っていますが、更に多くのリソースを繰り返しで作る際にこの記述方法は更に便利そうです。また Cloud Run に関して「内部トラフィックと Cloud Load Balancing からのトラフィックを許可する」ことでアクセスを限定する記述を metadata の部分で入れています。

実行

terraform init したのちに環境変数をコマンドで渡して実行してみます。

$ TF_VAR_gcp_project_id=[gcp_project_id] terraform apply
...
Apply complete! Resources: 11 added, 0 changed, 0 destroyed.

Outputs:

load_balancing_ip = [ip]

リソースが作成されました!少し待ってから出力された ip にアクセスしてみます。

アクセスできました!

まとめ

実際に業務で用意する際にはSSL証明書を用意してHTTPS化する必要があるかと思います。また WAF や認証などのセキュリティー対策や細かい設定などをプロジェクトに合わせて整えたほうが良さそうです。今回はマルチリージョン構成の Cloud Run を Terraform で構築してみました。最後まで読んでいただき、どうもありがとうございました。

Discussion