GitlabとGCPのCloudBuildを連携して、ビルド・デプロイを自動化する

## はじめに
今担当しているウェブシステムをGithubから社内のGitlabに移行しています。この移行に伴い、ビルドやデプロイを自動化するためCI/CDを導入しようと思っています。
導入するには、GitlabのCIやGCPの**Cloud build**を利用しようと思っているので、
この間
今週からGitlabのCIやGCPの**Cloud build**、**Cloud Run**などを触ってみました。


## 事前準備
- GCPのプロジェクトを作成する
- Gitlabリポジトリを作成して、サンプルコードや`Dockerfile`を用意する

今回は`go`言語で非常に簡単なウエブアプリを作りました。

ディレクトリ構成は以下のようです。

```
my_sample_project
│  Dockerfile
│  go.mod
|  cloudbuild.yml
|  main.go
```

```go:main.go
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

const serverPort = 8080

func handler(w http.ResponseWriter, r *http.Request) {
	json.NewEncoder(w).Encode(map[string]string{"message": "Welcome to Linkbal Advent Calendar 2020"})
}

func main() {
	fmt.Printf("Starting server on port %d.....\n", serverPort)

	http.HandleFunc("/", handler)

	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", serverPort), nil))
}
```

```dockerfile:Dockerfile
FROM golang:1.14.13-buster as builder

WORKDIR /app

COPY go.* ./
RUN go mod download

COPY . .

# Build the binary
RUN go build -mod=readonly -v -o server

FROM debian:buster-slim
RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
    ca-certificates && \
    rm -rf /var/lib/apt/lists/*

# Copy the binary to the production image from the builder stage.
COPY --from=builder /app/server /app/server

CMD ["/app/server"]
```

- cloudbuild.yml: Cloud Buildの構成ファイルです。後で説明します。

## 自動ビルド

まず、Gitlabから自動的にCloud Buildでコンテナーイメージをビルドできるようにします。

これを実現するには、以下の2つ方法あります。

### 1. GitlabのCIを使用しない方法

- **まず、GitlabのリポジトリをGCPの`Cloud Source Repositories`にミラーリング（Mirroring）する**

