🏃

Cloud Run + Cloud Build + TerraformでCI/CDビルドに対応したFastAPI HTTPサーバを立てる

2022/10/17に公開

Cloud Run + Cloud Buildを使ったインフラで動くFastAPI HTTPサーバを自動構築できるよう、Terraformを使って構築する方法を調査しました。

全体フロー

Cloud RunをTerraformで構築する全体フロー

サンプルコード

実装したコードを以下に保存していますので、ご参考ください。
https://github.com/Niccari/cloud_run_http_server_terraform_example

HTTPサーバ

以下の形で、FastAPIを使って疎通確認用のAPIのみ定義しておきます。

main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def root_api():
    return {"status": "ok"}

Terraform

以下はterraform上では管理しません。GCPのコンソールから別途定義しています。

  • terraform操作用のサービスアカウントおよび、そのIAM
    • 当該サービスアカウントについて、以下IAMを付与しています。
      • セキュリティ管理者: IAMポリシーを取得・設定できるようにする
      • 編集者: リソースを取得・変更できるようにする

API有効化

今回のケースにおいて、サービスごとに以下APIが必要なので設定します。

  • Cloud Build
    • cloudbuild.googleapis.com
    • cloudresourcemanager.googleapis.com: GCPのプロジェクト・リソース情報を取得するために必要
  • Cloud Run
    • run.googleapis.com
    • containerregistry.googleapis.com: Cloud Run向けのDockerイメージの格納、参照に必要
module/api/main.tf
variable "gcp_service_list" {
  type = list(string)
  default = [
    "cloudbuild.googleapis.com",
    "containerregistry.googleapis.com",
    "run.googleapis.com",
    "cloudresourcemanager.googleapis.com"
  ]
}

resource "google_project_service" "activate_gcp_services" {
  for_each = toset(var.gcp_service_list)
  service  = each.key
}

Cloud Build

以下の2点を行います。

  • Cloud Runデプロイ用に、Cloud Buildサービスアカウントに対しIAM付与
    • Cloud Run デベロッパー
    • サービス アカウント ユーザー
  • Cloud Buildトリガーを定義
module/cloudbuild/main.tf
variable "project_id" {
  description = "gcp project id"
  type = number
}

resource "google_project_iam_member" "cloudbuild_iam" {
  for_each = toset([
    "roles/run.developer",
    "roles/iam.serviceAccountUser"
  ])
  role    = each.key
  member  = "serviceAccount:${var.project_id}@cloudbuild.gserviceaccount.com"
  project = var.project_id
}

resource "google_cloudbuild_trigger" "cloudbuild_sample_api" {
  location = "us-central1"
  name     = "cloudbuild-sample-api"
  filename = "cloudbuild.yaml"

  github {
    owner = "Niccari"
    name  = "cloud_run_http_server_terraform_example"
    push {
      branch = "^main$"
    }
  }
  included_files = [
    "**/*.py",
    "Dockerfile",
    "Pipfile*",
    "requirements.txt",
  ]
}

Cloud Run

以下2点を設定します。

  • デプロイパラメータの設定
  • tfstateで変更を追わないパラメータの設定

デプロイパラメータの設定

以下の通り設定しています。

  • autogenerate_revision_name: デプロイするとき自動的にリビジョン名を付与します。falseの場合、template.metadata.nameにてリビジョン名を都度指定する必要があります[1]

  • template.spec.timeout_seconds: リクエストのタイムアウト時間[秒]

  • template.spec.container_concurrency: 最大同時実行コンテナ数

  • template.spec.containers.resources.limits["memory"]: 1コンテナインスタンスあたりの最大メモリ量

  • template.spec.containers.resources.limits["cpu"]: 1コンテナインスタンスあたりのvCPU数

  • template.metadata.annotations["autoscaling.knative.dev/minScale"]: コンテナインスタンスの最小数

  • template.metadata.annotations["autoscaling.knative.dev/maxScale"]: コンテナインスタンスの最大数

module/cloudrun/main.tf
variable "project_name" {
  description = "gcp project name"
  type = string
}

resource "google_cloud_run_service" "configure_cloud_run_service" {
  name                       = "sample-api"
  location                   = "us-central1"
  autogenerate_revision_name = true # 自動でリビジョン末尾の識別文字列を入れるために必要

  template {
    spec {
      timeout_seconds       = 300
      container_concurrency = 50
      containers {
        image = "us.gcr.io/${var.project_name}/sample-api" # すでにimageがアップロード済みでないといけない
        resources {
          limits = {
            "memory" : "256Mi",
            "cpu" : "1"
          }
        }
      }
    }
    metadata {
      annotations = {
        "autoscaling.knative.dev/minScale" = "0"
        "autoscaling.knative.dev/maxScale" = "5"
      }
    }
  }
}

tfstateで変更を追わないパラメータを設定する

Cloud Buildないし手動デプロイ時、以下のパラメータが自動更新されます。そのため、これらパラメータに差分があったときにgoogle_cloud_run_serviceがTerraformのapply対象になってしまいます。これらパラメータをtfstateと同期させる必要はないので、tfstateの監視対象から外します[2]

  • template.metadata.annotations["client.knative.dev/user-image"]
  • template.metadata.annotations["run.googleapis.com/client-name"]
  • template.metadata.annotations["run.googleapis.com/client-version"]
  • template.metadata.labels
  • template.spec.containers["image"]
module/cloudrun/main.tf
resource "google_cloud_run_service" "configure_cloud_run_service" {
  # ...省略...

  lifecycle {
    ignore_changes = [
      # gcloudからデプロイしたとき、以下のパラメータが入る。変更差分として判定したくないのでチェック対象から外す
      template[0].metadata[0].annotations["client.knative.dev/user-image"],
      template[0].metadata[0].annotations["run.googleapis.com/client-name"],
      template[0].metadata[0].annotations["run.googleapis.com/client-version"],
      # Clour Buildからビルド・デプロイしたとき、以下のパラメータが入る。変更差分として判定したくないのでチェック対象から外す
      template[0].metadata[0].labels,
      template[0].spec[0].containers["image"]
    ]
  }
}

(参考): 全体処理

サービスアカウントの秘密鍵を使うのは推奨されていないため、terraform操作用のサービスアカウントの権限借用(impersonate)をしています[3]

main.tf
terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = ">= 4.40.0"
    }
  }
  required_version = ">= 1.3"
  backend "gcs" {}
}

variable "project_name" {
  type = string
}

variable "project_id" {
  type = number
}

provider "google" {
  project                     = var.project_name
  region                      = "us-central1"
  impersonate_service_account = "terraform@${var.project_name}.iam.gserviceaccount.com"
}

module "api" {
  source = "./module/api"
}

module "cloudbuild" {
  source     = "./module/cloudbuild"
  depends_on = [module.api]
  project_id = var.project_id
}

module "cloudrun" {
  source       = "./module/cloudrun"
  depends_on   = [module.api]
  project_name = var.project_name
}
脚注
  1. https://techblog.zozo.com/entry/sre-work-for-cloudrun ↩︎

  2. ほか、https://gitlab.developers.cam.ac.uk/uis/devops/infra/terraform/gcp-cloud-run-app/-/blob/22-secrets-IAM/main.tf に記載のignore_changesを参照すると良いかも知れません。 ↩︎

  3. https://scrapbox.io/pokutuna/GCP_Service_Account_キーを使わない_Terraform ↩︎

Discussion