📝

Cloud Build で CD パイプラインを構築してみた

2024/07/19に公開

はじめに

Hogetic Lab でエンジニアをしている佐々木と申します。

現在のソフトウェア開発の環境では、新機能やアップデート、バグ修正を迅速かつ確実にエンドユーザーに届ける能力が重要です。Continuous Deployment(CD)は、このプロセスを自動化し、コードがリポジトリにプッシュされるたびに自動でデプロイを行う手法です。アプリケーションの信頼性を高めるためには必須の機能ではないでしょうか。
前回 「API テストを Scenarigo と GitHub Actions で自動化する」 の記事の続きになります。

それでは早速構築していきましょう!
今回は terraform を使用して作成しています。

概要

  1. ネットワーク環境及び DB の作成
    • VPC, Subnet, Private Service Access
  2. アプリケーション環境の構築
    • Artifact Registry, Serverless VPC Access, Cloud Run
  3. CD パイプラインの構築
    • Secret Manager, Cloud Build

ネットワーク環境及び DB の作成

resource "google_compute_network" "network" {
  name                    = "network"
  auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "subnet" {
  name          = "subnet"
  ip_cidr_range = "192.168.0.0/24"
  network       = google_compute_network.network.id

  depends_on = [google_compute_network.network]
}

// * 内部 IPv4 アドレス範囲を割り振る
// https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_global_address
resource "google_compute_global_address" "private_ip" {
  name          = "private-ip"
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  prefix_length = "16"
  network       = google_compute_network.network.id

  depends_on = [google_compute_network.network]
}

// * プライベート接続を作成する
// https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_networking_connection
resource "google_service_networking_connection" "connection" {
  network                 = google_compute_network.network.id
  service                 = "servicenetworking.googleapis.com"
  reserved_peering_ranges = [google_compute_global_address.private_ip.name]
  deletion_policy         = "ABANDON"

  depends_on = [google_compute_global_address.private_ip]
}

resource "google_sql_database_instance" "db_instance" {
  name             = "db-instance"
  database_version = "POSTGRES_13"
  region           = var.region

  settings {
    tier = "db-f1-micro"
    ip_configuration {
      ipv4_enabled    = false
      private_network = google_compute_network.network.id
    }
  }

  deletion_protection = false
  depends_on          = [google_service_networking_connection.connection]
}

アプリケーション環境の構築

resource "google_artifact_registry_repository" "repository" {
  repository_id = "repository"
  format        = "DOCKER"
  location      = var.region
}

// Serverless VPC Access Connector を作成する
// https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/vpc_access_connector
resource "google_vpc_access_connector" "connector" {
  name          = "serverless-vpc-connector"
  ip_cidr_range = "192.168.1.0/28"
  network       = google_compute_network.network.name

  depends_on = [google_compute_network.network]
}

resource "google_cloud_run_service" "service" {
  name     = "service"
  location = var.region

  template {
    spec {
      containers {
        image = "${var.region}-docker.pkg.dev/${var.project}/${google_artifact_registry_repository.repository.repository_id}/application:latest"

        env {
          name  = "DB_HOST"
          // Cloud SQL のプライベート IP アドレスを指定する
          value = google_sql_database_instance.db_instance.private_ip_address
        }
        ...
      }
    }

    metadata {
      annotations = {
        "run.googleapis.com/vpc-access-egress"    = "all-traffic"
        // 上で作成した VPC Access Connector を指定する
        "run.googleapis.com/vpc-access-connector" = google_vpc_access_connector.connector.name
      }
    }
  }

  traffic {
    percent         = 100
    latest_revision = true
  }
}

CD パイプラインの構築

  • 今回は Github Actions で CI を実行し、 main ブランチにマージされたタイミングでビルド, デプロイCloud Build Trigger が行う構成になっています。
  • 前提条件
    • Github と Cloud Build が統合されている。参考URL
    • Github Token を取得している
// Github Token を保存する Secret Manager を用意する
resource "google_secret_manager_secret" "sm" {
  secret_id = "github-token"

  replication {
    auto {}
  }
}

resource "google_secret_manager_secret_version" "smv" {
  secret      = google_secret_manager_secret.sm.id
  // 前提条件で取得している Github のトークン
  secret_data = var.github_token

  depends_on = [google_secret_manager_secret.sm]
}

data "google_iam_policy" "secretAccessor" {
  binding {
    role    = "roles/secretmanager.secretAccessor"
    members = ["serviceAccount:service-${var.project_id}@gcp-sa-cloudbuild.iam.gserviceaccount.com"]
  }
}

resource "google_secret_manager_secret_iam_policy" "policy" {
  secret_id   = google_secret_manager_secret.sm.secret_id
  policy_data = data.google_iam_policy.secretAccessor.policy_data

  depends_on = [google_secret_manager_secret_version.smv]
}

resource "google_cloudbuildv2_connection" "connection" {
  location = var.region
  name     = "connection"

  github_config {
    // Github のコンソールから Cloud Build の installation id を取得する。
    app_installation_id = var.installation_id
    authorizer_credential {
      // 上で作成した Github Token を保存した際の Secret Manager の バージョン ID を指定します。
      oauth_token_secret_version = google_secret_manager_secret_version.smv.id
    }
  }

  depends_on = [google_secret_manager_secret_iam_policy.policy]
}

resource "google_project_iam_member" "attach_permissions" {
  // https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloudbuild_trigger#example-usage---cloudbuild-trigger-service-account
  // 上記リンクを参考に今回は Cloud Build Trigger から Cloud Run をデプロイするので権限を付与しました。
  for_each = toset([
    "roles/run.admin",
    "roles/logging.logWriter",
    "roles/iam.serviceAccountUser",
  ])
  role    = each.key
  project = var.project
  member  = "serviceAccount:${var.project_id}@cloudbuild.gserviceaccount.com"
}

resource "google_cloudbuild_trigger" "trigger" {
  name = "trigger"

  // github の関連要素を指定
  github {
    owner = var.github_owner
    name  = var.github_repository
    push {
      branch = "^main$"
    }
  }

  filename   = "cloudbuild.yml"
  depends_on = [google_cloudbuildv2_connection.connection]
}

おまけ

  • cloudbuild.yml
    • 特筆すべきことはあまりないですが、 docker のイメージタグには commit ハッシュを利用しましょう!
steps:
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-f', 'docker/Dockerfile', '-t', 'asia-northeast1-docker.pkg.dev/${PROJECT_ID}/repository/application:${COMMIT_SHA}', '.']
- name: 'gcr.io/cloud-builders/docker'
  args: ['push', 'asia-northeast1-docker.pkg.dev/${PROJECT_ID}/repository/application:${COMMIT_SHA}']
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
  entrypoint: gcloud
  args:
  - 'run'
  - 'deploy'
  - 'service'
  - '--image'
  - 'asia-northeast1-docker.pkg.dev/${PROJECT_ID}/repository/application:${COMMIT_SHA}'
  - '--region'
  - 'asia-northeast1'

まとめ

  • 今回初めて Google Cloud で CI/CD を組んでみましたが、Cloud Build Trigger の構築は非常にわかりやすく、シンプルだと感じました。
  • また Cloud Run は最大同時実行数を 0 に設定できるので 概念検証の段階ステージング環境テスト環境 などで使用するとコスト面で非常に優秀だと感じています。
  • サンプルコード
GitHubで編集を提案
Hogetic Lab

Discussion