decadence

個人のメモ帳

CircleCIでbuildからGKEへのdeployまで

tl;dr

GKEが楽しいので、趣味productを以下のように環境にした

  • Scala(sbt)とTypeScript(npm/angular2)で書かれたアプリケーション
  • CircleCIでmaster push時に以下の処理
    • テスト回す
    • docker repositoryにimageをpush
    • kubectlで最新imageを用いてdeploy
  • sslkube-legoを使う

kubenetes 設定

基本的にkube-legoのサンプルと同じようなファイルがある。このあたりは予めGKEのclusterを作成した上で既に適用してる。

  • app
    • namespace.yaml, deployment.yaml, service.yaml, ingress-tls.yaml
  • nginx: ...
  • kube-lego: ...
  • ...

applicationの変更時はappのdeployment.yamlの内容を以下のように変えて反映する。

kubectl patch deployment app -p '{"spec":{"template":{"spec":{"containers":[{"name":"app","image":"asia.gcr.io/project/app:${CIRCLE_SHA1}"}]}}}}'

kubectl set image ...でも良い

後述するが、常にlatestのdocker imageを用意するようにしてるので、deployment.yamlの中のimageにはlatestを指すようにしている。

他にもCloudSQLを利用するためにcloud_sql_proxy立てたり、ScheduleJobを設定してたりする。

CircleCI

CIでテスト後にdocker imageのpush、最新imageを用いたdeployまで行う。

実行環境

テストを通す+buildのために必要な環境が整ったDocker Imageを作成し、CircleCIではそちらのimageを利用。以下のdocker imageはsbt, npm, ng, gcloud, dockerといったコマンドが利用出来る環境になっている。正直な所実際にdeployされるDocker Imageはもっと軽量な別物になるし、このImageは適当で良い。

https://hub.docker.com/r/krrrr38/sbt-ng-gcloud-kubectl/

GCP Service Account

CIの中で、docker imageのpush、GKEのclusterへの更新処理を行うため、それらの機能のみを持つGCP Service Accountを予め作成。認証情報を含んだjsonbase64して、GCLOUD_SERVICE_KEYという環境変数へ設定。

Authentication with Google Cloud Platform - CircleCI

.circleci/config.yaml

stepsとしては以下のものがある。

- checkout
- restore_cache:
- run: cat /dev/null | sbt test:compile
- run: cat /dev/null | npm --prefix ui/web install
- save_cache:
- run: cat /dev/null | sbt test:test
- run: cat /dev/null | npm --prefix ui/web test
- setup_remote_docker
- deploy: ./build.sh -d -l -s ${CIRCLE_SHA1}
  • build.shについて
    • frontのコードをbuild: ng build --prod
    • server側のコードをbuild+実行に必要なファイルを特定のdirectory以下にまとめる: sbt stage
    • 本番環境用のdocker imageを作成
      • 先に作成したdeploy用のdirectoryとjava実行環境のみあるdocker imageを作成
      • docker build -t "asia.gcr.io/${PROJECT}/${MODULE}:${SHA1}" -f "deploy/${MODULE}/Dockerfile" .
    • service accountの認証情報を読み込み
      • echo $GCLOUD_SERVICE_KEY | base64 --decode > gcp-authorization.json
      • gcloud auth activate-service-account --key-file gcp-authorization.json
    • 作成したdocker imageをGCP Container Registryへpush
      • ${SHA1}latestの2つのimageをpushしている
        • latestがきちんと最新のものになるようにしつつ、反映時には${SHA1}を利用している
      • gcloud docker -- push "asia.gcr.io/${PROJECT}/${MODULE}:${SHA1}"
      • gcloud docker -- push "asia.gcr.io/${PROJECT}/${MODULE}:latest"
    • deploy
      • k8s cluster情報の取得
        • gcloud container clusters get-credentials ${CLUSTER_NAME} --project $PROJECT --zone $PROJECT_ZONE
      • 変更の反映
        • kubectl patch deployment app -p '{"spec":{"template":{"spec":{"containers":[{"name":"app","image":"asia.gcr.io/project/app:${CIRCLE_SHA1}"}]}}}}'

build.shといいながらoption付けて、deployまでしてしまった

余談

cloud-buildersでimageの作成など考えていたが、依存libraryのcacheをどこに置くかなど悩んだ結果あまり答えがでなかった。CircleCIなら必要なデータは普通にcache出来るし、CI環境からそのままdocker imageを作成してpush出来るので、まぁこれで良いかという気持ち。

k8s、学習コストはあるもの、今後はdocker imageを手軽にほいほいGKEに投げていけるので、雑多な趣味環境を扱うにも良い。そもそも雑多なやつならgolangとかのがメモリ消費少なくなりやすいし良いんですけどね。

Ref