😺

TerraformとGithub ActionsでCloud RunからPostgreSQLへアクセスする例

2022/05/08に公開

terraformとgithub actionsでCloud Runを管理する例の紹介です。今回はPostgreSQLを使いますが、workload identity poolは次のリンク先の例ですでに作成済みとします。
https://zenn.dev/nnabeyang/articles/05ce98c4955123

Terraform

サービスアカウント、Cloud Run、Cloud SQLの設定は次のterraformで行います。まず、ディレクトリ構造は次のようになっています。

.
├── backend.conf
├── cloudrun.tf
├── main.tf
├── registry.tf
├── service_account.tf
├── sql.tf
├── terraform.tfvars
└── variables.tf

main.tfはterraformのバージョンの指定と、cloudrunサービスのアクセスポリシーを定義しています。この例では誰でもアクセス可能になっています。

main.tf
terraform {
  required_version = "~> 1.1.9"
  backend "gcs" {
    prefix = "terraform/state"
  }
}

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

resource "google_cloud_run_service_iam_policy" "policy" {
  location = google_cloud_run_service.default.location
  project  = google_cloud_run_service.default.project
  service  = google_cloud_run_service.default.name

  policy_data = data.google_iam_policy.cloud_run_public.policy_data
}

service_account.tfはサービスアカウントの作成と権限について定義しています。cloudrun_rolesに主なrole(権限のセットのようなもの)を定義しています。

service_account.tf

locals {
  cloudrun_roles = [
    "roles/run.developer",
    "roles/iam.serviceAccountUser",
    "roles/cloudsql.client"
  ]
}

resource "google_project_service" "default" {
  project = var.project
  service = "iamcredentials.googleapis.com"
}

resource "google_service_account" "github_actions" {
  project      = var.project
  account_id   = "hello-crud"
  display_name = "hell-crud service account for GitHub Actions"
  description  = "link to Workload Identity Pool used by github actions"
}

resource "google_service_account_iam_member" "github-account-iam" {
  service_account_id = google_service_account.github_actions.name
  role               = "roles/iam.workloadIdentityUser"
  member             = "principalSet://iam.googleapis.com/${var.workload_identity_pool_name}/attribute.repository/${var.repo_name}"
}

resource "google_project_iam_member" "service_account" {
  count   = length(local.cloudrun_roles)
  project = var.project
  role    = element(local.cloudrun_roles, count.index)
  member  = "serviceAccount:${google_service_account.github_actions.email}"
}

output "service_account_github_actions_email" {
  description = "github account for github actions"
  value       = google_service_account.github_actions.email
}

sql.tfではCloud SQL関連の定義になります。ユーザーpostgresはデフォルトのユーザーで、このユーザーのパスワードでgcloudから接続できます。deletion_protectionfalseなのは、削除しやすいようにするためです。

sql.tf
resource "google_sql_database_instance" "postgres" {
  name             = "cloudrun-sql"
  region           = var.location
  database_version = "POSTGRES_13"
  settings {
    tier = "db-f1-micro"
    database_flags {
      name  = "cloudsql.iam_authentication"
      value = "on"
    }
  }

  deletion_protection = "false"
}

resource "google_sql_database" "database" {
  name     = "quickstart_db"
  instance = google_sql_database_instance.postgres.name
}

resource "google_sql_user" "users" {
  name     = "quickstart-user"
  instance = google_sql_database_instance.postgres.name
  type     = "BUILT_IN"
  password = var.db_user_password
}

resource "google_sql_user" "root_user" {
  name     = "postgres"
  instance = google_sql_database_instance.postgres.name
  type     = "BUILT_IN"
  password = var.db_root_user_password
}

cloudrun.tfはCloud Runサービスの定義になります。envの部分は環境変数を定義しているだけなので、コード依存のものでありCloud SQL特有のものではありません。autogenerate_revision_nametrueにしているのは、github actionsで更新した後でもterraformから状態を変更するときにエラーにならないようにするためです。

cloudrun.tf
resource "google_cloud_run_service" "default" {
  name                       = "run-sql"
  location                   = var.location
  autogenerate_revision_name = true

  template {
    spec {
      containers {
        image = var.container_image
        env {
          name  = "INSTANCE_UNIX_SOCKET"
          value = "/cloudsql/${var.project}:${var.location}:${google_sql_database_instance.postgres.name}"
        }
        env {
          name  = "DB_NAME"
          value = google_sql_database.database.name
        }
        env {
          name  = "DB_USER"
          value = google_sql_user.users.name
        }
        env {
          name  = "DB_PASS"
          value = google_sql_user.users.password
        }
        env {
          name  = "DB_IAM_USER"
          value = "${google_service_account.github_actions.name}@${var.project}.iam"
        }
      }
      service_account_name = google_service_account.github_actions.email
    }
    metadata {
      annotations = {
        "autoscaling.knative.dev/maxScale"      = "1000"
        "run.googleapis.com/cloudsql-instances" = google_sql_database_instance.postgres.connection_name
        "run.googleapis.com/client-name"        = "terraform"
      }
    }
  }
}

