前提
- いま活動してるプロジェクトのひとつでは、GAE上で複数のアプリケーションを動かしている
- デプロイ自動化のしくみがあって、特定のルールに沿ったタグをpushするとGitHub Actionsやらでステージング環境/本番環境に各サービスの最新バージョンがデプロイされる(トラフィック移行は手でやる)
- 全サービスがデプロイされるのはまあまあ時間がかかる
悲劇
ある日チームメンバーがステージング環境を全更新しようとしてタグ打って昼メシして、戻ってきたらGAEの制限(プロジェクト内には全GAEサービス合算で210個のバージョンしか存在できない)に引っかかってしまってデプロイできてなくて、泣いてた
対策を考える
いまのとこ何バージョン残しとくとかのかっちりしたルールとかがなくて多くなってたら適当に古すぎるのは消そうみたいな運用になっており、まあそれは仕方ないにしてもせめて早い段階で人間がデプロイ完了できないことに気づく仕組みが必要だな……と思った。
いくつか方法はありそうな気がしたけど、ありもので楽に動かせそうなのはGitのなんらかのhookを使って適当なタイミングでチェックすることかなと思い、いくつかあるhookのなかで今回のユースケースに一番合いそうなpre-push hookを使ってやってみることにした。
実現方法
GAEにデプロイされてるバージョン数の取得
あるプロジェクト中のGAEにデプロイされてるサービスについては以下のコマンドで取得できる
% gcloud --project ${PROJECT_NAME} app versions list
SERVICE VERSION.ID TRAFFIC_SPLIT LAST_DEPLOYED SERVING_STATUS
xxx 20201102t050314 0.00 2020-11-02T14:05:47+09:00 SERVING
(略)
ので、数は wc -l
とかに食わせれば出力できる
フックの実装
ここにmanがある https://git-scm.com/docs/githooks#_pre_push
そしてここにいい感じの解説がある https://stackoverflow.com/a/42462882
これらを参考に作ってみたのがこれ
#!/bin/bash
check_tag () {
if [[ "$1" =~ ^stg_ ]]; then
echo "stgのバージョン数チェックするよ"
check_n_version $(gcloud --project my-awesome-project-stg app versions list | wc -l)
elif [[ $"$1" =~ ^prod_ ]]; then
echo "prodのバージョン数チェックするよ"
check_n_version $(gcloud --project my-awesome-project-prod app versions list | wc -l)
else
exit 0
fi
}
check_n_version () {
n_max_versions=190
if [[ "$1" -ge $n_max_versions ]]; then
echo "バージョン数多すぎ ($1)" 1>&2
exit 1
else
exit 0
fi
}
while read localref localhash remoteref remotehash; do
case $localref in
refs/tags/*)
check_tag ${localref#refs/tags/} ;;
*)
exit 0 ;;
esac
done
解説
シェル芸人でもGit芸人でもないから何やってるかわからなくなりそうなので、難しそうなところちょっと解説しておこう。
while read localref localhash remoteref remotehash; do
ここなんで while
でまわしてるかというのは上で示したstackoverflowでも言及されてるけど、git pushは1コマンドで複数のrefを送信できるので、全部について見てあげたいから(1つのpushしようとしてるrefにつき、この4つのセットが1行渡される)。
refs/tags/*)
check_tag ${localref#refs/tags/} ;;
ここの ${localref#refs/tags/}
というのは $localref
の先頭から refs/tags/
を抜き去った文字列を作ってくれる。こうすると check_tag
のなかでは渡されたのが真にタグの名前というのがわかるのでうれしい。
実際に動かしてみる
超雑なタグ名で試してみる。
うまくいくとき。
% git push origin stg_x
stgのバージョン数チェックするよ
Total 0 (delta 0), reused 0 (delta 0)
To github.com:xxxx/xxxxx.git
* [new tag] stg_x -> stg_x
こっちはダメなとき。
% git push origin stg_x
stgのバージョン数チェックするよ
バージョン数多すぎ (193)
error: failed to push some refs to 'github.com:xxxx/xxxxx.git'
うまくpushを阻止することができた。
できたこと
pre-push hookで自動デプロイの対象になるタグのpushを捕捉してGAEのバージョン数見てあげることで悲しみを減らせるようになったよ
今後の課題
- クライアントhookはGitの管理下にないために、各自がcloneしたあとに追加の作業が必要になるので、実際に適用するためにはなんらか手順を整える必要がある
- 運用ルールが整えばタグpushのタイミングで勝手に古いバージョン消すとかもアリかも。権限どうつけとくかとか本番環境も勝手に消えるのかとかちょっと怖いからかっちりテストしたいけど