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_member
の role
に roles/run.invoker
, member
に allUsers
を指定すると 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 を使う。
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 からキックする。
# 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 修正
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の進捗は観察しておきたい。