見出し画像

TerraformでCloud Runによるサーバーサイドタグ設定を自動化する方法


はじめに

こんにちは。電通デジタルのデータエンジニア 中﨑です。
ブラウザのCookie規制が進む中、サーバーサイドでのタグ管理は重要です。Google CloudのCloud Runを使用すると、スケーラブルで管理の手間の少ないサーバーサイド環境を簡単に提供できます。本記事では、GTMのサーバーサイドタグ設定のCloud Runをセットアップし、カスタムドメインの設定、グローバル外部アプリケーションロードバランサの設定、そしてこれらをTerraformで管理する方法について説明します。

想定読者

  • サーバーサイドタグを使った実装業務に携われている方

  • TerraformでCloud Runのセットアップを検討されている方

  • Cloud Runで ロードバランサを使用した環境構成を検討されている方

Cloud Runでカスタムドメインにマッピングする方法

まず、Cloud Runによるサーバーサイドタグ用の設定の流れは以下の通りとなっています。

  1. 新しいプレビュー サーバーとタグ設定用サーバーをプロビジョニングする

  2. デプロイメントをカスタム ドメインにマッピングする

  3. サーバーの URL を Google タグ マネージャーに追加する

詳細は以下の公式ドキュメントを参照してください。

今回は「2. デプロイメントをカスタム ドメインにマッピングする」以下2つの方法を目的の環境に合わせてTerraformで選択し、実行するコードをご紹介します。

  • グローバル外部アプリケーション ロードバランサを使用する(Google推奨)

  • Cloud Run のドメイン マッピング(限定的に利用可能、プレビュー)を使用する

Cloud Runを使用したグローバル外部アプリケーションロードバランサ

グローバル外部アプリケーションロードバランサはカスタムドメインをマッピングする際にGoogleが推奨している方法になります。
グローバル外部アプリケーションロードバランサをCloud Runで設定することで複数のリージョンにトラフィックを分散させることや低遅延アクセス、スケーラビリティの向上を実現することができます。
実装イメージとしては以下のようになっており複数のリソースを設定する必要があります。

Cloud Run アプリケーション用の外部アプリケーション ロードバランサのアーキテクチャ(以下
URL参照)

コンソールかGoogle Cloudでグローバル外部アプリケーションロードバランサを設定する際は以下の公式ドキュメントを参照してください。

Cloud Run のプレビュー機能のドメイン マッピングを使用してカスタム ドメインをマッピングする方法

グローバル外部アプリケーションロードバランサの環境には劣るものの、そこまで大きなスケールではない場合やコストを少しでも抑えたい場合はCloud Runのプレビュー機能のドメインマッピングがおすすめです。
この機能はCloud RunのUI画面>>カスタムドメインを管理>>マッピングを追加>>Cloud Runのドメインマッピング を選択します。

その後、「確認済みドメインを選択する」に所有権を証明したカスタムドメインを入力するとAレコード、AAAAレコードが生成されてDNSレコードを登録すると完了となります。
カスタムドメインの所有権の証明ができていない場合は「確認済みドメインを選択する」に「Verify a new domain」を選択し、「検証するベースドメイン」にカスタムドメインを入力するとsearch consoleで所有権の確認ができtxtレコードが発行されるのでDNSレコードに登録すると「確認済みドメインを選択する」にカスタムドメインが選べるようになります。
コンソールか Google CloudでCloud Runのプレビュー機能のドメインマッピングを設定する際は以下の公式ドキュメントを参照してください。

Terraformで両方の構成に対応できる実装例

ここからが本題になります。TerraformでCloud Runのセットアップから上記の2つのカスタムドメインのマッピング方法を求めている環境に合わせて選択できるようにします。
構成ファイルは以下terraform.tfvarsファイルとmain.tfファイルを用意します。
terraform.tfvarsファイルではmain.tfファイルの変数を定義しています。環境ごとにterraform.tfvarsファイルの変数のみを変えるだけで環境を構成することができます。

# terraform.tfvars
project_id = "your_gcp_project_id" 
container_config = "serverコンテナのcontainer_configを入力" 
sub_domain = "サブドメインを入力" 
domain_mapping = "cloud run or loadbalancerを入力" 

