Cloud BuildとkustomizeでGKEのCDパイプラインを作る

f:id:y-ohgi:20180911204409p:plain

概要

Cloud BuildでCDをしたいので、する。
ついでにkustomizeも組み合わせる。

※プロダクションに導入するには権限がガバガバなので適宜修正。

今回のリポジトリ

github.com

目次

やること

スタック

  • GitHub
  • Cloud Source Repositories
    • リポジトリGitHubとCloud Source Repositoriesの両方用いる理由は完全にお仕事に導入する場合この構成になるため。
    • お仕事先はGitHub Enterpriseを使用しているが、GitHub EnterpriseとCloud Buildは直接連携できないため間にCloud Source Repositoriesを噛ます
  • CircleCI
    • GitHubからCloud Source Repositoriesにコードを上げる用途
  • Cloud Build
    • コンテナのビルドと配布・GKEへのデプロイ
  • Container Registry
    • コンテナの配布先
  • GKE
    • 雑に立てておく。
    • 1.10-gkeで雑に立てた。
      • 他に変更した設定はnodeのオートスケールを有効にして最小台数を1にした。
      • プリエンプティブで立てようとしたら余ってないとか言われてつらい
  • kustomize

CDの流れ

  1. CircleCIでGitHubからCloud Source Repositoriesへpush
    • CircleCIはtagがpushされたら走らせる
  2. Cloud Source RepositoriesをトリガーにCloud Buildを走らせる
    • Cloud Source Repositoriesへ上がったコードをCloud Buildを用いてContainer Registoryへデリバリ
  3. Cloud Buildでデプロイ
    • kustomizeとkubectlを用いてGKEへデプロイを行う

1. CircleCIでGitHubからCloud Source Repositoriesへpush

1.1. コードを用意してGitHubへpush

雑にコードを書く。nodeが書きたかったのでnode
レスポンスに環境変数STAGE を返すようにする。

index.js

const express = require('express')
const app = express()
const ENV = process.env

app.get('/', (req, res) => {
  res.send(`hello world. ${ENV.STAGE}`)
})

app.listen(3000, () => {
  console.log('listening: 0.0.0.0:3000')
})

package.json

{
  "name": "cloudbuild-practice",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "express": "^4.16.3"
  }
}

Dockerfile

FROM node:alpine

WORKDIR /app

COPY . /app

RUN yarn install

ENV STAGE=develop

EXPOSE 3000

CMD ["yarn", "run", "start"]

動作確認

$ docker build -t myapp .
$ docker run -d -e STAGE=local -p 3000:3000 myapp
$ curl localhost:3000
hello world. local

Dockerをrootで起動してたり細かい所がアレだけど動けばいいの精神。
雑にGitHubへpush.
https://github.com/y-ohgi/cloudbuild-practice

1.2. Cloud Source Repositoriesでリポジトリの作成

$ gcloud source repos create cloudbuild-practice
$ gcloud source repos list
REPO_NAME            PROJECT_ID         URL
cloudbuild-practice  xxxxxxxxxx  https://source.developers.google.com/p/xxxxxxxxxx/r/cloudbuild-practice

1.3. GCPのサービスアカウントを作成

Cloud Source Repositoriesへアクセスするためのサービスアカウントを作成する。

$ export GCLOUD_PROJECT=$(gcloud config get-value project)
$ gcloud iam service-accounts create repositories-access --display-name "repositories-access"
$ gcloud projects add-iam-policy-binding ${GCLOUD_PROJECT} --member serviceAccount:repositories-access@${GCLOUD_PROJECT}.iam.gserviceaccount.com --role roles/source.admin
$ gcloud iam service-accounts keys create repositories-access.json --iam-account repositories-access@${GCLOUD_PROJECT}.iam.gserviceaccount.com

roles/source.admin の権限は与え過ぎな気がするけど同じく動けばいいの精神。

1.3. CircleCIのアクティベートと環境変数の登録

CircleCIのWebコンソールからGitHubで作成したプロジェクトを追加。
環境変数に先程取得した repositories-access.jsonbase64化したものとプロジェクト名を追加

