Google Cloudで簡単なCloud Runのアーキテクチャを設計する

# はじめに
こんにちはmasa-asaです。今回はクラウドプラットフォームの一つであるGoogle Cloudで、Cloud Runを用いるための簡単なアーキテクチャを実現する方法について述べたいと思います。本記事の概要は以下です。

- Cloud Runを作成する
- Load Balancer作成し、Cloud Runと連携する
- Cloud Armorによりファイアウォールルールを作成してアクセス制限をする
- リソースはIaCで構築。terraformを用いる

ざっくりとした構成は以下の図のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942439/c849f43c-5d51-bd91-0f8e-b811293e92f7.png)


また、本記事では参考リンクが多くなりますので、末尾の参考資料ではなく、各リソースの説明の最後にリンクを参考として記載します。ご了承お願いいたします。

https://cloud.google.com/load-balancing/docs/application-load-balancer?hl=ja#global-external

# terraformとは
HashiCorpが開発するIaCを実現するためのツールです。
大手のクラウドサービスに対応しており、AWS,Azure,Google Cloudのいずれにおいてもリソース管理を行うことができます。基本的なコマンドの簡単な説明は以下です。

- terraformの初期化：`terraform init`
- terraformの実行による差分の確認：`terraform plan`
- terraformの実行：`terraform apply`
- terraformで作成したリソースの破棄`terraform destroy`


https://registry.terraform.io/providers/hashicorp/google/latest/docs

https://developer.hashicorp.com/terraform/tutorials/gcp-get-started/infrastructure-as-code

# Cloud Runを作成する
terraformで以下のように作成します。
`image`にCloud Runにデプロイするイメージを指定します。今回はGoogleが用意しているサンプルのイメージを用います。また、認証が不要になるようなiamの設定を入れ込んでいます。

```terraform
resource "google_cloud_run_v2_service" "default" {
    name     = "crun-sandbox-asa"
    location = "asia-northeast1"
    deletion_protection = false
    ingress = "INGRESS_TRAFFIC_ALL"

    template {
        max_instance_request_concurrency = 20
        scaling {
        max_instance_count = 1
        }
        containers {
        image = "us-docker.pkg.dev/cloudrun/container/hello"
        resources {
            limits = {
              cpu = "1"
              memory = "512Mi"
                }
            }
        }
    }
}


data "google_iam_policy" "noauth" {
  binding {
    role = "roles/run.invoker"
    members = [
      "allUsers",
    ]
  }
}

resource "google_cloud_run_service_iam_policy" "noauth" {
  location    = google_cloud_run_v2_service.default.location
  project     = google_cloud_run_v2_service.default.project
  service     = google_cloud_run_v2_service.default.name

  policy_data = data.google_iam_policy.noauth.policy_data
}

output "clrun_sandbox_asa_name" {
  value = google_cloud_run_v2_service.default.name
}
```

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service



# ロードバランサを作成する
本記事のキモになる部分です。Google Cloud のコンソールでは、ロードバランサは専用のGUIで数ステップに分けて作成していきます。ところが、terraformでは、VPCやCloud Runのようにロードバランサ自体のリソースは存在しません。

下図はGoogle Cloud のドキュメントから抜粋したグローバルアプリケーションロードバランサの構成です。terraformでロードバランサを実現するには、これらの構成を再現するように記述する必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942439/ee7ba04a-3d81-aafb-ac0e-52f832f53709.png)

列挙すると以下のようなリソースを作成する必要があります。
- Serverless NEG
- Cloud Armor Policy
- Backend Service
- URL Map
- SSL 証明書の設定
- SSL Policy
- Target Proxy
- Compute Address
- Forwarding Rule

次から上で列挙したリソースを作成していきます。

### Serverless NEGの作成
Sereverless NEGの作成です。Cloud Runを指定するので、変数としてCloud Runの名前を指定しています。

