始めに
先進サービス開発事業部の山岡です。
私がバックエンドを開発しているNEIGHBORSはAppEngine(Go)の上に構築されています。
デプロイやトラフィック切り替えの際にはSDKに付属しているappcfg.py
やgcloud
を使用することになりますが、極々簡単なスクリプトを組むことで地味ながら安全・便利になるので紹介したいと思います。
ちなみに、本当はCircleCIを使ったCI/CDの仕組みが作ってありますが諸事情により一時的に使っていない状態であるため各々の端末からデプロイしています。
再び使うようになった暁にはそちらの仕組みも紹介したいと思います。
デプロイスクリプト
手元でのデプロイを行う場合、うっかり作業中のブランチでデプロイしてしまったりする事故が想定されます。
またGitHub上でMergeしたことに満足してしまいgit pull
を忘れローカルの古いものをデプロイしてしまったりといったこともあり得ます(実際一度やりかけました……)。
こういった人間がやらかしがちな単純ミスを回避するには機械的な対策が有効であるため、以下2点の安全装置を組み込みました。
- 現在のブランチが master でない場合は中断
- ローカルとリモートで master の最新ハッシュを比較し一致しない場合は中断
サンプル
#!/bin/bash set -eu BRANCH=$(git rev-parse --abbrev-ref HEAD) if [ $BRANCH != "master" ] ; then # 現在のブランチが master でない場合は中断 echo "branch is not master" exit 1 fi git fetch HASH=$(git rev-parse --short master) REMOTE_HASH=$(git rev-parse --short origin/master) # GitHub側 master の最新コミットハッシュを取得 APPPATH="$(git rev-parse --show-toplevel)/src/example-application/" if [ $HASH = $REMOTE_HASH ] ; then # ローカルとリモートの最新ハッシュが一致する場合はデプロイ実行 appcfg.py -A example-application-production \ --version $HASH \ update $APPPATH \ -E EXAMPLE_BUCKET_NAME:example-application.example \ -E EXAMPLE_ENDPOINT:https://example.com/endpoint else echo "mismatch remote master hash" exit 1 fi
トラフィック切り替えスクリプト
十分にテストを重ねたとしてもリリース時のトラフィック切り替えの瞬間は少し緊張するものです。
もし何かが起こったらすぐに切り戻して復旧しなければなりません。
幸いAppEngineはアプリケーションのバージョニングとトラフィック切り替えが非常に簡単に行える仕組みになっているため、万一の際にもコマンド一発で切り戻せます。
しかしその場合は当然ながら切り替え前のバージョンが何であったかを知っていなければなりませんが、毎回切り替えの度にチェックするのは非効率ですし大抵事故が起きた場合に限って確認するのを忘れていたなんてことになりがちです。
やはりここでも人手を排するアプローチが有効でしょう。
そこで切り替えの実行前に現在のトラフィック割り当て状況を表示し、切り替え後にも同様のものを表示するスクリプトを組みました。
こうすれば万一問題が発生してもどのバージョンからどのバージョンに切り替えたのかが明白であるため迅速確実な切り戻しが可能です。
またやや蛇足ですがgcloud app versions list
のコマンドはバージョンのアルファベット順でソートされてしまい見辛いためsort
を使って日付順となるようにしました。
サンプル
#!/bin/bash set -eu PROJECT_ID="example-application-production" HASH=$(git rev-parse --short master) # 切り替え前情報(トラフィック割り当てのあるバージョンのみ)を日付順で表示 CURRENT_TRAFFIC=$(gcloud --project $PROJECT_ID app versions list) echo "Current traffic" echo "$CURRENT_TRAFFIC" | head -n 1 && echo "$CURRENT_TRAFFIC" | tail -n +2 | grep -v 0.00 | sort -r -k 4 echo -en "\n" gcloud --project $PROJECT_ID -q app services set-traffic default --splits $HASH=1 gcloud --project $PROJECT_ID app versions list | sort -r -k 4 # 切り替え後の情報を日付順で表示
実行例
$ ./scripts/serve.sh Current traffic SERVICE VERSION TRAFFIC_SPLIT LAST_DEPLOYED SERVING_STATUS default 4444444 1.00 2018-03-28T19:04:44+09:00 SERVING Setting the following traffic allocations: - example-application-production/default/5555555: 1.0 Any other versions on the specified services will receive zero traffic. Setting traffic split for service [default]...done. SERVICE VERSION TRAFFIC_SPLIT LAST_DEPLOYED SERVING_STATUS default 5555555 1.00 2018-03-28T19:04:44+09:00 SERVING default 4444444 0.00 2018-01-16T15:03:35+09:00 SERVING default 3333333 0.00 2017-09-12T14:14:55+09:00 SERVING default 2222222 0.00 2017-08-22T01:34:26+09:00 SERVING default 1111111 0.00 2017-08-16T14:46:09+09:00 SERVING
万一切り替え後に問題が起きた場合はCurrent traffic
のところに表示されているバージョンを指定して切り戻しコマンドを実行します。
$ gcloud app services set-traffic default --splits 4444444=1 --project example-application-production
最後に
検証環境へのデプロイのようなもう少し緩くて問題無い用途であればここまでする必要は無くalias
等で独自コマンドを作ってしまう方が手軽です。
私は弊社エンジニアの鈴木がOSSとして開発しているjigを使ってコマンドを定義していますので、これについてはまた別の記事で紹介したいと思います。
また、これらのスクリプトはAppEngineのアプリケーションが一つであることが前提であるため、バッチ処理用サービスが別にある等といった場合についてはもう少し工夫が必要になるでしょう。