$ base64 repositories-access.json | pbcopy
# "SOURCE_REPOSITORY_SERVICE_ACCOUNT"のキー名で環境変数を追加
$ gcloud config get-value project | pbcopy
# "GCP_PROJECT"のキー名で環境変数を追加
$ gcloud source repos list
# "SOURCE_REPOSITORY_URL"のキー名でhttpsから始まるURLを環境変数へ追加

f:id:y-ohgi:20180906080012p:plain

1.4. CircleCIのコンフィグを記述

.circleci/config.yml へコンフィグを追加

version: 2
jobs:
  push_code:
    docker:
      - image: google/cloud-sdk:alpine
    steps:
      - checkout
      - run:
          name: setup gcloud command
          command: |
            echo "${SOURCE_REPOSITORY_SERVICE_ACCOUNT}" | base64 -d > /service-account.json
            gcloud auth activate-service-account --project=${GCP_PROJECT} --key-file=/service-account.json
      - run:
          name: push source code
          command: |
            git config credential.helper gcloud.sh
            git remote add google ${SOURCE_REPOSITORY_URL}
            git push --all google

workflows:
  version: 2
  push_code:
    jobs:
      - push_code:
          filters:
            tags:
              only: /.*/
            branches:
              ignore: /.*/

gitへpushして試しにタグを付与して動作確認

$ git tag v0.0.0
$ git push origin v0.0.0

良さげ f:id:y-ohgi:20180906080137p:plain f:id:y-ohgi:20180906080145p:plain

この .circleci/config.yml を一発で書けたの誰かほめて

2. Cloud Source RepositoriesをトリガーにCloud Buildを走らせる

3章でデプロイ。2章はRegistryへのデリバリまで。

PRはこれ。 https://github.com/y-ohgi/cloudbuild-practice/pull/1

2.1. cloudbuild.yamlの用意

Cloud Build用の設定ファイルである cloudbuild.yaml を用意する。

cloudbuild.yaml

steps:
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'build'
      - '--tag=asia.gcr.io/$PROJECT_ID/cloudbuild-practice:$TAG_NAME'
      - '--tag=asia.gcr.io/$PROJECT_ID/cloudbuild-practice:latest'
      - '.'
images:
  - 'asia.gcr.io/$PROJECT_ID/cloudbuild-practice:$TAG_NAME'
  - 'asia.gcr.io/$PROJECT_ID/cloudbuild-practice:latest'

2.2. CircleCIでtagもpushするよう修正

      - run:
          name: push source code
          command: |
            git config credential.helper gcloud.sh
            git remote add google ${SOURCE_REPOSITORY_URL}
            git push --all google
+            git fetch --tags
+            git push google --tags

2.3. Cloud Buildの設定

Webコンソールからぽちぽち

"Cloud Source Repositories"をソースに選択
f:id:y-ohgi:20180906080216p:plain

コードをpushしたリポジトリを選択
f:id:y-ohgi:20180906080224p:plain

トリガーをタグにしてcloudbuild.yamlを使用する
f:id:y-ohgi:20180906080248p:plain

2.4. デリバリできている確認

$ git tag origin v0.0.4
$ git push origin v0.0.4

しばらく待ってContainer Registoryを確認。あった。
f:id:y-ohgi:20180906080301p:plain

3. Cloud Buildでデプロイ

3章のPRはこれ。
CloudBuildでデプロイの実行 by y-ohgi · Pull Request #3 · y-ohgi/cloudbuild-practice · GitHub

3.1. GKEの構築

以下の条件でGKE環境を用意する。

  • GKEのクラスタは用意済み
  • developとproductionの2環境を用意
    • web-develop/web-productionを用意する。
      • $ kubectl create namespace web-production
      • $ kubectl create namespace web-develop
  • 環境はnamespaceで分ける
  • 最低限の労力でやる。
$ gcloud container clusters get-credentials mycluster

デリバリしたコンテナの動作確認

$ cat <<EOL | kubectl apply -f 
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    run: web
  name: web
spec:
  replicas: 1
  selector:
    matchLabels:
      run: web
  template:
    metadata:
      labels:
        run: web
    spec:
      containers:
      - name: web
        image: asia.gcr.io/ohgi-gcp-practice/cloudbuild-practice:latest
        env:
          - name: STAGE
            value: default
        ports:
          - containerPort: 3000

---
apiVersion: v1
kind: Service
metadata:
  name: web
  labels:
    run: web
