🙆

Cloud RunをGithub ActionsとTerraformで管理する

2022/05/07に公開

terraformでgcpの状態を管理し、github actionsでコードが更新されるたびにcloud runへdeployする方法を紹介します。順番としては、まずgithubとworkload identity連携するためのサービスアカウントを作成し、そのサービスアカウントを実行者としてCloud Runサービスを作成します。それからCloud Runで実行するアプリを用意し、github actionsでデプロイする設定を行います。

github連携用のサービスアカウントの作成

サービスアカウント用の設定は次のようにします。

ディレクトリ構造
.
├── backend.conf
├── main.tf
├── registry.tf
├── terraform.tfvars
└── variables.tf

main.tfでは、terraformのバージョン指定とサービスアカウントのroleの指定が定義されています。repo_nameは権限を与えるgithubリポジトリなので、この段階で連携するリポジトリを決めておく必要があります。

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

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

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

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

resource "google_iam_workload_identity_pool" "github" {
  provider                  = google-beta
  project                   = var.project
  workload_identity_pool_id = "github"
  display_name              = "github"
  description               = "Workload Identity Pool for GitHub Actions"
}

resource "google_iam_workload_identity_pool_provider" "github" {
  provider                           = google-beta
  project                            = var.project
  workload_identity_pool_id          = google_iam_workload_identity_pool.github.workload_identity_pool_id
  workload_identity_pool_provider_id = "github-provider"
  display_name                       = "github actions provider"
  description                        = "OIDC identity pool provider for execute github actions"

  attribute_mapping = {
    "google.subject"       = "assertion.sub"
    "attribute.repository" = "assertion.repository"
    "attribute.owner"      = "assertion.repository_owner"
    "attribute.refs"       = "assertion.ref"
  }

  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }
}

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/${google_iam_workload_identity_pool.github.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
}

output "google_iam_workload_identity_pool_provider_github_name" {
  description = "Workload Identity Pood Provider ID"
  value       = google_iam_workload_identity_pool_provider.github.name
}

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

variables.tf
variable "project" {
  description = "A name of a GCP project"
  type        = string
  default     = null
}
variable "repo_name" {
  description = "github repository name"
  default     = "user/repository"
}

registry.tfにはcloud registryの作成とサービスアカウントに権限を与えています。

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}"
}

backend.confにバケット名を書いてまずinitします。

backend.conf
bucket = "tf-state-xxxxxxxxx"
terraform init -backend-config=./backend.conf

問題なく実行できたら、次はplanで問題ないか見ます。

terraform.tfvars
project   = "*******"
repo_name = "user/repository"
terraform  plan  -var-file=./terraform.tfvars

問題なければapplyしましょう(好みで-auto-approve付けてください)。

$ terraform  apply  -var-file=./terraform.tfvars
Outputs:åß

service_account_github_actions_email = github-actions@${gcp_project}.iam.gserviceaccount.com
google_iam_workload_identity_pool_provider_github_name = projects/**********/locations/global/workloadIdentityPools/${pool_id}/providers/${provider_id}

アウトプットは後で使うので、消さないようにしましょう。とりあえずこれでworkload identity連携できるサービスアカウントができました。

Cloud Runサービスの作成

Cloud Runサービスの設定は次のようにします。

ディレクトリ構造
.
├── backend.conf
├── cloudrun.tf
├── main.tf
├── out.txt
├── terraform.tfvars
└── variables.tf

main.tfはterraformのバージョン指定と公開範囲について書いています。全体公開になっているので、目的に合わなければ設定を変える必要があります。なおgoogle_cloud_run_service.defaultcloudrun.tfで定義しています。

main.tf
terraform {
  required_version = "~> 1.1.9"
  required_providers {
    google = ">= 4.20.0"
  }
  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
}

cloudrun.tfはCloud Runサービスの定義になります。外部からコンテナイメージとサービスアカウントを指定する作りになっています。

cloudrun.tf
resource "google_cloud_run_service" "default" {
  name     = "cloudrun-hello"
  location = var.location  
  autogenerate_revision_name = true
  template {
    spec {
      containers {
        image = var.container_image
      }
      service_account_name = var.service_account_name
    }
  }
}

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

variables.tfは先ほどと同様で、変数の定義が書かれています。

variables.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 "container_image" {
  description = "docker container image"
  type        = string
  default     = ""
}

variable "service_account_name" {
  description = "Email address of the IAM service account"
  type        = string
  default     = ""
}

さきほどと同様にして実行しますが、terraform.tfvarsは次のようにします。まだアプリを作成していないのでcontainer_imageはgoogleが用意したサンプルプロジェクトを指定します。

terraform.tfvars
project              = "*********"
location             = "asia-northeast1"
container_image      = "gcr.io/cloudrun/hello"
service_account_name = "github-actions@${project_id}.iam.gserviceaccount.com"

実行後に表示されるurlにアクセスして、次のような画面が表示されれば成功です。

github actions

本当はアプリを作る必要がありますが、次のリポジトリのrun/helloworldを使います。
https://github.com/GoogleCloudPlatform/golang-samples/tree/782a7a7b57faee71500d5ef76854f9a0be7aa1fd/run/helloworld

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 }}/hello_cloudrun:${{ 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: cloudrun-hello
          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 }}/hello_cloudrun

GOOGLE_IAM_WORKLOAD_IDENTITY_POOL_PROVIDERSERVICE_ACCOUNT_EMAILは、サービスアカウントを作成したときにOutputで表示されたものをgithubリポジトリのシークレットに設定します。GCP_PROJECT_ID_PRDはGCPのプロジェクトIDで、GCP_REGION_PRDはリージョンです(us-central1, asia-northeast1など)。最後のClean up Container imagesは、今回pushしたイメージ以外はContainer Registryから削除する処理です。最後にgithubリポジトリのmainブランチにpushするとCloud Runへデプロイされ、先ほどのURLを開くとHello World!とだけ表示されるはずです。ここまで、GCPのリソースについてはterraformのステート保存用にGCSのバケットを作った以外はterraformで管理しているのでterraform destroyで削除できます。

次は

PostgreSQLを使った例を書きました。良かったら、読んだください。
https://zenn.dev/nnabeyang/articles/f540cb1135232a

Discussion