このGCPの[公式ガイド](https://cloud.google.com/solutions/mirroring-gitlab-repositories-to-cloud-source-repositories#costs)に沿って簡単にミラーリングを設定できます。

- **Cloud Buildのトリガーを使って、ソースコードを変更するたびに、自動のビルドを設定する**

GCP管理画面でCloud Buildの新規トリガーを作成する

![スクリーンショット 2020-12-17 23.34.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/9ae37f3f-ba12-514d-578b-fc43e68a3e67.png)


- イベント: トリガーを発火するリポジトリの3つイベントという選択肢がありますが、最後は「Githubアプリのみ」ですので、最初2つ選択肢しか選択できません。
  - **ブランチにpushする**: 特定のブランチに対してcommitが行われたときにビルドを開始するトリガーを設定します。
  - **新しいタグをpushする**: 特定のタグを含むCommitが行われたときにビルドを開始するトリガーを設定します。

本番環境へのデプロイの時のみ使う場合、**ブランチにpushする**を選択して、masterブランチにプッシュするたびにビルドを開始するという設定でいいですが、テスト環境へのデプロイでも使いたい場合、**新しいタグをpushする**の方がいいですね。開発中にコードをPushするたびに、ビルド・デプロイが必要ないだから。

- リポジトリ：　上のステップにGitlabからミラーリング済みのリポジトリです。
- タグ：　`.*`：全てのタグ
- ビルドの構成： リポジトリ内にCloud Build 構成ファイルを指定する

#### Cloud Buildの構成ファイル

```yml:cloudbuild.yml
steps:
  # コンテナーイメージをビルド
  - name: "gcr.io/cloud-builders/docker"
    args: [ "build", "-t", "asia.gcr.io/$PROJECT_ID/trantan_ci_example:${TAG_NAME}", "." ]
  # コンテナーイメージをプッシュ
  - name: 'gcr.io/cloud-builders/docker'
    args: [ 'push', 'asia.gcr.io/$PROJECT_ID/trantan_ci_example:${TAG_NAME}']

images:
  - "asia.gcr.io/$PROJECT_ID/trantan_ci_example:${TAG_NAME}"

logsBucket: 'gs://trantan_ci_example_cloudbuild_logs'
```

上のトリガーの設定で**ブランチにpushする**を選択した場合、`${TAG_NAME}`（タグ名）の代わりに、`${COMMIT_SHA}`を使ってください。

この方法で、自動のビルドだけではなく、CloudFunctionsの自動デプロイでも実現できそうですね。

### 2. GitlabのCIを使用する方法

#### GCP側で設定する
まず、GCP管理画面で以下の必要な設定をします。

- [Cloud Build API](https://console.cloud.google.com/cloud-build/builds)を有効にする
- Cloud Build用サービスアカウントを作成する
  ![スクリーンショット 2020-12-17 11.08.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/296e7da9-03ed-fd03-eebf-ed3a7005fa29.png)

  - 「Cloud Build サービス エージェント」権限ロールを付与する
  - 作成したサービスアカウントに対するJSONキーを作成する

#### GitlabのレポジトリのCI/CDを設定する

- リポジトリにCIのRunnerを追加する：　`Settings > CI/CD > Runners`

![スクリーンショット 2020-12-17 12.43.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/deb6f95e-b833-3191-91ea-e73df4755976.png)


- GCPの情報を保存するためのレポジトリの変数を作成する： `Settings > CI/CD > Variables`

![スクリーンショット 2020-12-17 12.59.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/491f6ca3-bc70-1e08-b4c0-c7c344cc830b.png)

GCP_PROJECT_ID: GCPのプロジェクトID
GCP_SERVICE_KEY: 上のステップで作成したサービスアカウントのJSONキー


#### GitlabのリポジトリにCIを追加する

プロジェクトディレクトリーに`.gitlab-ci.yml`を追加する

```yml:.gitlab-ci.yml
stages:
  - test
  - build

# コードフォーマット、テスト
test:
  stage: test
  # Lintチェックやユニットテスト実行など追加する

# ビルド
cloud_build:
  stage: build
  image: google/cloud-sdk
  before_script:
    - echo $GCP_SERVICE_KEY > gcloud-service-key.json # Google Cloud service accounts
  script:
    - gcloud auth activate-service-account --key-file gcloud-service-key.json
    - gcloud config set project $GCP_PROJECT_ID
    - gcloud builds submit . --config=cloudbuild.yml --substitutions=TAG_NAME="$CI_COMMIT_TAG"
  # タグ付きコミットをプッシュした際に、行われる
  # コミットをプッシュするたびに、行われる場合、このオプジョンが要らない
  only:
    - tags
```

これでタグを付けて、コミットをプッシュした際に、自動的にビルドできます。

CIのJobのログは以下の通りです。

![スクリーンショット 2020-12-17 14.37.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/d1134d99-6450-336f-f732-70eb5077412a.png)


## 自動デプロイ

最後、Cloud Buildでビルドしたコンテナーイメージを使用して、Cloud Runサービスを自動的にデプロイする方法を説明したいと思います。

### GCPの設定を変更する
GCP管理画面で、`Cloud Build > 設定`ページを開いて、サービスアカウント権限でCloud Runにデプロイできるように、`Cloud Run`権限を有効にする

![スクリーンショット 2020-12-17 22.51.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/5ffbcc68-88e2-d4f3-71be-9f15dfcd4dcd.png)


### Cloud Buildの構成ファイルを変更する

Cloud Buildでビルドした後に、Cloud Runにビルドしたコンテナーイメージを使ってデプロイするように、以下のステップを`cloudbuild.yml`ファイルの`steps`に追加する

```yml:cloudbuild.yml
steps:
  # cloud runにデプロイ
  - name: "gcr.io/cloud-builders/gcloud"
    args: ['run', 'deploy', 'trantan-ci-example-app', '--image', 'asia.gcr.io/$PROJECT_ID/trantan_ci_example:${TAG_NAME}', '--region', 'asia-northeast1', '--platform', 'managed', '--allow-unauthenticated']
```

`trantan-ci-example-app`: サービス名


結果は以下の通りです。

![スクリーンショット 2020-12-17 23.15.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/03ba67d6-e254-9e02-699f-ecdaa72a16b3.png)


このサンプルのアプリはCloud Runにデプロイしましたが、ビルドしたコンテナーイメージを使って、他のサービスにもデプロイできます。例えば、僕が担当しているサービスは今GKE（Google Kubernetes Engine）上で動いてるので、CIの`deploy`ステージやジョブを追加して、`gcloud`コマンドでデプロイできそうです。

## おわりに

今回は検証のため、GitlabやGCPのドキュメントに参考しながら**Cloud Build**や**Cloud Run**を触って、非常に簡単なサンプルのアプリだけど、自動的にビルド・デプロイを実現できました。

これから実際のサービスに導入してみようと思いますので、都合が良いときに、これに関する記事をまた書こうと思います。

## 参考
- https://cloud.google.com/solutions/mirroring-gitlab-repositories-to-cloud-source-repositories
- https://docs.gitlab.com/ee/ci/README.html
- https://cloud.google.com/cloud-build/docs/deploying-builds/deploy-cloud-run