domain_mapping変数で「cloud run」を入力した場合Cloud Runプレビュー機能のドメインマッピングを実行し、「loadbalancer」をグローバル外部アプリケーションロードバランサを作成されるようにします。ロードバランサの場合はCloud Runをマルチリージョン構成で作成します。
また、sub_domain変数がない場合はdomain_mapping変数でどちらを入力してもドメインマッピングは#実行されずシングルリージョンのみCloud Runを作成されるようにします。
container_configはserverGTMの管理>>コンテナの設定>>タグ設定サーバーからcontainer_configを取得できます。

main.tfファイルではterrffrom.tfvarsから受け取った変数を定義しています。domain_mapping変数で「cloudrun」か「loadblancer」が入力されていなかった場合はエラーメッセージを出すような仕様にしてあります。

# main.tf
variable "project_id" {
  type = string
}

variable "container_config" {
  type = string
}

variable "sub_domain" {
  type    = string
  default = ""
}

variable "domain_mapping" {
  type = string

  validation {
    condition     = contains(["cloudrun", "loadbalancer"], var.domain_mapping)
    error_message = "domain_mapping must be either 'cloudrun' or 'loadbalancer'."
  }
}

resource "google_cloud_run_v2_service" "server_side_tagging_preview" {
  name     = "server-side-tagging-preview"
  location = "asia-northeast1"
  ingress  = "INGRESS_TRAFFIC_ALL"

  template {
      containers {
        image = "gcr.io/cloud-tagging-10302018/gtm-cloud-image:stable"
      resources {
        limits   = {
            cpu    = "1000m"
            memory = "512Mi"
          }
        cpu_idle = true
        }        
      env {
          name  = "RUN_AS_PREVIEW_SERVER"
          value = "true"
        }
      env {
          name  = "CONTAINER_CONFIG"
          value = var.container_config
        }
      }
      timeout                          = "60s"
      max_instance_request_concurrency = 80 
      scaling {
        min_instance_count = 0
        max_instance_count = 10
       }
  }

  traffic {
    type    = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
    percent = 100
  }
}

resource "google_cloud_run_v2_service" "server_side_tagging" {
  name     = "server-side-tagging"
  location = "asia-northeast1"
  ingress  = "INGRESS_TRAFFIC_ALL"

  template {
      containers {
        image = "gcr.io/cloud-tagging-10302018/gtm-cloud-image:stable"        
      resources {
        limits   = {
            cpu    = "1000m"
            memory = "512Mi"
          }
        cpu_idle = true
        }
      env {
          name  = "PREVIEW_SERVER_URL"
          value = google_cloud_run_v2_service.server_side_tagging_preview.uri
        }
      env {
          name  = "CONTAINER_CONFIG"
          value = var.container_config
        }
      }
      timeout                          = "60s"
      max_instance_request_concurrency = 80 
      scaling {
        min_instance_count = 1
        max_instance_count = 10
       }
  }

  traffic {
    type    = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
    percent = 100
  }
  depends_on = [google_cloud_run_v2_service.server_side_tagging_preview]
}

resource "google_cloud_run_service_iam_binding" "server_side_tagging_preview" {
  location = google_cloud_run_v2_service.server_side_tagging_preview.location
  service  = google_cloud_run_v2_service.server_side_tagging_preview.name
  role     = "roles/run.invoker"
  members  = ["allUsers"]
}

resource "google_cloud_run_service_iam_binding" "server_side_tagging_main" {
  location = google_cloud_run_v2_service.server_side_tagging.location
  service  = google_cloud_run_v2_service.server_side_tagging.name
  role     = "roles/run.invoker"
  members  = ["allUsers"]
}

resource "google_cloud_run_domain_mapping" "server_side_tagging" { 
  count    = var.domain_mapping == "cloudrun" && var.sub_domain != "" ? 1 : 0
  name     = var.sub_domain
  location = google_cloud_run_v2_service.server_side_tagging.location

  metadata{
     namespace = var.project_id
     }

  spec {
     route_name = google_cloud_run_v2_service.server_side_tagging.name
    }
 }


