CloudRun を使っていく準備

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

# はじめに

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

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

# 準備

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

```sh
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 を選択して必要な入力項目を入力したら作成を押すのみ。

<img width="1083" alt="スクリーンショット 2020-02-07 14.56.21.png (319.6 kB)" src="https://img.esa.io/uploads/production/attachments/11466/2020/02/07/41057/8356f69a-7345-4d2e-b100-9dafd68655ab.png">

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

<img width="1082" alt="スクリーンショット 2020-02-07 14.57.23.png (234.2 kB)" src="https://img.esa.io/uploads/production/attachments/11466/2020/02/07/41057/0b9ce81b-3f0d-40ec-b22e-7eb3faa3d16e.png">

めちゃ楽なんだが!!!

# Terraform Sample

```hcl
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 を指定する。

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

```hcl
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 なアクセスが許可される。

```hcl
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 が丸見え

🙈

![viewable-secret-in-console.png (280.1 kB)](https://img.esa.io/uploads/production/attachments/11466/2020/02/07/41057/da8ee44f-9de2-4c7e-8646-ead3cb40038b.png)

# Solution

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

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

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

CloudBuild を使う。

```yaml: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 からキックする。

```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 を無視できる。

```hcl
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 への登録

```sh
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 修正

```yaml: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 の構文でいい感じのサポートが欲しい感。

# 今後

[エンドユーザーの認証機構](https://cloud.google.com/run/docs/authenticating/end-users?hl=ja)や[カスタムドメインマッピング](https://cloud.google.com/run/docs/mapping-custom-domains?hl=ja)を試していきたい。[CloudRun Known Issues](https://cloud.google.com/run/docs/issues?hl=ja)の進捗は観察しておきたい。

# おわり