LoginSignup
0

More than 1 year has passed since last update.

posted at

updated at

Organization

Git hook 活用してみる

前提

  • いま活動してるプロジェクトのひとつでは、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のタイミングで勝手に古いバージョン消すとかもアリかも。権限どうつけとくかとか本番環境も勝手に消えるのかとかちょっと怖いからかっちりテストしたいけど

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0