GKE+CircleCI 2.0で継続的デプロイ可能なアプリケーションをシュッと作る

こんにちは:) Pairsエンジニアの竹内です!
この記事は、eureka Engineering Advent Calendar 2017 8日目の記事です。
前回は高橋さんのPairs海外版におけるシステム構成の変遷を暴露するぜでした。

tl;dr

  • GitHubへコードがPushされる度にGKEへデプロイ出来るプロジェクトを手順を追って説明します。
  • Kubernetesのレシピも一緒にデプロイ出来るためReplicationの増減等も簡単に変更出来ます。
  • ↑の環境が整ったプロジェクトをLeiningenテンプレートのspellcardを使って簡単に作れます。

はじめに

Google Kubernetes Engine(GKE)は、コンテナ化されたアプリケーションを複数のノードへデプロイして運用出来るKubernetesベースの便利なSaasです。
普段の業務ではフロントエンドでTypeScriptを書いていたり、サーバーサイドでGoを書いたりする事が多い私ですが、
個人的なウェブサービスの運用や自己研鑽の為にここ8ヶ月程GKEを利用しています。


自分のGKEクラスタでは表題のようにCircleCIを使って継続的デプロイ可能な環境を実現しているのですが、
環境構築にあたり参考にしたCircleCI公式ドキュメントが旧版のCircleCIを使ったドキュメントであったため、自前でCircleCI 2.0向けの設定に書き換えて運用しています。


そんな訳で、今回はその自前の設定を使ってアプリケーションのデプロイ環境を構築するまでの手順を紹介致します。

実現する継続的デプロイ可能な環境

上記の図のように、GitHubへKubernetesのレシピやアプリケーションコードの変更をpushする度に

  1. CircleCI上でコンテナをビルド
  2. ビルドしたコンテナをCircleCI上でテスト
  3. テストが通ったコンテナをGKEがクラスタへデプロイ

と言った形で自動でGKEへアプリケーションがデプロイされるようになります。


続いて、この環境を実現するための手順を一つずつ追っていきます。

1. GKEクラスタを準備する

まずはデプロイ先のGKEクラスタを用意します。
初めてGKEに触れるのであれば、こちらのGoogle Kubernetes Engine クイックスタートgcloudコマンドラインツールのデフォルト設定 までを元に準備をすると良いでしょう。


また、もしあなたが作りたいアプリケーションをClojureで書く予定であれば、
Leiningenテンプレートのspellcardを用いて以下のコマンドだけでこの後の手順2,3を完了出来ます。

lein new spellcard <プロジェクト名> <gcpのプロジェクトID> <GKEクラスタ名>
chmod +x <プロジェクト名>/deploy.sh

以下に、手動で準備する場合についても手順を書いて行きます。

2. アプリケーション向けKubernetesレシピ(Service・Deployment)を用意する

※これから先の手順では、簡単の為に素のnginxコンテナをデプロイするものとして話を進めて行きます。


アプリケーションコンテナのデプロイに必要なKubernetesのDeploymentと、
ロードバランサの役割を果たすKubernetesのServiceを準備します。

sample/k8s/deployment.yml

apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: sample
  labels:
    app: sample
spec:
  replicas: 2 # レプリケーションの増減数
  selector:
    matchLabels:
      app: sample
  template:
    metadata:
      labels:
        app: sample
    spec:
      containers:
      - name: sample
        image: asia.gcr.io/${PROJECT_NAME}/sample:${CIRCLE_SHA1}
        ports:
        - containerPort: 80

sample/k8s/service.yml

apiVersion: v1
kind: Service
metadata:
  name: sample
spec:
  selector:
    app: sample
  type: NodePort
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      name: http

この2つを以下のように配置します。

sample
└── k8s
    ├── deployment.yml <-
    └── service.yml <-

GCPのproject-idは my-project-id 、アプリケーション名を sample としています。

3. CircleCIの設定ファイルを準備する

CircleCIからコンテナのビルド・テスト・GKEへのデプロイを行うため、

  • CircleCIの設定ファイル本体の .circleci/config.yml ファイル
  • コンテナのレシピとなる Dockerfile
  • コンテナへのテストを行うための docker-compose.test.yml ファイル
  • コンテナのデプロイを行うための deploy.sh ファイル

を追加します。

.circleci/config.yml

