git-pr-releaseとGitHub Actionsでワンクリックデプロイを実現する
突然ですが、git-pr-release
のなんちゃってコラボレーターである私が僭越ながら、その王道の使い方を皆様に伝授していきます。何番煎じかの記事ではありますが、現代の、特にGitHub Actions出現以降の使い方をまとめたいというのが動機です。
git-pr-release
はGitHubを業務開発で利用している場合に便利なツールで、デフォルトブランチにマージされたpull requestをリリース項目として一覧し、それをpull request化してくれるものです。これにより以下のことが実現できます。
- リリース項目が一覧されることによるリリース内容の明確化
- マージボタンによる明快なワンクリックデプロイの実現
- pull requestラベルによるリリース履歴の抽出
導入
git-pr-release
はCIで自動実行するのが基本です。今はGitHub Actionsがあるので、なんと、以下の1枚のYAMLをGitHub上のリポジトリに配置するだけです。
# .github/workflows/git-pr-release.yaml
name: git-pr-release
on:
push:
branches:
- develop
jobs:
git-pr-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # git-pr-release needs the git histories
- uses: actions/setup-ruby@v1
with:
ruby-version: 3.1
- run: gem install --no-document git-pr-release
- run: git-pr-release --squashed
env:
GIT_PR_RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_PR_RELEASE_BRANCH_PRODUCTION: main
GIT_PR_RELEASE_BRANCH_STAGING: develop
GIT_PR_RELEASE_LABELS: pr-release
# GIT_PR_RELEASE_TEMPLATE: .github/git-pr-release.erb
# TZ: Asia/Tokyo
もちろん環境によって多少は調整が必要です。GIT_PR_RELEASE_BRANCH_STAGING
にデフォルト開発ブランチを、GIT_PR_RELEASE_BRANCH_PRODUCTION
に本番ブランチ名を指定してください。このブランチについては少し後に説明します。
成功イメージ
トピックブランチがマージされるなどしてデフォルト開発ブランチのコミットが進むと、GitHub Actionsにより以下のようにデフォルトブランチから本番ブランチへのpull requestが自動で作られます。マージされたpull reqeustの一覧がリリース項目として表示されており、開発ブランチのコミットが進む度にリストは自動で更新されます。
折を見てこのpull requestをマージすれば、mainブランチのコミットが進み、それによりdeployされるという寸法です。deployフローは各自のものをお使いください。
前提とするブランチングモデル
git-pr-release
は以下のようなブランチングモデルを前提としています。ここではmainとdevelopという名前にしていますが、名前は先に述べたように設定可能です。
メインで開発を進めるdevelopブランチと並走するmainブランチがあります。developからmainへのマージによりmainブランチのコミットが進むことがdeploymentのトリガーとなり、deployフローが走り始めることを想定しています。
mainは基本的にはdevelopからのマージでしかコミットは進みませんし、マージされた時点ではdevelopとmainの内容は等しくなります。
このブランチングモデルは、物議を醸しがちなgit flowの簡易版とも言えるもので、割とポピュラーなのではないでしょうか。developが進んだ時点でステージングやQA環境を自動デプロイしている組織も多いでしょう。
他のブランチングモデルとしては、本番ブランチ(ここではmain)を分けて作らずに、デフォルトブランチにマージされた時点で本番deployを走らせるようなフローや、トピックブランチのレビューが通った時点でそのトピックブランチを本番deployし、それが成功したらデフォルトブランチにマージするようなフローもあるでしょう。
そういったスタイルに比べて、このモデルのデリバリー速度は少し落ちるかもしれませんが、ステージング環境が用意できること、リリースのタイミングを制御できること、複数のフィーチャーブランチが近いタイミングでマージされた時にdeployが混雑せずリリースフローを直列化できるところなどにメリットがあるでしょう。
GitHub Actions用のYAMLの解説
冒頭のYAML上のいくつかのポイントについて解説していきます。
shallow cloneとgitの履歴
git-pr-release
はリリース項目の抽出のためにgit log
を使います。なのでshallow cloneで履歴が少ないケースでは上手く動きません。ですので、clone時にshallow cloneを防ぐ設定が必要です。
- uses: actions/checkout@v3
with:
fetch-depth: 0 # git-pr-release needs the git histories
実は、shallow clone時に自動でunshallowする挙動 が近々リリースされるので、この設定せずとも動くようになりますが、余計なfetchが減らせるので、この設定を入れるメリットはまだほんの少しだけあります。
squash mergeのための--squashed オプション指定
pull requestのmergeにsquash mergeを使っている場合、--squashed
オプションを指定してください。
squashしないプロジェクトではこのオプションを付ける必要はありませんが、付けても多少APIリクエストが増える可能性があるだけなので、常につけて大きな損はないでしょう。
ちなみに、開発ブランチへのコミットがすべてマージコミットである場合には--squashed
オプションによる追加のAPIリクエストは一切発生しません。なので、このオプションを標準挙動にしてしまっても良いのではないかとも思っていますが、そのあたりはメンテナ陣と議論が必要ですね。
pull requestへのラベル自動付与
GIT_PR_RELEASE_LABALS
環境変数にpull requestに自動付与するラベルを設定できます。カンマ区切りで複数指定も可能です。これは後でリリースを抽出して振り返りたい場合等に便利です。あまり言及されていることは少ないのですが、設定することをおすすめします。
TZ環境変数によるタイムゾーン設定
標準ではpull requestのタイトルに時刻が表示されますが、GitHub Actions上で実行する場合UTC表示になります。開発チームにわかりやすいタイムゾーンで時刻表示したい場合、TZ環境変数を設定すればお手軽に表示を変更できます。
JSTで表示したい場合GitHub ActionsのYAMLの適切な箇所に以下のようなenv設定書くことになるでしょう。
TZ: Asia/Tokyo
独自テンプレートの活用
pull requestのタイトル及び本文はテンプレートでカスタマイズできます。GIT_PR_RELEASE_TEMPLATE
環境変数でファイルパスを指定します。erb形式のテンプレートで、デフォルトの内容は以下です。一行目がpull requestのタイトルになり二行目以降が本文になります。
Release <%= Time.now %>
<% pull_requests.each do |pr| -%>
<%= pr.to_checklist_item %>
<% end -%>
最近の自分のユースケースだと、リリース方法、手順、ロールバック方法等を冒頭に載せているのと、mergeされたpull requestをそれぞれチェックリストとして表示する必要性は感じなくなったので、以下のようにしています。
## How to deploy
...
## Items to be Released
<% pull_requests.each do |pr| -%>
- #<%= pr.number %> <%= pr.mention %>
<% end -%>
テンプレートが意図通り出力されるかどうかの確認には、手元で git-pr-release --dry-run
を実行すると良いでしょう。
落ち穂拾い
マージボタンに対するワークフロー
個人的にはあまり厳密にやらないのですが、git-pr-releaseが作成したpull requestのマージを、特定メンバーのレビューや何らかのチェックが通るまではブロックしたいこともあるでしょう。そういった場合はBranch ProtectionやCODEOWNERS機能を活用すると良いです。
- https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/about-protected-branches
- https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
リリース障害時の切り戻しはどうするの?
リリースしたコードに不具合があり、手動で切り戻しが必要になった時にどうするか。
ロールバックが作り込まれていればそれを使えばよいですが、個人的には本番ブランチを変更してcommitを進めてリリースしてしまうのがお手軽だと思っています。具体的にはgit-pr-releaseのpull requestをrevertしたりhotfixを入れるなどする。
「本番ブランチのcommitが進めば新しいリリースが作られてdeployが始まる」というメンタルモデルに寄せてしまったほうが覚えることや混乱が少ないというのが理由。障害時に普段使わない不慣れなロールバック作業をすると余計なミスが生まれるリスクも高まります。
もちろん、直前に稼働していたコンテナイメージなどの成果物を再利用できたほうが切り戻しが早いというのはあります。そのためにロールバックを整えるのも手ですが、例えば、成果物を特定するためのidentityにコミットハッシュを使うのではなく、git rev-parse HEAD^{tree}
でツリーオブジェクトのハッシュ値を使えば、revert commitでも直前の成果物をつかってくれる仕組みを組むことが可能でしょう。
参考: 同じソースツリーでテストが通っていたらテストをスキップする
さて、本番ブランチにrevertやhotfixを入れると、開発ブランチと本番ブランチの内容に食い違いが出てしまいます。そういった時にどうするか。
本番ブランチと開発ブランチの食い違い問題
結論を書くと、普段とは逆向きに、本番ブランチから開発ブランチに向けてmergeしてしまえば良いです。
git-pr-release
は前述の通り、本番ブランチと開発ブランチの内容はマージ時点で同一になることを想定しています。そして、通常時は開発ブランチから本番ブランチへのマージでしか本番ブランチのコミットは進まないため、それがほぼ担保されます。
しかし、前項のような緊急オペレーションを行なったなどの理由で乖離が発生する可能性があります。こういったときは、気持ち悪く感じるかもしれませんが、本番ブランチから開発ブランチに、通常とは逆向きのマージをしてしまえば大体問題ないです。ここはちょっと手作業が必要になってしまいますが。
それで問題が起きたことは、これまで10年近く複数プロジェクトに渡って長らく使ってきましたが、特にありません。いざとなったら本番ブランチを消すなりリネームして新たに開発ブランチから作り直しても良いと思います。また、厳密には本番ブランチと開発ブランチに乖離が無いかのチェックを簡単なスクリプトでCIしても良いのかもしれませんが、やったことはなく、困ったこともありません。
設定ファイル .git-pr-release
git-pr-release
には設定ファイルがあります。リポジトリルートに .git-pr-release
というファイル名で配置して、git-config形式で設定を記述します。
ここに書ける設定はすべてここまでで解説した GIT_PR_RELEASE_*
環境変数で代用することができます。特にこの記事ではGitHub ActionsのYAML一枚でわかりやすく完結させるために、このファイルは使っていません。
ただ、手元で git-pr-release
を動かす場合、具体的にはテンプレートの動作確認のために --dry-run
をつけて動かしたいなどの場合、設定ファイルがあったほうが都合が良いこともあります。
また、リポジトリルートにこのファイルがあることで、git-pr-release
を利用していることがわかりやすいという話もあります。ただ、近年はリポジトリルートにファイルが散乱しがちなので、逆に置きたくないという向きもあるでしょう。
カスタムアクション化しないの?
メンテナンス体制との兼ね合いで、メインメンテナ(@onk)的に提供する気は無いようです。
実際、冒頭のYAMLを見てもらえれば分かりますが、やってることは gem install git-pr-release && git-pr-release
だけなので、いちいちカスタムアクションをかぶせるのも大袈裟とも言えます。
有志の方でカスタムアクションを作られてる方もいて、僕もそれを利用していたこともありましたが、最新機能を使いたくなったこともあり、最近はそれらを使わない素朴な形になりました。
都度インストールすると、オーバーヘッドやRubyGemsのダウンリスクなどが気になるかもしれませんが、gem install git-pr-release
の所要時間は2秒程度ですし、そういった些細な事象を気にして複雑度を上げる必要もないと考えます。
Go化などは考えないのか?
git-pr-release
はRuby製のツールですが、オリジナル作者の @motemen や私は最近はGo Hackerでもあります。なので、私も個人的にGoで作り直すことを考えたこともありましたが、必要ないと思うようになりました。
Rubyによる実行オーバーヘッドは些細なものです。それに、言語自体のパフォーマンスよりもリモートリポジトリやAPIへのアクセスが実行時間に対して支配的です。それに、git-pr-release
自体の実行時間は差分の量にもよりますが精々10秒程度です。そこが開発スピードの全体ボトルネックになることは考えづらいでしょう。
erbでテンプレートが書ける点もRuby製ならではの嬉しさと言えます。
git-pr-release
は長年使われており、メンテナンスもされ、より安定して使いやすくなっていっています。例えば、squash merge関連の機能が整備されたこと等が、ここ一年の地味に嬉しい機能拡張として挙げられます。そのあたりも後発ツールに比べた強みがあるので、是非ご利用ください。(ポジショントーク)
--no-fetch
オプションは現状全く意味がない
余談ですが、 --no-fetch
というオプションがあるのですが、これは現状全く動いていません。このオプションを指定してもなんと何も動作が変わらないのです。
これについては、以下のissueで過去の経緯や、今後どうするかを議論しているので興味がある方は御覧ください。