Gitlab CIを利用したGCP(GKE)への自動デプロイ

この記事は [Enigmo Advent Calendar 2018](https://qiita.com/advent-calendar/2018/enigmo) の20日目の記事です。

# 概要

GCP(GKE)を利用してログからユーザ属性を機械学習により予測し、
その結果をAPIで返却するシステムを構築しました。
運用していく上でCI/CDツールは何にしようかなぁというところで、
今後社内でGitlabを使っていくという流れがあったのと
GKEとの相性も良さそうということでGitlab CIを利用することにしました。[^1]

今回はGitlabとGCP(GKE)環境を連携する方法と、
Gitlab CIのキモとなる.gitlab-ci.ymlの内容についてまとめたいと思います。


# システム構成

簡単にデプロイ対象のシステム構成を紹介します。
赤い枠で示したものが今回の作業対象になります。

![gitlab_arch.png](https://qiita-image-store.s3.amazonaws.com/0/65095/dcd5cc0f-4ff8-f724-2e5d-3fdc1d158707.png)



# 環境条件

今回は下記の環境を使用しています。
Gitlabについては毎月バージョンが更新されるので、旧バージョンを利用されている方などは若干UI等が変わっているかもしれません。

* Gitlab  
GitLab Community Edition 11.5.3
* GKE  
クラスタバージョン 1.10.9-gke.5


# CI/CDのフロー

CI/CDは下記のフローで行います。

### テスト環境
* developブランチへのcommit&push  

**以降は自動**

* dockerイメージのbuild
* dockerイメージをGCSへpush
* GKE(CronJon)へのデプロイ
* GKE(Deployment)へのデプロイ

## 本番環境

**本番はすべて手動**

* developブランチからmasterブランチへのMR作成
* マージ
* GKE(CronJon)へのデプロイ
* GKE(Deployment)へのデプロイ 

# 設定手順

Gitlab CIを利用するにあたり必要な設定を行っていきます。

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

GCP(GKE)とGitlab CIとの連携のためにGCPコンソールからサービスアカウントを作成します。

* ここでは仮に「Gitlab CI」という名前のサービスアカウントを作成します。
* 役割(role)に下記の2つを付与してください。
    * Kubernetes Engine 開発者
    * ストレージ管理者
* キーのタイプでJSONを選択し、キーを作成し、ローカルにキーファイルをdownloadします。


## 変数の設定

GKEへの認証情報をGitlabに登録します。
対象のプロジェクト(リポジトリ)のSettings -> CI/CD -> 変数にて、
キーに「SERVICE_ACCOUNT_KEY」を入力し、
値に先ほどdownloadしたキーファイルの中身をそのままコピペします。

下記の例では「SERVICE_ACCOUNT_KEY_PROD」というキーもありますが、
これは本番とテストでプロジェクトを分けているためです
1つのプロジェクト配下の場合は1つのキーで問題ないと思います。

![gitlab_ci_settings.png](https://qiita-image-store.s3.amazonaws.com/0/65095/17fa6a28-fd73-af96-4c11-9d015772699d.png)


## Specific Runnersの登録

下記コマンドにてSpecific Runnerを登録します。
今回はdocker in docker方式で行います。
shell executorを利用する場合は[こちら](https://docs.gitlab.com/ee/ci/docker/using_docker_build.html)を参考にしてください。

```
$ sudo gitlab-runner register -n \
> --url [URL] \
> --registration-token [TOKEN] \
> --executor docker \
> --description "docker-runner" \
> --tag-list "gke" \
> --docker-image "docker:stable" \
> --docker-privileged
```

※指定するURLとTOKENについてはGitlabのSettings -> CI/CD -> Runnerの項目を参照してください。

今回はオンプレ版のGitlab環境を使用していますが、
[gitlab.com](https://about.gitlab.com/)でShared Runnerを利用する場合には
こちらの手順は不要です。



## .gitlab-ci.ymlの作成



今回はCronjobへのデプロイ定義を例に説明します。
Deploymentへのデプロイ定義も基本的に同じです。

* 共通設定
    * テスト/本番環境など、デプロイ対象の環境が複数ある場合にはどうしても定義が冗長的になります。
    * それを解消するために、下記ではYAMLのアンカー/エイリアスとGitlabのextends(v11.3で追加)という機能を利用して、共通的な定義はまとめるようにしています。

```yaml
.auth_gke: &auth_gke |
  gcloud auth activate-service-account --key-file=key.json
  gcloud config set project ${PRJ_ID}
  gcloud config set container/cluster ${CLUSTER_NAME}
  gcloud config set compute/zone ${CLUSTER_ZONE}
  gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${CLUSTER_ZONE}

.setting_dev:
  environment: develop
  variables:
    CI_DEBUG_TRACE: 'true'
    PRJ_ID: 'XXXXX-dev'
    CLUSTER_NAME: 'dev-XXXX'
    CLUSTER_ZONE: 'asia-northeast1-a'
    GCS_KEY: 'XXXX.json'
    ENV_CONFIG: '/PATH/conf/settings_dev.json'

.setting_prod:
  environment: production
  variables:
    PRJ_ID: 'XXXXX-prod'
    CLUSTER_NAME: 'XXXXX'
    CLUSTER_ZONE: 'asia-northeast1-a'
    GCS_KEY: 'XXXX.json'
    ENV_CONFIG: '/PATH/conf/settings_prod.json'

services:
  - docker:dind

stages:
  - build
  - deploy
```

* ジョブ定義①  
ここではdockerイメージのbuildと、GCRへのコンテナイメージのpushを行っています。
  * extendsで先程定義した内容を継承しています。
  * only:changesで該当のファイルに変更があった場合のみジョブが実行されるようになります

```yaml
docker build_base:
  extends: .setting_dev
  image: 'docker:stable'
  stage: build
  tags:
    - gke
  script:
    - docker info
    # Build the image
    - docker build --cache-from ${IMAGE_TAG} -t ${IMAGE_TAG}:${IMAGE_VER} .
    # Log in to Google Container Registry
    - echo "$SERVICE_ACCOUNT_KEY" > key.json
    # - docker login -u _json_key --password-stdin https://asia.gcr.io < key.json
    - docker login -u _json_key -p "$SERVICE_ACCOUNT_KEY" https://asia.gcr.io
    # Push the image
    - docker push ${IMAGE_TAG}:${IMAGE_VER}
  only:
    refs:
      - develop
    changes:
      - Dockerfile
      - bin/*
      - conf/*
      - requirements.txt

```


* ジョブ定義②  
ここではテスト/本番環境に対する2つのCronJobリソースへのデプロイを行います。
  * 本番環境へはwhen: manualを定義して手動でデプロイするようにしています。

```yaml
deploy_model_builder_dev:
  extends: .setting_dev
  image: 'claranet/gcloud-kubectl-docker:1.2.2'
  stage: deploy
  tags:
    - gke
  script:
    # Authenticate with GKE
    - echo "$SERVICE_ACCOUNT_KEY" > key.json
    - *auth_gke
    # set up CronJob
    - cat kubernetes/XXXX-job.yaml | envsubst | kubectl apply -f -
  only:
    refs:
      - develop
    changes:
      - .gitlab-ci.yml
      - kubernetes/XXXX-job.yaml

deploy_bulk_judgement_dev:
  extends: .setting_dev
  image: 'claranet/gcloud-kubectl-docker:1.2.2'
  stage: deploy
  tags:
    - gke
  script:
    - echo "$SERVICE_ACCOUNT_KEY" > key.json
    - *auth_gke
    # set up CronJob
    - cat kubernetes/XXXX-job.yaml | envsubst | kubectl apply -f -
  only:
    refs:
      - develop
    changes:
      - .gitlab-ci.yml
      - kubernetes/XXXX-job.yaml

deploy_model_builder_prod:
  extends: .setting_prod
  image: 'claranet/gcloud-kubectl-docker:1.2.2'
  stage: deploy
  tags:
    - gke
  script:
    # Authenticate with GKE
    - echo "$SERVICE_ACCOUNT_KEY_PROD" > key.json
    - *auth_gke
    # set up CronJob
    - cat kubernetes/XXXX-job.yaml | envsubst | kubectl apply -f -
  only:
    - master
  when: manual

deploy_bulk_judgement_prod:
  extends: .setting_prod
  image: 'claranet/gcloud-kubectl-docker:1.2.2'
  stage: deploy
  tags:
    - gke
  script:
    # Authenticate with GKE
    - echo "$SERVICE_ACCOUNT_KEY_PROD" > key.json
    - *auth_gke
    # set up CronJob
    - cat kubernetes/XXXX-job.yaml | envsubst | kubectl apply -f -
  only:
    - master
  when: manual

```

# ロールバック

せっかくなのでロールバックの手順についても記載しておきます。
Gitlabの左メニューから運用 -> 環境とたどっていくと、下記のような画面が表示されます。


![gitlab_op_001.png](https://qiita-image-store.s3.amazonaws.com/0/65095/3e9ae207-7b66-931b-111d-c7716d02e80b.png)

 
上記で記載した.gitlab-ci.ymlにenvironmentという定義があると思いますが、
そこで定義した文字列が環境のところに表示されているのがわかると思います。
たとえばここでproductionをクリックします。
すると下記の画面に遷移します。


![gitlab_op_002.png](https://qiita-image-store.s3.amazonaws.com/0/65095/c3a94737-a89e-ffac-a9d6-3967d42e2a6a.png)


この一覧はproductionに対するコミットの一覧です。
たとえば直前のコミットにロールバックしたい場合は赤枠のところをクリックすればOKです。
もちろん、この画面から任意のコミットを選択して再デプロイすることも可能です。

environmentの定義があることで環境毎のコミット履歴が一覧で見れるかつ、
そこからのデプロイ、ロールバックも簡単に行えます。
複数環境がある場合にはぜひ設定することをおすすめします。

# まとめ

* 今回、Gitlab CIを初めて利用してみましたが、特に癖もなく使いやすかったです。
* Auto DevOpsや、先日発表されたGitLab Serverlessなど、まだまだアツい機能があるので、時間を見つけて検証してみようと思います。



[^1]: 当初はGitlabのAuto Devopsの利用を考えていましたが、検討時点では利用しているGitlabのバージョンが古かったため、ひとまずGitlab CIを利用することにしました。