spec:
  ports:
  - port: 3000
    protocol: TCP
  selector:
    run: web
EOL
$ kubectl port-forward <web POD NAME> 3000:3000 &
$ curl localhost:3000
hello world. default

※ なんか頑張ってGCPのプロジェクト名隠してた記憶あるけど、まあ一家。

3.2. kustomizeでGKEのマニフェストを作成

リポジトリkubernetesディレクトリを作成し、マニフェストはここへ配置。
yamlペタペタすると長くなるので割愛。PRはこれ
https://github.com/y-ohgi/cloudbuild-practice/pull/2

ディレクトリ構成

$ tree kubernetes/
kubernetes/
|-- base
|   |-- kustomization.yaml
|   `-- manifest.yaml
`-- overlays
    |-- develop
    |   |-- env_stage.yaml
    |   `-- kustomization.yaml
    `-- production
        |-- env_stage.yaml
        `-- kustomization.yaml

行っていることは以下の3つ

  1. コンテナのタグをkustomizeで管理
  2. 環境変数 STAGE を環境毎に変更
    • develop環境: STAGE=develop
    • production環境: STAGE=production
  3. namespaceを環境毎に変更
    • develop環境: web-develop
    • production環境: web-production

3.3. デプロイスクリプトを用意

CloudBuild公式で提供されているkubectl用イメージ gcr.io/cloud-builders/kubectl を使いたいがkustomizeが入ってないので自分で入れる。
kubectlイメージはGKEへの認証系のスクリプトを提供してくれているのが魅力

#!/bin/bash

# e.g.) ./scripts/kustomize-apply.sh kubernetes/overlays/develop/

KUSTOMIZE_OVERLAY=$1

KUSTOMIZE_VERSION=1.0.7

KUBECTL_CMD="/builder/kubectl.bash"

if which kustomize ; then
  kustomize build $KUSTOMIZE_OVERLAY | ${KUBECTL_CMD} apply -f -
  exit 0
fi

curl -s https://api.github.com/repos/kubernetes-sigs/kustomize/releases/latest |\
  grep browser_download |\
  grep linux |\
  cut -d '"' -f 4 |\
  xargs curl -L -o /usr/local/bin/kustomize
chmod +x /usr/local/bin/kustomize

kustomize build $KUSTOMIZE_OVERLAY | ${KUBECTL_CMD} apply -f -

3.4. CloudBuildへKubernetesへの権限を付与

Webコンソールへ移動し、CloudBuildのサービスアカウント 01234567@cloudbuild.gserviceaccount.com へ「Kubernetes Engine管理者」を付与。

忘れていると以下のエラーと出会える

Running: gcloud container clusters get-credentials --project="ohgi-gcp-practice" --zone="asia-northeast1-a" "mycluster"
Fetching cluster endpoint and auth data.
ERROR: (gcloud.container.clusters.get-credentials) ResponseError: code=403, message=EXTERNAL: Required "container.clusters.get" permission(s) for "projects/ohgi-gcp-practice/zones/asia-northeast1-a/clusters/mycluster".

3.5. デプロイの実行

今までのコードをpush。
タグを付与してデプロイされるのを祈る。

$ git tag v1.0.0
$ git push origin --tags

f:id:y-ohgi:20180906075939p:plain

くりあー!

3.6. 動作確認

develop環境の動作確認

$ kubens web-develop
$ kubectl get svc,pods
NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/web   ClusterIP   10.47.250.163   <none>        3000/TCP   2m

NAME                       READY     STATUS    RESTARTS   AGE
pod/web-7bbf794f95-bhwcl   1/1       Running   0          2m
$ kubectl port-forward pod/web-7bbf794f95-bhwcl 3000:3000 &
$ curl localhost:3000
hello world. develop
$ fg
C-c

production環境の動作確認

$ kubens web-production
$ kubectl get svc,pods
NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/web   ClusterIP   10.47.252.144   <none>        3000/TCP   4m

NAME                      READY     STATUS    RESTARTS   AGE
pod/web-d45b66c49-vlpxw   1/1       Running   0          4m
$ kubectl port-forward pod/web-d45b66c49-vlpxw 3000:3000 &
$ curl localhost:3000
hello world. production
$ fg
C-c

所感

思ってたよりCloud Buildがかゆいところに手が届くので便利。
お仕事でも導入したい。