output "url" {
  value = google_cloud_run_service.default.status[0].url
}

registry.tfはサービスアカウントにレジストリの編集権限を与えています。

registry.tf
resource "google_container_registry" "registry" {
  project  = var.project
  location = "ASIA"
}

resource "google_storage_bucket_iam_member" "registry_create" {
  bucket = google_container_registry.registry.id
  role   = "roles/storage.admin"
  member = "serviceAccount:${google_service_account.github_actions.email}"
}

variables.tfは変数の定義になります。

variable.tf
variable "project" {
  description = "A name of a GCP project"
  type        = string
  default     = null
}

variable "location" {
  description = "A location of a cloud run instance"
  type        = string
  default     = "asia-northeast1"
}

variable "repo_name" {
  description = "github repository name"
  default     = "user/repository"
}

variable "container_image" {
  description = "docker container image"
  type        = string
  default     = ""
}

variable "db_user_password" {
  description = "password for the builtin postgresql user"
  default     = ""
}

variable "db_root_user_password" {
  description = "password for the root postgresql user"
  default     = ""
}

variable "workload_identity_pool_name" {
  description = "name of workload identity pool"
  default     = ""
}

このterraformを実行するには連携するgithubリポジトリをrepo_nameで指定する必要があります。またterraformの実行はdocker imageがないので適当に別のもの(例えばgcr.io/cloudrun/helloとか)を用意するか、手動で上げます。

github

githubに用意するアプリは次のリンクのcloudsql/postgres/database-sqlのものを使います。
https://github.com/GoogleCloudPlatform/golang-samples/tree/5e8a65dec4916d1144b0351709ddeb28eb9436fb/cloudsql/postgres/database-sql

*.yamlは不要なので、削除しても問題ありません。github actionsだけ次のように変更します。

.github/workflows/deploy.yml
name: deploy
on:
  push:
    branches:
      - "main"
  pull_request:
    branches:
      - "main"

env:
  GCP_REGION: ${{ secrets.GCP_REGION_PRD }}
  IMAGE: asia.gcr.io/${{ secrets.GCP_PROJECT_ID_PRD }}/run_sql:${{ github.sha }}
  GOOGLE_IAM_WORKLOAD_IDENTITY_POOL_PROVIDER: ${{ secrets.GOOGLE_IAM_WORKLOAD_IDENTITY_POOL_PROVIDER }}
  SERVICE_ACCOUNT_EMAIL: ${{ secrets.SERVICE_ACCOUNT_EMAIL }}
jobs:
  build:
    runs-on: ubuntu-latest

    permissions:
      contents: 'read'
      id-token: 'write'

    steps:
      - name: Checkout the repository
        uses: actions/checkout@v3
      - id: "auth"
        uses: "google-github-actions/auth@v0"
        with:
          workload_identity_provider: "${{ env.GOOGLE_IAM_WORKLOAD_IDENTITY_POOL_PROVIDER }}"
          service_account: "${{ env.SERVICE_ACCOUNT_EMAIL }}"
      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v0
      - name: Authorize Docker push
        run: gcloud auth configure-docker
      - name: Build a docker image
        run: docker build -t ${{ env.IMAGE }} -f Dockerfile .
      - name: Push the docker image
        run: docker push ${{ env.IMAGE }}
      - name: Deploy to Cloud Run
        id: deploy
        uses: google-github-actions/deploy-cloudrun@v0
        with:
          service: run-sql
          image: ${{ env.IMAGE }}
          region: ${{ env.GCP_REGION }}
      - name: Clean up Container images
        run: |
          gcloud container images list-tags "${BASE_IMAGE}" \
            --filter="NOT tags:${GITHUB_SHA}" --format="get(digest)" | \
          while read digest
          do
            gcloud container images delete -q --force-delete-tags "${BASE_IMAGE}@$digest"
          done
        env:
          GITHUB_SHA: ${{ github.sha }}
          BASE_IMAGE: asia.gcr.io/${{ secrets.GCP_PROJECT_ID_PRD }}/run_sql

以前の記事に書いてある通りにリポジトリにSecretsした後、git push -u origin mainしたらCloud Runサービスが更新されます(もちろんその前にterraform applyしてること)
https://zenn.dev/nnabeyang/articles/05ce98c4955123#github-actions
github actionsのビルドが成功した後、Cloud Runサービスにアクセスしたとき、次のように表示され、ボタンをクリックすると投票行えることが確認できれば成功です。

Discussion