Cloud BuildでGKEにデプロイ

Cloud Build はGCP上でアプリケーションを自動でビルド、デプロイできるサービスです。

https://cloud.google.com/build?hl=ja

GitHubと連携してリポジトリへのプッシュをトリガーに発動することができます。
（ブランチや、特定のタグをトリガーとして設定ができます）

ビルドのステップをyamlに記述して構成することができます。

自分の用途でビルド～デプロイまで以下のような流れとなります。

```
GitHubにプッシュ（トリガーとなるタグ付けをしてプッシュ）
　↓
CloudBuild が起動（トリガーに設定されたタグにより起動）
　↓
イメージをビルド
　↓
ビルドされたイメージを ContainerRepository にプッシュ
　↓
Unitテスト実行
　↓
コンテナイメージをGKEにデプロイ
```

### GKE（事前準備）
クラスター、ノードプールなどをあらかじめ設定しておく必要があります
（詳細はこの記事では省略します）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274034/3d579bf1-e027-896f-d7a3-b29aa2238db8.png)

### CloudBuildのトリガー設定
対象（連携する）リポジトリ、イベント、構成用yamlの指定などを行います。

イベントはCloudBuildを開始するためのリポジトリ上のアクションで
特定のブランチやタグなどのpushなどを設定できます。

以下の例では`release_.{7}`としていますが、
`release_(コミットhashの短縮版)`でタグ付けしたものをトリガーの条件としています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274034/e97b8273-89e8-baee-8db6-914b444e30c3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274034/7008eb52-49dd-00d9-5b6d-32601b4c616e.png)

### リポジトリのディレクトリ構成（サンプル）

```bash
├── cloud_build
│   ├── cloudbuild.yaml　　　・・・ CloudBuild構成用yaml
│   ├── apply.yaml　　　　　　・・・ GKEへのデプロイ用yaml（CloudBuild構成用yaml内から呼び出し）
│   └── dockerfile
│   │   ├── nginx
│   │   │   ├── Dockerfile　　・・・ CloudBuild内イメージビルド用
├── laravel　　　　　　　　　　　・・・アプリケーション（laravelプロジェクト）
│   ├── app
│   ├── bootstrap
│   ├── config
・
・
・
```
上記のように構成用yaml（`/cloud_build/cloudbuild.yaml`）を配置した場合は
トリガーに以下のように設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274034/edc11104-c5b8-8710-2a9c-a6b2fe40c83a.png)


### 構成用yamlの記述
実行するステップの作業を記述します

|キー|指定内容|
|-|-|
|name|ビルドの各ステップで利用する一般公開されているDockerイメージを指定します。<br>CloudBuildでは`gcr.io/cloud-builders/******`のようなイメージが用意されていて、例えば`gcr.io/cloud-builders/docker`を利用するとクラウドビルド上でDockerコマンドを実行することができます。|
|args|`name`に指定したイメージ実行時に渡す引数を記述します|
|id|ビルドステップの識別子となります|
|waitFor|ビルドステップの[順番の制御](https://cloud.google.com/build/docs/configuring-builds/configure-build-step-order?hl=ja)に利用できます|


```yaml
#######################################################################
# リポジトリ内から .dockerignoreファイルを削除
#######################################################################
steps:
- name: 'ubuntu'
  args:
    - bash
    - -c
    - 'rm -rf .dockerignore'
  id: 'rm-dockerignore'
#######################################################################
# イメージをビルド
# （$TAG_NAMEはGitコミットのタグの値、ビルドしたDockerイメージに同じタグ付け）
#######################################################################
- name: 'gcr.io/cloud-builders/docker'
  args:
    - build
    - -f
    - cloud_build/dockerfile/nginx/Dockerfile
    - --tag
    - asia.gcr.io/$PROJECT_ID/laravel:$TAG_NAME
    - .
  waitFor: ['rm-dockerignore']
#######################################################################
# ビルドしたイメージを container registry にプッシュ
#######################################################################
- name: 'gcr.io/cloud-builders/docker'
  args:
    - push
    - asia.gcr.io/$PROJECT_ID/laravel:$TAG_NAME
#######################################################################
# GKEデプロイ用のyaml内のコンテナイメージのタグを置換
#######################################################################
- name: 'ubuntu'
  args:
    - bash
    - -c
    - 'sed -i.bk s/latest/$TAG_NAME/g cloud_build/apply.yaml'
  id: 'laravel-build-done'
#######################################################################
# テスト実行
#######################################################################
- name: 'gcr.io/cloud-builders/docker'
  args:
    - run
    - --rm
    - --workdir=/var/www/html/laravel
    - --entrypoint=composer
    - asia.gcr.io/$PROJECT_ID/laravel:$TAG_NAME
    - test:coverage
  waitFor: ['laravel-build-done']
  id: 'phpunit-done'
#######################################################################
#gcloud認証
#######################################################################
- name: 'gcr.io/cloud-builders/gcloud'
  args:
  - beta
  - container
  - clusters
  - get-credentials
  - $_CLUSTER_NAME
  - --zone=$_ZONE
  - --project=$PROJECT_ID
  waitFor: ['-']
  id: 'gcloud-get-credentials'
#######################################################################
# アプリのデプロイ設定
#######################################################################
- name: 'gcr.io/cloud-builders/kubectl'
  args:
    - apply
    - --filename=cloud_build/apply.yaml
  env:
    - CLOUDSDK_COMPUTE_ZONE=$_ZONE
    - CLOUDSDK_CONTAINER_CLUSTER=$_CLUSTER_NAME
  waitFor: ['gcloud-get-credentials','phpunit-done']
#######################################################################
timeout: 1000s
```
yaml内に変数を埋め込んでいますが、CloudBuild実行時に値が置換されます。

https://cloud.google.com/build/docs/configuring-builds/substitute-variable-values?hl=ja

`$PROJECT_ID`はGCPのプロジェクトIDが設定されます。

`$TAG_NAME`はトリガーとなったGitリポジトリのタグ名が埋め込まれています。

`$_ZONE`、`$_CLUSTER_NAME`はそれぞれGKEのゾーン名、クラスタ名ですが、
CloudBuildのトリガー設定画面で設定しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274034/a031e91a-4e84-eec0-5ebc-233b97de3591.png)


