CloudRun を使っていく準備


CloudRun の CI/CD 作ったときの振り返りとかハマったこととかを書いていく。

はじめに

CloudRun めっちゃいい。Docke Image だけあればすぐにサービスインできる。請求がコンテナが立ち上がっている間のコンピューティングリソースに対してなのもいい💪

仕事で是非使っていきたいお気持ち😉

準備

Docker Image 作って GCR に上げるだけ。

docker build -t [IMAGE-NAME] .

gcloud auth configure-docker

docker tag [IMAGE-NAME] gcr.io/[PROJECT-ID]/[IMAGE-NAME]:[TAG]

docker push gcr.io/[PROJECT-ID]/[IMAGE-NAME]:[TAG]

これで準備は整った。

ひとまずコンソールからデプロイして雰囲気を掴む

GCRから Image を選択して必要な入力項目を入力したら作成を押すのみ。

作成ボタンを押すと作られる。

めちゃ楽なんだが!!!

Terraform Sample

resource "google_cloud_run_service" "my_cloudrun" {
  name = var.cloud_run_name
  location = "us-central1"

  template {
    spec {
      containers {
        image = data.google_container_registry_image.my_image.image_url
        env {
          name = "DB_USER"
          value = var.db_user
        }
        env {
          name = "DB_PASSWORD"
          value = var.db_password
        }
        env {
          name = "DB_HOST"
          value = google_sql_database_instance.my_db.connection_name
        }
        env {
          name = "DB_DATABASE"
          value = var.database_name
        }
      }
      service_account_name = google_service_account.my_sa.email
    }
    metadata {
      annotations = {
        "autoscaling.knative.dev/maxScale" = "1000"
        "run.googleapis.com/cloudsql-instances" = google_sql_database_instance.my_db.connection_name
        "run.googleapis.com/client-name" = "cloud-console"
      }
    }
  }
  traffic {
    percent = 100
    latest_revision = true
  }
  depends_on = [
    google_project_service.my_project]
}

CloudSQL との接続には metadata.annotations."run.googleapis.com/cloudsql-instances" にコネクションネームを指定する。

サービスアカウントを通じて CloudRun にロールを付与

service_account_name には role/cloudsql.client が付与されたサービスアカウントの Email を指定する。

サービスアカウントのリソースは以下のような定義になる。

resource "google_service_account" "my_sa" {
  account_id = var.service_account_id
  display_name = "my-sa"
}

resource "google_project_iam_member" "cloud_sql_conn" {
  role = "roles/cloudsql.client"
  member = "serviceAccount:${google_service_account.main.email}"
}

もしかしたら roles/iam.serviceAccountUser のロールも必要だったかもしれないけどちょっと忘れてしまった...。

CloudRun の公開設定

CloudRunサービスはデフォルトではリクエストする際に Authorization ヘッダーが必要。

google_cloud_run_service_iam_memberroleroles/run.invoker, memberallUsers を指定すると Authorization ヘッダーが不要になり Public なアクセスが許可される。

resource "google_cloud_run_service_iam_member" "cr_invoker_all" {
  service = google_cloud_run_service.my_cloudrun.name
  location = google_cloud_run_service.my_cloudrun.location
  role = "roles/run.invoker"
  member = "allUsers"
}

Problem

P1. Terrafrom から GCR の digest の差分が読み込めない

GCR上の Image には digest という概念があり、terraform で CloudRun に指定する Image の指定がタグまでだとその差分を terraform state から読み込むことができず、プロビジョニングされない。

かと言って、Image の更新の度に digest を指定とかもしたくない...。

仕方なく手動でデプロイとかすると CloudRun のリビジョンが更新されてしまい、tfstate の差分が発生し apply できなくなってしまうという。

P2. CloudRun の管理画面から secret が丸見え

🙈

Solution

S1. Terraform から CloudRun へのプロビジョニングは初回だけにする

Terraform から CloudRun へのプロビジョニングは初回のみにして、
以降は Image の更新と CloudRun へのプロビジョニングを同じタイミングで、具体的にはアプリケーションのリポジトリのCIで行うようにした。

アプリケーションのCIにCloudRunプロビジョニングのジョブ追加

CloudBuild を使う。