resource "google_cloud_run_v2_service" "server_side_tagging_sub" {
  count    = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  name     = "server-side-tagging"
  location = "asia-northeast2"
  ingress  = "INGRESS_TRAFFIC_ALL"

  template {
      containers {
        image = "gcr.io/cloud-tagging-10302018/gtm-cloud-image:stable"        
      resources {
        limits     = {
            cpu    = "1000m"
            memory = "512Mi"
          }
          cpu_idle = true
        }
      env {
          name  = "PREVIEW_SERVER_URL"
          value = google_cloud_run_v2_service.server_side_tagging_preview.uri
        }
      env {
          name  = "CONTAINER_CONFIG"
          value = var.container_config
        }
      }
      timeout                          = "60s"
      max_instance_request_concurrency = 80 
      scaling {
        min_instance_count = 1
        max_instance_count = 10
       }
  }

  traffic {
    type    = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
    percent = 100
  }
  depends_on = [google_cloud_run_v2_service.server_side_tagging_preview]
}

resource "google_cloud_run_service_iam_binding" "server_side_tagging_sub" {
  count    = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  location = google_cloud_run_v2_service.server_side_tagging_sub[count.index].location
  service  = google_cloud_run_v2_service.server_side_tagging_sub[count.index].name
  role     = "roles/run.invoker"
  members  = ["allUsers"]
}

resource "google_compute_region_network_endpoint_group" "server_side_tagging_neg_main" {
  count                 = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  name                  = "server-side-tagging-neg"
  network_endpoint_type = "SERVERLESS"
  region                = "asia-northeast1"

  cloud_run {
    service = google_cloud_run_v2_service.server_side_tagging.name
  }
}

resource "google_compute_region_network_endpoint_group" "server_side_tagging_neg_sub" {
  count                 = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0 
  name                  = "server-side-tagging-neg"
  network_endpoint_type = "SERVERLESS"
  region                = "asia-northeast2"

  cloud_run {
    service = google_cloud_run_v2_service.server_side_tagging_sub[count.index].name
  }
}

resource "google_compute_backend_service" "server_side_tagging_backend" {
  count                 = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0 
  name                  = "server-side-tagging-backend"
  load_balancing_scheme = "EXTERNAL_MANAGED"

  backend {
    group = google_compute_region_network_endpoint_group.server_side_tagging_neg_main[count.index].id
  }

  backend {
    group = google_compute_region_network_endpoint_group.server_side_tagging_neg_sub[count.index].id
  }
}

resource "google_compute_global_address" "ip_address" {
  count      = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  name       = "server-side-tagging-ip-address"
  ip_version = "IPV4"
}

resource "google_compute_url_map" "urlmap" {
  count           = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  name            = "server-side-tagging-urlmap"
  default_service = google_compute_backend_service.server_side_tagging_backend[count.index].id
}

resource "google_compute_managed_ssl_certificate" "ssl_certificate" {
  count = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  name  = "server-side-tagging-cert"
  
  managed {
    domains = [var.sub_domain]
  }
}

resource "google_compute_target_https_proxy" "https_proxy" {
  count                       = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  name                        = "server-side-tagging-https-proxy"
  http_keep_alive_timeout_sec = 610
  url_map                     = google_compute_url_map.urlmap[count.index].id
  ssl_certificates            = [google_compute_managed_ssl_certificate.ssl_certificate[count.index].id]
}

resource "google_compute_global_forwarding_rule" "forwarding_rule" {
  count                 = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  name                  = "server-side-tagging-forwarding-rule"
  ip_protocol           = "TCP"
  load_balancing_scheme = "EXTERNAL_MANAGED"
  port_range            = "443"
  target                = google_compute_target_https_proxy.https_proxy[count.index].id
  ip_address            = google_compute_global_address.ip_address[count.index].address
}

ブロックごとに説明をしていきます。

Cloud Runのサーバーサイドタグ設定