```terraform

## Sereverless NEG。接続するCloud Runのサービスを指定する。
resource "google_compute_region_network_endpoint_group" "cloudrun_neg" {
  name                  = "neg-sandbox-asa"
  network_endpoint_type = "SERVERLESS"
  region                = "asia-northeast1"
  cloud_run {
    service = var.cloud_run_service_name
  }
}


output "serverless_neg_self_link" {
  value = google_compute_region_network_endpoint_group.cloudrun_neg.self_link
  
}

```

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_region_network_endpoint_group


### Cloud Armorの作成
以下の例のように、Coud Armorのポリシーとポリシーに含まれるルールを作成します。
必要に応じて、`google_compute_security_policy_rule`でルールを作成し、`action`で許可・拒否を決めていきます。

```terraform
# Cloud Armorの設定
resource "google_compute_security_policy" "region-sec-policy-basic" {
  name        = "my-sec-policy-basic"
  description = "basic region security policy"
  type        = "CLOUD_ARMOR"
  provider    = google-beta 
}

resource "google_compute_security_policy_rule" "rule_default" {
  security_policy = google_compute_security_policy.region-sec-policy-basic.name
  priority        = 2147483646
  action          = "deny(403)"
  description     = "default deny"
  provider    = google-beta 

  match {
    versioned_expr= "SRC_IPS_V1"
    config {
      src_ip_ranges = ["*"]
    }
  }
}

resource "google_compute_security_policy_rule" "rule_one" {
  security_policy = google_compute_security_policy.region-sec-policy-basic.name
  priority        = 100
  action          = "allow"
  description     = "in company proxy 1"
  provider    = google-beta 

  match {
    versioned_expr= "SRC_IPS_V1"
    config {
      src_ip_ranges = ["xxx.xxx.xxx.xxx/xx"]
    }
  }
}

output "cloud_armor_self_link" {
  value = google_compute_security_policy.region-sec-policy-basic.self_link
}
```

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_security_policy

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_security_policy_rule

### Backend Serviceの作成
Backend Serviceの作成を行います。ここで、先ほど作成したSereverless NEGとCloud Armor Policyを利用します。
具体的には、`security_policy`には、Cloud Armorの作成で作ったセキュリティポリシーを指定します。
また、`backend`の`group`に作成したSereverless NEGを指定します。

```terraform
# バックエンドサービスの作成

resource "google_compute_backend_service" "backend_service_sandbox_asa" {
  description           = "Cloud Runに対するバックエンドサービス"
  load_balancing_scheme = "EXTERNAL_MANAGED" # リージョン外部アプリケーションロードバランサの場合　https://cloud.google.com/load-balancing/docs/backend-service?hl=ja
  name                  = "bes-sandbox-asa"
  provider = google-beta
  port_name             = "http"
  protocol              = "HTTPS"
  session_affinity      = "NONE"
  security_policy       = var.cloud_armor_self_link
  timeout_sec           = 30
  backend {
    group               = var.serverless_neg_self_link
    capacity_scaler     = 1.0
    balancing_mode      = "UTILIZATION"
  }

  log_config {
    enable      = true
    sample_rate = 1.0
  }
}


output "backend_service_id" {
  value = google_compute_backend_service.backend_service_sandbox_asa.id
  
}


```

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_backend_service

### URL Mapの作成
URL Mapの作成を行います。今回URL Mapは2種類作成します。それぞれ、httpsの受信を扱うものと、httpの通信をhttpsにリダイレクトするためのものです。

URL Mapが紐づくデフォルトサービスに、上で作成したBackend Serviceのidを指定しています。直下の例がhttpsの場合のコードです。
```terraform
# URL Map https
resource "google_compute_url_map" "url_map_sandbox_https_asa" {
  name            = "url-map-https-sandbox-asa"
  default_service = var.backend_service_id
}

output "https_url_map_id" {
  value = google_compute_url_map.url_map_sandbox_https_asa.id
}

```

下の例ががhttpをhttpsにリダイレクトするためのURL Mapです。
`https_redirect`を`true`にすることで、リダイレクトできます。`strip_query`を`true`にすると、元のURLにあったクエリがリダイレクト時に削除されます。ここでは`false`(デフォルト値)に設定しています。