version: 2
jobs:
  build:
    working_directory: /app
    environment:
      PROJECT_NAME: my-project-id
      CLUSTER_NAME: my-cluster-name
      CLOUDSDK_COMPUTE_ZONE: asia-northeast1-a
      DEBIAN_FRONTEND: noninteractive
      GOOGLE_APPLICATION_CREDENTIALS: ${HOME}/account-auth.json
    docker:
      - image: google/cloud-sdk:171.0.0-alpine
    steps:
      - checkout
      - setup_remote_docker
      - restore_cache:
          keys:
            - v1-{{ .Branch }}
          paths:
            - /caches/app.tar
      - run:
          name: Install dependencies
          command: |
            apk add --no-cache 
              py-pip=9.0.0-r1 
              gettext
            pip install 
              docker-compose==1.12.0
            gcloud components install kubectl
      - run:
          name: Install Docker client
          command: |
            set -x
            VER="17.05.0-ce"
            curl -L -o /tmp/docker-$VER.tgz https://get.docker.com/builds/Linux/x86_64/docker-$VER.tgz
            tar -xz -C /tmp -f /tmp/docker-$VER.tgz
            mv /tmp/docker/* /usr/bin
      - run:
          name: Check docker version
          command: |
            docker version
      - run:
          name: Load Docker image layer cache
          command: |
            set +o pipefail
            docker load -i /caches/app.tar | true
      - run:
          name: Build application Docker image
          command: |
            docker build --cache-from=asia.gcr.io/${PROJECT_NAME}/sample:latest -t asia.gcr.io/${PROJECT_NAME}/sample:$CIRCLE_SHA1 .
            docker tag asia.gcr.io/${PROJECT_NAME}/sample:$CIRCLE_SHA1 asia.gcr.io/${PROJECT_NAME}/sample:latest
      - run:
          name: Save Docker image layer cache
          command: |
            mkdir -p /caches
            docker save -o /caches/app.tar asia.gcr.io/${PROJECT_NAME}/sample:latest
      - save_cache:
          key: v1-{{ .Branch }}-{{ epoch }}
          paths:
            - /caches/app.tar
      - run:
          name: Run tests
          command: |
            docker-compose -f docker-compose.test.yml run --rm sample
      - deploy:
          name: Deploy application Docker image
          command: |
            if [ "${CIRCLE_BRANCH}" == "master" ]; then
              echo $ACCT_AUTH | base64 -d > ${HOME}/account-auth.json
              gcloud auth activate-service-account --key-file ${HOME}/account-auth.json
              gcloud config set project $PROJECT_NAME
              gcloud --quiet config set container/cluster $CLUSTER_NAME
              gcloud config set compute/zone ${CLOUDSDK_COMPUTE_ZONE}
              gcloud --quiet container clusters get-credentials $CLUSTER_NAME
              gcloud config set container/use_client_certificate True
              ./deploy.sh
            fi

Dockerfile

FROM nginx:1.13.7

docker-compose.test.yml

version: "2"
services:
  sample:
    image: asia.gcr.io/${PROJECT_NAME}/sample:$CIRCLE_SHA1
    command: /bin/bash -c "nginx -t -c /etc/nginx/nginx.conf"

deploy.sh

#!/bin/bash

# Exit on any error
set -e

for f in k8s/*.yml
do
    envsubst < $f > "generated-$(basename $f)"
done
gcloud docker -- push asia.gcr.io/${PROJECT_NAME}/sample:$CIRCLE_SHA1
kubectl apply -f generated-deployment.yml --record
kubectl apply -f generated-service.yml

以上のファイルもプロジェクトフォルダ内に配置します。

sample
├── .circleci
│   └── config.yml <-
├── deploy.sh <-
├── docker-compose.test.yml <-
├── Dockerfile <-
└── k8s
    ├── deployment.yml
    └── service.yml

また、 deploy.sh がCircleCIから実行できるように実行権限を付与しておきます。

chmod +x deploy.sh

ここまでの準備が出来たら、GitHubレポジトリを立てて一旦コードをPushします。
おおむねこのレポジトリの状態になるはずです。


GitHubレポジトリを準備出来たら、CircleCIを連携させます。

4. CircleCIをGitHubレポジトリと連携させる

CircleCIの連携はCircleCIのダッシュボードから簡単に行なえますが、CircleCIからGKEへデプロイするためのService Account Keyを発行して登録する必要があります。


まず、Service Account Keyはhttps://console.developers.google.com/apis/credentials/serviceaccountkey?project=_のページから以下の手順で準備できます。

  1. GKEクラスタを作成したプロジェクトを選択
  2. 「認証情報を作成」->「サービスアカウントキー」
  3. 「サービスアカウント」を新しい「新しいサービスアカウント」へ選択し、「役割」に「Kubernetes Engine」-> 「Kubernetes Developer」と「ストレージ」-> 「ストレージ管理者」を追加してJSONでキーを作成する

Service Account Keyが準備出来たら、 base64 でbase64形式の文字列にして、CircleCIのダッシュボードから環境変数 $ACCT_AUTH として登録します。


ここまで準備が出来れば、後はGitHubへPushする度に自動でアプリケーションがGKEへデプロイされます。

おわりに

やや駆け足ですが、GKEとCircleCI 2.0を使った継続的デプロイ可能なアプリケーションを構築するまでの手順を紹介いたしました。


GKEを利用し始めてから、レプリケーションの増減や各種ミドルウェアの導入等もアプリケーションと同じ感覚で変更出来るようになり、大変快適な開発環境を得られているので
是非一度お試しいただければと思います:)


また途中にも登場しましたが、こちらのレポジトリに今回のコードサンプルが置いてありますのでご参考ください。


それでは、またの機会にお会いしましょう!


次回は山下さんの Go言語のプロファイリングツール、「pprofのWeb UI」がめちゃくちゃ便利なので紹介する です、お楽しみに!

  • このエントリーをはてなブックマークに追加

エウレカでは、一緒に働いていただける方を絶賛募集中です。募集中の職種はこちらからご確認ください!皆様のエントリーをお待ちしております!

Recommend

UI改善における仮説検証の3つのポイント

ネイティブエンジニアが半年間Webエンジニアとして働いてきた話