公式ドキュメント通りにプレビューサーバーとタグ設定用サーバーを作成します。container_configはseverGTMから取得してください。
注意点としてはサーバーサイドタグ設定の場合はCloud Runの認証を「未認証を許可」と設定する必要はありますがgoogle_cloud_run_v2_serviceリソースには認証設定をコントロールするパラメータはありません。
Cloud Runの認証設定を変更するにはgoogle_cloud_run_service_iam_bindingリソースを使用し"roles/run.invoker"ロールをallusersとする必要があります。

resource "google_cloud_run_v2_service" "server_side_tagging_preview" {
  name     = "server-side-tagging-preview"
  location = "asia-northeast1"
  ingress  = "INGRESS_TRAFFIC_ALL"

  template {
      containers {
        image = "gcr.io/cloud-tagging-10302018/gtm-cloud-image:stable"
      resources {
        limits   = {
            cpu    = "1000m"
            memory = "512Mi"
          }
        cpu_idle = true
        }        
      env {
          name  = "RUN_AS_PREVIEW_SERVER"
          value = "true"
        }
      env {
          name  = "CONTAINER_CONFIG"
          value = var.container_config
        }
      }
      timeout                          = "60s"
      max_instance_request_concurrency = 80 
      scaling {
        min_instance_count = 1
        max_instance_count = 10
       }
  }

  traffic {
    type    = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
    percent = 100
  }
}

resource "google_cloud_run_v2_service" "server_side_tagging" {
  name     = "server-side-tagging"
  location = "asia-northeast1"
  ingress  = "INGRESS_TRAFFIC_ALL"

  template {
      containers {
        image = "gcr.io/cloud-tagging-10302018/gtm-cloud-image:stable"        
      resources {
        limits   = {
            cpu    = "1000m"
            memory = "512Mi"
          }
        cpu_idle = true
        }
      env {
          name  = "PREVIEW_SERVER_URL"
          value = google_cloud_run_v2_service.server_side_tagging_preview.uri
        }
      env {
          name  = "CONTAINER_CONFIG"
          value = var.container_config
        }
      }
      timeout                          = "60s"
      max_instance_request_concurrency = 80 
      scaling {
        min_instance_count = 1
        max_instance_count = 10
       }
  }

  traffic {
    type    = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
    percent = 100
  }
  depends_on = [google_cloud_run_v2_service.server_side_tagging_preview]
}

resource "google_cloud_run_service_iam_binding" "server_side_tagging_preview" {
  location = google_cloud_run_v2_service.server_side_tagging_preview.location
  service  = google_cloud_run_v2_service.server_side_tagging_preview.name
  role     = "roles/run.invoker"
  members  = ["allUsers"]
}

resource "google_cloud_run_service_iam_binding" "server_side_tagging_main" {
  location = google_cloud_run_v2_service.server_side_tagging.location
  service  = google_cloud_run_v2_service.server_side_tagging.name
  role     = "roles/run.invoker"
  members  = ["allUsers"]
}

Cloud Runプレビュー機能のドメインマッピング

countを使用して条件分岐をしています。domain_mapping変数が"cloudrun"かつsub_domain変数が入力されている場合にgoogle_cloud_run_domain_mappingリソースが実行されるようになっています。
Specのパラメータでタグ設定用サーバーのCloud Runを指定してドメインマッピングしてください。

resource "google_cloud_run_domain_mapping" "server_side_tagging" { 
  count    = var.domain_mapping == "cloudrun" && var.sub_domain != "" ? 1 : 0
  name     = var.sub_domain
  location = google_cloud_run_v2_service.server_side_tagging.location

  metadata{
     namespace = var.project_id
     }

  spec {
     route_name = google_cloud_run_v2_service.server_side_tagging.name
    }
 }

グローバル外部アプリケーションロードバランサCloud Runマルチリージョン構成

こちらもcountを使って条件分岐をしており、domain_mapping変数が"loadbalancer"かつsub_domain変数が入力されている場合にリソースが実行されます。ロードバランサはCloud Runをマルチリージョン構成にでき複数のリージョンにトラフィックを分散できます。
作成したCloud Runのリージョンごとにバックエンドグループを作成しバックエンドで紐づけています。
その後は、ipaddressを作成し、指定のバックエンドにルーティング設定するurlmap、SSL 証明書リソースのssl_certificate、URL マップにリクエストを転送するターゲット https_proxy、受信リクエストをプロキシに転送する転送ルールのforwarding_ruleを作成して完成となります。