```terraform
# URL Map http→httpsリダイレクト用
resource "google_compute_url_map" "url_map_sandbox_asa" {
  default_url_redirect {
    https_redirect         = true
    redirect_response_code = "MOVED_PERMANENTLY_DEFAULT"
    strip_query            = false
  }

  name    = "url-map-http-sandbox-asa"
}

output "http_url_map_id" {
  value = google_compute_url_map.url_map_sandbox_asa.id
}

```

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_url_map

### SSL証明書をロードバランサに適用する設定
後で作成するhttps用のTarget Proxyに用いるためのSSL証明書の設定を作成します。
`certificatet`と`private_key`にそれぞれPEM形式の証明書とPEM形式の秘密鍵を指定します。
今回は実用のシステムではないので、オレオレ証明書のファイルを指定しています。PEM形式の証明書・秘密鍵をべた書きすることも可能ですが、実際の利用ではSecret Managerに保存した証明書・秘密鍵を用いることになるかと思います。

```terraform
# httpsのフォワーディングルールに適応するためのssl証明書 secret managerから取得する
resource "google_compute_ssl_certificate" "ssl_cert_sandbox_asa" {
  certificate = file("./modules/load-balancer/ssl-certificate/oreore_cert/server.crt")
  description = "sandbox-asa"
  name_prefix = "sandbox-ssl"
  private_key = file("./modules/load-balancer/ssl-certificate/oreore_cert/server.key")

  lifecycle {
    create_before_destroy = true
  }
}

output "ssl_cert_sandbox_asa_id" {
  value = google_compute_ssl_certificate.ssl_cert_sandbox_asa.id
}

```

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_ssl_certificate

### SSL Policyの作成
後で作成するhttps用のTarget Proxyに用いるためにSSL Policyを作成します。
`profile`はGoogleが事前定義したものに加えてカスタムしたものも使用可能で、要件の厳しさに応じて設定可能です。

```terraform
resource "google_compute_ssl_policy" "ssl_policy_sandbox_asa" {
  name            = "ssl-policy-sandbox-asa"
  profile         = "MODERN"
  min_tls_version = "TLS_1_2"
}

output "ssl_policy_self_link" {
  value = google_compute_ssl_policy.ssl_policy_sandbox_asa.self_link
}

```

https://cloud.google.com/load-balancing/docs/ssl-policies-concepts?hl=ja#profilefeaturesupport


https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_ssl_policy

### Target Proxyの作成
Target Proxyの作成でもhttpとhttpsの場合の２種類を作ります。Target Proxyはhttpとhttpsの場合でリソース名が異なっていることに注意が必要です。
`url_map`には、上で作成したURL Mapのidを指定しています。
```terraform
# httpの場合
resource "google_compute_target_http_proxy" "default" {
  name             = "target-proxy-http"
  url_map          = var.http_url_map_id
}

output "http_target_proxy_id" {
  value = google_compute_target_http_proxy.default.id
  
}

```

httpsの場合、 SSL通信を行うための証明書の設定とSSL Policyの設定が必要です。それぞれ、前述の内容で作成したリソースを指定しています。
```terraform
# httpsの場合
resource "google_compute_target_https_proxy" "default" {
  name             = "target-proxy-https"
  url_map          = var.https_url_map_id
  ssl_certificates = [var.ssl_id]
  ssl_policy       = var.ssl_policy_self_link
}

output "https_target_proxy_id" {
  value = google_compute_target_https_proxy.default.id
}

```

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_target_http_proxy

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_target_https_proxy

### グローバルIPアドレス（Compute Address）の作成
ロードバランサに設定するグローバルIPアドレスの作成（取得）を行います。以下で、静的なグローバルIPアドレスを1つ取得します。`address`を指定していないので、IPアドレスの値はGoogle Cloudが持つアドレスから自動で割り当てられたものになります。

```terraform
resource "google_compute_global_address" "lb_ip" {
  address_type = "EXTERNAL"
  description  = "LBに付与する静的IP"
  name         = "lb-static-ip-sandbox-asa"
}

output "lb_ip_id" {
  value = google_compute_global_address.lb_ip.id
}
```

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_global_address