clouduild.yaml
steps:
  - id: CloudRun Deploy
    name: 'gcr.io/cloud-builders/gcloud'
    args: [
      run, deploy, $_SERVICE_NAME, --allow-unauthenticated,
      --image, $_IMAGE_NAME,
      --region, us-central1,
      --platform, managed,
      --set-env-vars,
      "DB_USER=$_DB_USER,
      DB_PASSWORD=$_DB_PASSWORD,
      DB_DATABASE=$_DB_DATABASE,
      DB_HOST=$_DB_CONNECTION_NAME,
      --max-instances, '1000',
      --set-cloudsql-instances, $_DB_CONNECTION_NAME,
      --service-account, $_SERVICE_ACCOUNT_EMAIL
    ]

上記 CloudBuild の設定を .gitlab-ci.yaml からキックする。

.gitlab-ci.yaml
# Docker build & push...

deploy:
  stage: deploy
  image: google/cloud-sdk:latest
  before_script:
    - gcloud config set project $GCP_PROJECT_ID
    - cat $GCP_SERVICE_ACCOUNT_JSON > /tmp/credentials.json
    - gcloud auth activate-service-account --key-file=/tmp/credentials.json
  variables:
    IMAGE_NAME: gcr.io/[PROJECT-ID]/[IMAGE-NAME]:[TAG]
  script:
    - source $ENV_VARS
    - SUBSTITUTIONS=$(echo _SERVICE_NAME=$SERVICE_NAME, \
      _IMAGE_NAME=$IMAGE_NAME, \
      _DB_USER=$DB_USER,\
      _DB_PASSWORD=$DB_PASSWORD, \
      _DB_DATABASE=$DB_DATABASE, \
      _DB_CONNECTION_NAME=$DB_CONNECTION_NAME, \
      _SERVICE_ACCOUNT_EMAIL=$_SERVICE_ACCOUNT_EMAIL)
    - SUBSTITUTIONS=$(echo $SUBSTITUTIONS | awk '{ gsub(" ", ""); print }')
    - gcloud builds submit --config=cloudbuild.yaml --substitutions=$SUBSTITUTIONS
  when: manual

本当は Image の build & push も CloudBuild でやれたら良さそう。

Terraform に ignore_changes を追加

CloudRun の初回プロビジョニング以降は下記のような設定を追加することで CloudRun の apply を無視できる。

resource "google_cloud_run_service" "my_cloudrun" {

  // 様々な設定...

  lifecycle {
    ignore_changes = all
  }
}

S2. Secret Manager を使う

ただ、結論から言うとコンソールに出力される環境変数の値を非表示にすることは無理だったっぽい。開発者しか見ないから別にいいのか。

カスタマーサポートに問い合わせたときに教えてもらったリンク。
https://stackoverflow.com/questions/60034139/access-environment-variables-stored-in-google-secret-manager-from-google-cloud-b

Secret Manager を使うのがいいよとのこと(2020/02/07時点でベータ版なので使うときはちょっと注意)

Secret Manager への登録

echo -n [DB_PASSWORD] | \
  gcloud beta secrets create my_service_db_passowrd \
  --replication-policy automatic \
  --labels=stage=dev,product_name=my-service \
  --data-file=-

gcloud beta secrets versions access 1 --secret="my_service_db_passowrd"

gcloud beta secrets versions access latest --secret="my_service_db_passowrd"

CloudBuild 修正

cloudbuild.yaml
steps:
  - id: Access Secret manager
    name: 'gcr.io/cloud-builders/gcloud@sha256:c1dfa4702cae9416b28c45c9dcb7d48102043578d80bfdca57488f6179c2211b'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        gcloud beta secrets versions access --secret=my_service_db_passowrd latest > /secrets/db_passowrd
    volumes:
      - name: 'secrets'
        path: '/secrets'
  - id: CloudRun Deploy
    name: 'gcr.io/cloud-builders/gcloud'
    entrypoint: 'bash'
    volumes:
      - name: 'secrets'
        path: '/secrets'
    args:
      - '-c'
      - |
        export DB_PASSWORD=$(cat /secret/db_password) \
        && gcloud run deploy ...

ほぼ Shell 書いてるような感じ...😓
もっと CloudBuild の構文でいい感じのサポートが欲しい感。

今後

エンドユーザーの認証機構カスタムドメインマッピングを試していきたい。CloudRun Known Issuesの進捗は観察しておきたい。

おわり