resource "google_cloud_run_v2_service" "server_side_tagging_sub" {
  count    = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  name     = "server-side-tagging"
  location = "asia-northeast2"
  ingress  = "INGRESS_TRAFFIC_ALL"

  template {
      containers {
        image = "gcr.io/cloud-tagging-10302018/gtm-cloud-image:stable"        
      resources {
        limits     = {
            cpu    = "1000m"
            memory = "512Mi"
          }
          cpu_idle = true
        }
      env {
          name  = "PREVIEW_SERVER_URL"
          value = google_cloud_run_v2_service.server_side_tagging_preview.uri
        }
      env {
          name  = "CONTAINER_CONFIG"
          value = var.container_config
        }
      }
      timeout                          = "60s"
      max_instance_request_concurrency = 80 
      scaling {
        min_instance_count = 1
        max_instance_count = 10
       }
  }

  traffic {
    type    = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
    percent = 100
  }
  depends_on = [google_cloud_run_v2_service.server_side_tagging_preview]
}

resource "google_cloud_run_service_iam_binding" "server_side_tagging_sub" {
  count    = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  location = google_cloud_run_v2_service.server_side_tagging_sub[count.index].location
  service  = google_cloud_run_v2_service.server_side_tagging_sub[count.index].name
  role     = "roles/run.invoker"
  members  = ["allUsers"]
}

resource "google_compute_region_network_endpoint_group" "server_side_tagging_neg_main" {
  count                 = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  name                  = "server-side-tagging-neg"
  network_endpoint_type = "SERVERLESS"
  region                = "asia-northeast1"

  cloud_run {
    service = google_cloud_run_v2_service.server_side_tagging.name
  }
}

resource "google_compute_region_network_endpoint_group" "server_side_tagging_neg_sub" {
  count                 = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0 
  name                  = "server-side-tagging-neg"
  network_endpoint_type = "SERVERLESS"
  region                = "asia-northeast2"

  cloud_run {
    service = google_cloud_run_v2_service.server_side_tagging_sub[count.index].name
  }
}

resource "google_compute_backend_service" "server_side_tagging_backend" {
  count                 = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0 
  name                  = "server-side-tagging-backend"
  load_balancing_scheme = "EXTERNAL_MANAGED"

  backend {
    group = google_compute_region_network_endpoint_group.server_side_tagging_neg_main[count.index].id
  }

  backend {
    group = google_compute_region_network_endpoint_group.server_side_tagging_neg_sub[count.index].id
  }
}

resource "google_compute_global_address" "ip_address" {
  count      = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  name       = "server-side-tagging-ip-address"
  ip_version = "IPV4"
}

resource "google_compute_url_map" "urlmap" {
  count           = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  name            = "server-side-tagging-urlmap"
  default_service = google_compute_backend_service.server_side_tagging_backend[count.index].id
}

resource "google_compute_managed_ssl_certificate" "ssl_certificate" {
  count = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  name  = "server-side-tagging-cert"
  
  managed {
    domains = [var.sub_domain]
  }
}

resource "google_compute_target_https_proxy" "https_proxy" {
  count                       = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  name                        = "server-side-tagging-https-proxy"
  http_keep_alive_timeout_sec = 610
  url_map                     = google_compute_url_map.urlmap[count.index].id
  ssl_certificates            = [google_compute_managed_ssl_certificate.ssl_certificate[count.index].id]
}

resource "google_compute_global_forwarding_rule" "forwarding_rule" {
  count                 = var.domain_mapping == "loadbalancer" && var.sub_domain != "" ? 1 : 0
  name                  = "server-side-tagging-forwarding-rule"
  ip_protocol           = "TCP"
  load_balancing_scheme = "EXTERNAL_MANAGED"
  port_range            = "443"
  target                = google_compute_target_https_proxy.https_proxy[count.index].id
  ip_address            = google_compute_global_address.ip_address[count.index].address
}

それぞれのリソースの詳細なパラメータに関しては以下のTerraformの公式リファレンスを参照してください。