### Forwarding Ruleの作成
Forwarding Ruleもhttpとhttpsの場合の２種類を作成します。http通信とhttps通信のためにそれぞれポート80と443を指定しています。

また、Forwarding Rule作成の際にIPアドレスの指定と、Target Proxyの設定をしています。
これらは前の内容で取得したipアドレスと作成したTarget Proxyです。
`network_tier`に関しては、グローバル外部ALBの場合はPREMIUMにする必要があります。

```terraform
# フォワーディングルール（LBのフロントエンドhttp）
resource "google_compute_global_forwarding_rule" "http_forwarfing" {
  name                  = "test-http-forwarding-rule"
  provider              = google-beta
  ip_protocol           = "TCP"
  load_balancing_scheme = "EXTERNAL_MANAGED"
  port_range            = "80-80"
  target                = var.http_target_proxy_id
  ip_address            = var.lb_ip_id
  network_tier          = "PREMIUM"
}
```

```terraform
# フォワーディングルール（LBのフロントエンドhttps）
resource "google_compute_global_forwarding_rule" "https_forwarfing" {
  name                  = "test-https-forwarding-rule"
  provider              = google-beta
  ip_protocol           = "TCP"
  load_balancing_scheme = "EXTERNAL_MANAGED"
  port_range            = "443-443"
  target                = var.https_target_proxy_id
  ip_address            = var.lb_ip_id
  network_tier          = "PREMIUM"
}
```

https://cloud.google.com/network-tiers/docs/overview?hl=ja

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_global_forwarding_rule

# 作成したリソースにアクセスしてみる
`terraform init`,`terraform plan`,`terraform apply`コマンドを実行し、リソースを作成しました
。直下の図は作成されたCloud Runです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942439/3ab6b2e0-8015-0629-f3b4-8ac46c708eab.png)

Cloud RunのURLに直接アクセスすると、エラーになります。ロードバランサを経由しないアクセスを遮断していることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942439/590ff206-676c-1828-d9b5-28419e36f84e.png)

下の図はURL Mapです。httpsとhttp→httpsリダイレクト用の2つがあることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942439/ae84b77a-19d4-5c37-0465-86d75e257305.png)

httpsのURL Mapの詳細でIPアドレスが確認できます。このIPアドレスはterraformで作成（取得）したものです。
IPアドレスをに対してアクセスを行ってみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942439/f0ba4f91-c85f-f8eb-7ba0-c57b184a53be.png)

オレオレ証明書を用いているため、警告が表示されますが、アクセスできたことがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942439/452c1ec1-dc5a-0002-0e57-5d1af35ffdb2.png)

# 補講
### Providerやバージョンの設定
provider.tfなどで利用するproviderやterraformのバージョンを明示することができます。
例では、バージョン 1.6.0 以上の 1.x.x バージョンを使用することを指定しています。
`povider`では、googleを指定し、`projrct`には対象のGoogle Cloudのプロジェクトidを指定します。
これは必ずしもprovider.tfに記述する必要はなく、main.tfなどに記述することもできます。

```terraform
terraform {
  required_version = "~>1.6.0"
}

# providerとなるgoogle cloudプロジェクトを指定
provider "google" {
  project = "xxxxxxxxxxxx"
  region  = "asia-northeast1"
}
```

### VPCの作成
今回は必要ありませんが、VPCを作成するためのterraformを以下に紹介します。リージョン範囲のロードバランサを作成する場合には、VPCが必要になります。
```terraform
# VPC Network
resource "google_compute_network" "vpc_network" {
  name                    = "vpc-sandbox-asa"
  auto_create_subnetworks = false
  mtu                     = 1460
  routing_mode            = "GLOBAL"
}

output "vpc_network_id" {
  value = google_compute_network.vpc_network.id 
}

```

作成に成功すると、以下の画面のようにVPCができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942439/b4e6fc06-82a0-e037-17f8-07582787442d.png)


https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_network

