CircleCI + Cloud Buildを使用してCloud Runへのデプロイを自動化する

## はじめに

[bocci bocci](https://boccibocci.com)を運営している@shoji-kaiです。
[前回の記事](https://qiita.com/shoji-kai/items/82f89d7e62fd0bdaacae)ではCloud Runを使って無料でリダイレクトサーバを立ち上げる記事を書きましたが、すべてgcloudコマンドを使って手動で実行していました。
私は、いちおうDevOpsエンジニアでありますので、ここはしっかりとCI/CD環境を用意して自動化したいところです。
そこで今回は、CircleCI + Cloud Buildを使用してCloud Runへのデプロイを自動化してみたいと思います。

## APIの有効化

最初にCloud Build APIを有効にしておきます。
その他、足りないAPIがあれば適宜有効にしてください。

```
gcloud services enable cloudbuild.googleapis.com
```

## Cloud Buildのハマリポイント

実際にCloud Buildを動かす前にハマリポイントを解消しておきます。
私が最初に手動でClould Buildを実行した際に以下のようなエラーが発生しました。

```
Step #2: ERROR: (gcloud.run.deploy) PERMISSION_DENIED: Permission 'run.services.get' denied on resource ...
```

Owner権限で実行しているにも関わらず"PERMISSION_DENIED"とは何ぞや？と思い調べてみたところ、Cloud Buildするには以下の設定が必要なようです。

https://cloud.google.com/build/docs/deploying-builds/deploy-cloud-run?hl=ja#required_iam_permissions

というわけで、Cloud BuildサービスアカウントにCloud Run管理者ロールとサービスアカウントユーザーロールを割り当てます。
上記URLにあるようにGCPコンソールでやってもいいのですが、ここではgcloudコマンドを使ってやってみます。

```zsh:zsh
roles=(run.admin iam.serviceAccountUser)
for role in $roles; do
  gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member serviceAccount:<PROJECT_NUMBER>@cloudbuild.gserviceaccount.com \
    --role roles/$role
done
```

これでCloud Build時のPERMISSION DENIEDエラーが解消されました。
毎度のことながら、ドキュメントはきちんと読みましょうということですね。

## 手動でCloud Buildしてみる

前回は自分のMacBook AirでDockerイメージをビルドしていましたが、今回はCloud Buildを使用してビルドします。
まずは試しに手動で実行してみます。

Cloud Buildでビルドするには、cloudbuild.yamlと呼ばれるビルド構成ファイルを用意します。

```cloudbuild.yaml
steps:
  # ビルド
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', '<REGISTRY_NAME>/<PROJECT_ID>/<REPOSITORY_NAME>/<CONTAINER_NAME>', '.']
  # プッシュ
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', '<REGISTRY_NAME>/<PROJECT_ID>/<REPOSITORY_NAME>/<CONTAINER_NAME>']
  # デプロイ
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: gcloud
    args: ['run', 'deploy', '<SERVICE_NAME>', '--image', '<REGISTRY_NAME>/<PROJECT_ID>/<REPOSITORY_NAME>/<CONTAINER_NAME>', '--region', '<REGION>', '--allow-unauthenticated']
# Artifact Registryに保存。プッシュとの違いは下記URLを参照
# ref. https://cloud.google.com/build/docs/building/build-containers?hl=ja#store-images
images: ['<REGISTRY_NAME>/<PROJECT_ID>/<REPOSITORY_NAME>/<CONTAINER_NAME>']
# ロギング設定
# ref. https://cloud.google.com/build/docs/api/reference/rest/v1/projects.builds#loggingmode
options:
  logging: CLOUD_LOGGING_ONLY

```

Artifact RegistryにDockerリポジトリをまだ作っていない場合は先に作成します。

```
gcloud artifacts repositories create <REPOSITORY_NAME> --location=<REGION> --repository-format=Docker
```

Cloud Buildでビルドします。

```
% cd <path/to/docker>
% ls -1
Dockerfile
cloudbuild.yaml
default.conf
nginx.conf
% gcloud builds submit .
```

正常に終了すると以下のようなメッセージが表示されます。

```
% gcloud builds submit .
:
(snip)
:
DONE
---------------------------------------   ...
ID                                        ...   STATUS
7cd4a829-f1a0-4c36-b7e8-93bf64eb3ef4      ...   SUCCESS
```

## CircleCIでビルド〜デプロイまでを自動化する

Cloud Buildの使い方が分かったところで、次にCircleCIで自動化していきます。
CircleCI自体のセットアップや詳細な使い方などについては割愛しますので、他のドキュメントをご参照ください。
ここでは、当該GitHubリポジトリとCircleCIプロジェクトとの連携が済んでいる前提で話しを進めます。

### サービスアカウントの作成

まずはCircleCI用のサービスアカウントを用意します。

```
gcloud iam service-accounts create circleci --display-name circleci
```

次にサービスアカウトにビルド〜デプロイで必要なロールを付与します。

```zsh:zsh
roles=(cloudbuild.builds.editor storage.admin artifactregistry.admin)
for role in $roles; do
  gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member serviceAccount:circleci@<PROJECT_ID>.iam.gserviceaccount.com \
    --role roles/$role
done
```

最後にサービスアカウントのキーファイル(JSON)を作成しておきます。後でCircleCIに登録します。

```
gcloud iam service-accounts keys create circleci-key.json \
  --iam-account circleci@<PROJECT_ID>.iam.gserviceaccount.com
```

### CircleCIの設定

今回はcircleci/gcp-cloud-runというCircleCI Orbsを利用してCloud Runへデプロイします。
circleci/gcp-cloud-runの細かな設定などについては[ドキュメント](https://circleci.com/developer/orbs/orb/circleci/gcp-cloud-run)をご参照ください。

まずはCircleCIプロジェクトに以下の環境変数を登録します。

```
- GOOGLE_PROJECT_ID   # プロジェクトID
- GOOGLE_COMPUTE_ZONE # ゾーン e.g. us-central1-c, etc
- GCLOUD_SERVICE_KEY  # 先に作成した circleci-key.json の中身
- REGION              # リージョン e.g. us-central1, etc
- REPOSITORY          # Aritfact RegistoryのDockerリポジトリ名
```

.circleci/config.ymlは以下のようにします。

```.circleci/config.yml
version: 2.1
orbs:
  cloudrun: circleci/gcp-cloud-run@1.0.2
jobs:
  build_and_deploy:
    docker:
      - image: 'cimg/base:stable'
    steps:
      - checkout
      - cloudrun/init
      - run:
          name: Create a docker repository if not exists.
          shell: /bin/bash
          command: |
            gcloud artifacts repositories create $REPOSITORY \
              --location=$REGION \
              --repository-format=Docker 2>/dev/null || echo 'already exists'
      - cloudrun/build:
          config: docker/nginx/cloudbuild.yaml
          source: docker/nginx/
workflows:
  build-and-deploy:
    jobs:
      - build_and_deploy
```

### CircleCIの実行

GitHubにコードをプッシュしてCircleCIを走らせます。

```
git add .
git commit -m '<コミットメッセージ>'
git push
```

CircleCIの画面から正常にデプロイされたことを確認できれば成功です。
![スクリーンショット 2021-11-12 16.52.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1177074/92ee3901-cced-3cf0-a6ca-b3b267bc34ae.png)

## おわりに

CircleCIもCloud Buildも個人で使うには十分な無料枠がありますので、これまた無料で運用できます。
自動化の設定は初回はやや手間がかかりますが、そこさえクリアすれば後は楽できますし、手作業がなくなるのでオペミスも減らせます。
エンジニアたるもの、やはりDRYに行きたいところです。
それでは、こちらの記事がどなたかの参考になれば幸いです。