### GitHubへのプッシュとトリガーの稼働の実際
トリガー発動のためのタグ付けします
コミットhashを元にタグ付けをしてみます（`git rev-parse --short`で短いコミットhashが取得できます)

```bash
$ git tag release_$(git rev-parse --short HEAD)
$ git log --oneline
* 459ddf9 (HEAD -> **, tag: release_459ddf9) *****
```

タグをプッシュします

```bash
$ git push origin ** --tags
Enumerating objects: 25, done.
Counting objects: 100% (25/25), done.
Delta compression using up to 6 threads
Compressing objects: 100% (13/13), done.
Writing objects: 100% (14/14), 1.40 KiB | 715.00 KiB/s, done.
Total 14 (delta 10), reused 0 (delta 0)
remote: Resolving deltas: 100% (10/10), completed with 9 local objects.
To github.com:*******/********.git
 * [new tag]         release_459ddf9 -> release_459ddf9
```

CloudBuildのビルドの詳細でビルドプロセスが確認できます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274034/3c6f0836-f58b-5dbe-c84a-356b0fb32298.png)

正常に完了すると全てのステップがグリーンになります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274034/095980f2-f76e-4834-417a-d0930efa8fc1.png)

最後のステップでGKEへデプロイを行っています。
`kubectl get pods -w`でGKEのポッドの状態をウォッチすると
新たにビルドされたイメージがデプロイされることで
元々稼働していたポッドが終了し
新たなポッドが生成されていることがわかります

```bash
$ kubectl get pods -w
NAME                                         READY   STATUS    RESTARTS   AGE
*****-******-857db5db5f-pmxkh               1/1     Running   0          3d6h
#元々稼働していたポッドが終了していく
*****-******-857db5db5f-pmxkh               1/1     Terminating   0          3d6h
*****-******-857db5db5f-pmxkh               0/1     Terminating   0          3d6h
*****-******-857db5db5f-pmxkh               0/1     Terminating   0          3d6h
*****-******-857db5db5f-pmxkh               0/1     Terminating   0          3d6h
#新たなポッドが立ち上がり始める
*****-******-857d58675c-gcmgb               0/1     Pending       0          0s
*****-******-857d58675c-gcmgb               0/1     Pending       0          0s
*****-******-857d58675c-gcmgb               0/1     ContainerCreating   0          0s
#新たなポッドが起動完了
*****-******-857d58675c-gcmgb               1/1     Running             0          25s
```

以下はテストが失敗した場合のパターンです。
テストステップの失敗によりCloudBuildも失敗となり、
アプリケーションのデプロイもされることがありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274034/d0df169e-37f0-dd04-35b2-1df417637c20.png)