###  Subnetの作成
リージョン範囲ロードバランサの場合はプロキシ専用サブネットが必要です。このためにVPCも必要になるというわけです。作成例は以下のようになります。
`purpose`を`REGIONAL_MANAGED_PROXY`に設定しています。
```terraform
# 外部ALBのためのEnvoyプロキシ専用サブネット
resource "google_compute_subnetwork" "proxy_only" {
  name          = "proxy-only-subnet"
  ip_cidr_range = "10.148.0.0/23" 
  network       = var.vpc_network_id
  purpose       = "REGIONAL_MANAGED_PROXY"
  region        = "asia-northeast1"
  role          = "ACTIVE"
  description   = "Subnet for the external ALB Envoy proxy" 
}

output "subnetwork" {
  value = google_compute_subnetwork.proxy_only
}
```

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_subnetwork

https://cloud.google.com/load-balancing/docs/proxy-only-subnets?hl=ja#envoy-lb



# 注意事項
cloud runの作成時に、認証が不要な設定をしていますが、'run.services.setIamPolicy'権限がない場合は403エラーになります。

terraformの例で変数を用いていますが、変数の扱い方についてはここでは記述していません。
変数の扱いについては以下のようなサイトが参考になりました。

https://book.st-hakky.com/hakky/how-to-use-terraform-variable/#:~:text=%E4%BE%8B%E3%81%88%E3%81%B0%E3%80%81%20variables.tf%20%E3%81%A8%E3%81%84%E3%81%86%E5%90%8D%E5%89%8D%E3%81%AE%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E4%BD%9C%E6%88%90%E3%81%97%E3%80%81%E3%81%9D%E3%81%AE%E4%B8%AD%E3%81%AB%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E5%A4%89%E6%95%B0%E3%82%92%E5%AE%A3%E8%A8%80%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%81%A7%E3%81%99%E3%80%82%20%E5%A4%89%E6%95%B0%E3%82%92%E5%AE%A3%E8%A8%80%E3%81%97%E3%81%9F%E5%BE%8C%E3%80%81%E3%81%9D%E3%82%8C%E3%82%89%E3%81%AE%E5%A4%89%E6%95%B0%E3%82%92%20Terraform,%E3%82%B3%E3%83%BC%E3%83%89%E5%86%85%E3%81%A7%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%E9%9A%9B%E3%81%AB%E3%81%AF%E3%80%81%E7%89%B9%E5%AE%9A%E3%81%AE%E6%96%B9%E6%B3%95%E3%81%A7%E5%8F%82%E7%85%A7%E3%81%99%E3%82%8B%E5%BF%85%E8%A6%81%E3%81%8C%E3%81%82%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82%20%E5%A4%89%E6%95%B0%E3%82%92%E6%AD%A3%E3%81%97%E3%81%8F%E5%8F%82%E7%85%A7%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%81%A7%E3%80%81%E8%A8%AD%E5%AE%9A%E5%80%A4%E3%82%84%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%82%92%E5%8B%95%E7%9A%84%E3%81%AB%E6%89%B1%E3%81%86%E3%81%93%E3%81%A8%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82%20%E5%A4%89%E6%95%B0%E3%82%92%E5%8F%82%E7%85%A7%E3%81%99%E3%82%8B%E5%9F%BA%E6%9C%AC%E7%9A%84%E3%81%AA%E6%96%B9%E6%B3%95%E3%81%AF%20var%20%E6%8E%A5%E9%A0%AD%E8%AA%9E%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%81%A7%E3%81%99%E3%80%82

https://developer.hashicorp.com/terraform/language/values/variables

# まとめ
今回の内容をまとめると以下になります。
- Cloud Runを作成する
- Load Balancer作成し、Cloud Runと連携する
- Cloud Armorによりファイアウォールルールを作成してアクセス制限をする
- リソースはIaCで構築。terraformを用いる

重要な点は、terraformでGoogle Cloudのロードバランサを作成する場合は、複数のリソースを組み合わせて作成し、最終的にロードバランサとして動作する、という点です！

以上になります。お読みいただきありがとうございました！