CircleCIを使ってGitのコミットごとにGCPにDockerイメージを作る

# はじめに
コミットごとにDokerのイメージを作るようにしました。
それぞれのコミットごとに完全動作するコンテナが出来上がります。コミットごとに別々の環境がデプロイされると開発が良くなると考えています。

これには

1. CIの一つとしてイメージのビルドが通る、イメージを使ってテストを実行できる、といったことが可能になる
2. それぞれのイメージでデプロイができ、実行（テスト）ができる
3. 検証をする場合にイメージのビルドを行わず、プルすることができる

といったメリットがあります。

コミットごとデプロイについての関連記事です。

> PWAアプリがブランチごとに自動デプロイされる開発フローを取り入れたらすごく捗った話
> https://qiita.com/yousan/items/b2741110ca2c0dae6330

## Dockerfileの準備

テスト用に `Dockerfile` を作成しておきます。

```Dockerfile:Dockerfile
FROM golang:latest
```

テスト用なのでどういったDockerfileでも構いません。

# 設定
## CircleCIの設定

CircleCIでGCPを使う場合の参考ページです
https://circleci.com/docs/2.0/google-auth/

基本的にこのページに沿っていけば実現できるのですが、うまく動かないポイントも多いため注意が必要です。

まずCircleCIで`Add Project`し、GitHub（やGitLab, Bitbucket）と連携しておきます。
（GitHubとCircleCIの連携は済ませているとします）

<img width="999" alt="Screen Shot 2020-07-10 at 16.43.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47327/2a4e8262-a2b3-caec-508c-3ca879b5b016.png">


## GCPでのサービスアカウントの登録
GCP上でサービスアカウントを登録します。

<img width="725" alt="Screen Shot 2020-07-10 at 16.45.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47327/b524a6c0-233c-50e5-e581-65554c20f4bd.png">

作成したサービスアカウントからキーをJSONでダウンロードしておきます。

<img width="1080" alt="Screen Shot 2020-07-10 at 16.46.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47327/50a5063e-5aff-1138-6642-b06a9166376c.png">


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47327/76ac5a1b-f843-526b-486a-7e1707568dd7.png)


次に「IAM」から作成したサービスアカウントに対して権限を付与します。

| 権限  |
|---|
| Cloud Build サービス アカウント |
| Storage オブジェクト管理者 |
| Storage オブジェクト作成者 |
| Storage オブジェクト閲覧者 |
| 閲覧者 |

<img width="1085" alt="Screen Shot 2020-07-10 at 16.48.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47327/8b602c30-89e5-5aa6-5308-dde915a095bd.png">


Cloud Buildを実行するための権限、CGRにイメージをアップロードするためのCloud Storage関連の権限、プロジェクトを参照するための閲覧権限が必要でした。

思った以上に権限が必要で、足りないと途中でエラーが出るためその対処は大変です。
CircleCIによるGCPの参考ページにもGCRの場合には追加の権限が必要になる、との記載があります。

> If you are having issues pushing container images to GCR you may need more granular permissions than the default service account provides. You can grant permission changes in the Cloud Storage IAM Console
https://circleci.com/docs/2.0/google-auth/

「プロジェクトの編集者」といった権限を与えるとなんでもできてしまうため権限が足りなエラーは解消しますが、自動化する場合には万が一の漏洩を考えて最小限の権限とすることが必要です。

## ローカルでの試験
CircleCIでの手順に進む前にローカル環境から正しく動くか、権限が足りているか、ということを確認しておきます。
こうすることでCircleCI、GCPといった複数のサービスをまたがって行う作業に対してトラブルを早めに見つけられるようにしておきます。

まずプロジェクトIDとリージョン（ゾーン）を設定しておきます。（プロジェクト名をexampleとしていますが適宜読み替えてください）

```console
$ export GOOGLE_PROJECT_ID=example
$ export GOOGLE_COMPUTE_ZONE=ap-northeast1
```

先ほどダウンロードしたCircleCI用のキーで`gcloud`コマンドで認証を掛けます。

```console
$ echo <ダウンロードしたキーの場所>  | gcloud auth activate-service-account --key-file=-

Activated service account credentials for: [circleci@example.iam.gserviceaccount.com]
```

プロジェクトとリージョンの設定を行います。

```console
$ gcloud --quiet config set project ${GOOGLE_PROJECT_ID}
$ gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE}
```

アカウントが切り替わっていることを確認しておきます。

```console
$ gcloud config list

[compute]
zone = ap-northeast1
[core]
account = circleci@example.iam.gserviceaccount.com
disable_usage_reporting = False
project = example

Your active configuration is: [default]
```

先ほど作成したDockerfileをビルドしてみます。

```console
$ gcloud builds submit --tag gcr.io/${GOOGLE_PROJECT_ID}/test .


Creating temporary tarball archive of 10 file(s) totalling 5.4 KiB before compression.
Uploading tarball of [.] to [gs://example_cloudbuild/source/1594367971.xxxxxxxxxxxxxxxx.tgz]
Created [https://cloudbuild.googleapis.com/v1/projects/xxxxxxxxxxxxxxxxxxxxxx].
.....


latest: digest: sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx size: 1795
DONE
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

ID                                    CREATE_TIME                DURATION  SOURCE                                                                                            IMAGES                                    STATUS
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx  2020-07-10T07:59:32+00:00  18S       gs://example_cloudbuild/source/1594367971.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.tgz  gcr.io/example/test (+1 more)  SUCCESS
```

成功しました。

確認しておきます。

```console
$ gcloud container images list-tags gcr.io/example/test

DIGEST        TAGS                                    TIMESTAMP
b413792axxxx  latest                                  2020-07-07T18:20:07
``````

正しく作成されていますね。

## CircleCIへの組み込み
CircleCIでビルドが通るように設定を行います。

環境変数を設定します。

CircleCIのプロジェクトを開いた画面中にある `Project Settings` から `Environment Variables` を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47327/b9e51244-6d2e-3047-dad3-903373092605.png)

それぞれの変数を設定しています。
鍵のJSONについては `pbcopy` などを利用すると便利です。

```console
$ cat ~/Download/example.json | pbcopy
```

| 環境変数名 | |
|----|---|
| GCLOUD_SERVICE_KEY | 先の手順で作成したサービスアカウントの*鍵* |
| GOOGLE_PROJECT_ID | プロジェクトID |
| GOOGLE_COMPUTE_ZONE | コンピュートゾーン (ap-northeast1 ap-northeast2 など) |

下記のように３つの変数を設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47327/d001de76-69e1-4737-7198-d05968659d09.png)

## セットアップ
次にCircleCIの `config.yml` を設定します。

CircleCIでは`google/cloud-sdk`というイメージを使うと`gcloud`が一通り揃っています。

```.circleci/config.yml
version: 2.1
jobs:
  build:
    docker:
      - image: google/cloud-sdk
    steps:
      - checkout
      - run: |
          echo $GCLOUD_SERVICE_KEY | gcloud auth activate-service-account --key-file=-
          gcloud --quiet config set project ${GOOGLE_PROJECT_ID}
          gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE}
          gcloud builds submit --tag gcr.io/${GOOGLE_PROJECT_ID}/test .
```

コミット、プッシュをして動作させます。
`circleci`コマンドが入っている場合、下記のように `validate` を掛けておくと構文エラーをへらすことができます。

```console
$ circleci config validate && git add .circleci/config.yml &&  git commit -m 'Add CircleCI config' && git push origin HEAD
```

CircleCIでのビルドを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47327/b994c4c5-0305-df6c-2487-87de6e45f62e.png)


問題がなければ正しくビルドされ、GCRにイメージが作成されているはずです。

# Cloud Runでの実行
冒頭に述べた「それぞれのDockerイメージに対してテストを行う」を行いたいと思います。
GKEではクラスタ上にPodを作りますが、すべてのDockerイメージに対してPodを作成することは難しいです。というのもPodごとに動作メモリが必要で、クラスタで最大利用可能メモリが決まっており、それを超えてPodを作成することができません。

そのためビルド済みDockerイメージのテストをCloud Runを使って実行します。

参考: Google Cloud Run を使うまで
https://qiita.com/massie_g/items/5a9ce514eaa7c460b5e3

# 費用など
GCRでは Cloud Storage としてイメージが保管されるため、月々の費用がかかります。
2020年7月現在、概算で下記の通りです。

> Standard のバケット料金は、1 GB あたり月額約 $0.026 です。
> https://cloud.google.com/container-registry/pricing?hl=ja

巨大なイメージを管理する場合には注意が必要です。