Classi開発者ブログ

教育プラットフォーム「Classi」を開発・運営するClassi株式会社の開発者ブログです。

GitHub Actions と Release Please を使ったアプリケーションのリリース自動化

こんにちは @lacolaco です。最近は、先日プレスリリースが出された「学習トレーニング」機能を裏で支えているコンテンツ管理システム(以下内部CMS)の開発に携わっています。

corp.classi.jp

この記事では、内部CMSのフロントエンド(Angular アプリケーション)のリリースフローを自動化している仕組みを紹介します。現在のリリースフローの全体像は次の図のようになっています。この中にある Release Please というのが、今回特に紹介したいツールです。いくつか日本語でのブログ記事などもあるので特にマイナーというわけではないと思いますが、多くの場合はライブラリのリリースに使われています。一方、アプリケーションのリリースで使っているケースはあまり発信されてないように思われたので、この記事が事例の一つとして参考になれば幸いです。

Release Please

Release Please は Google がOSSとして公開しているツールのひとつです。READMEに書かれているように、このツールではGitレポジトリのコミットログをもとにしてCHANGELOG ファイルの生成・GitHub Releases の作成・プロジェクトのバージョン更新などを一挙に自動化してくれます。

github.com

Release Please automates CHANGELOG generation, the creation of GitHub releases, and version bumps for your projects.

Release Please の仕組みは Conventional Commits をベースとしており、レポジトリのコミットログを Conventional Commits の仕様に準じて解析します。 前回のリリースバージョンから最新のコミットまでの間に、 fix や feat のように「リリースする必要がある変更」があれば、Release Please はリリース用のプルリクエスト(リリースPR)を作成します。リリースPRはデフォルトだと次のようなものです。

https://github.com/googleapis/release-please より

リリースPRには次回のリリースに含まれる変更内容が CHANGELOG として記述されており、開発ブランチに新たなコミットが追加されればそれに追従して更新されます。このプルリクエストをマージすることで、レポジトリの CHANGELOG.md やバージョンファイルが更新され、GitHub Releases も作成されます。

Release Please 自体は単なるCLIツールなので、GitHub上のレポジトリでは release-please-action を使うのが便利です。これも Google が公開しているもので、簡単なセットアップでレポジトリの GitHub Actions に Release Please を導入できます。

github.com

ここからは具体的にrelease-please-actionを導入するためのステップを説明します。

release-please-action の導入

Release Please はその仕組み上、レポジトリのメインストリームとなっている開発ブランチのコミットを追跡する必要があります。以後は main ブランチを対象に考えますが、それぞれのレポジトリの事情に合わせて読み替えてください。まずは、Release Please を実行するワークフローファイル .github/workflows/release-please.yml を作成します。次の例のように、 main ブランチへコミットがプッシュされるたびに実行されるようにトリガーを設定し、 google-github-actions/release-please-action アクションを呼び出します。

name: release-please

on:
  push:
    branches:
      - main

permissions:
  contents: write
  pull-requests: write

jobs:
  release-please:
    runs-on: ubuntu-latest
    steps:
      - uses: google-github-actions/release-please-action@v3
        with:
          release-type: node
          package-name: my-app

google-github-actions/release-please-action アクションは振る舞いをカスタマイズするオプションがたくさんありますが、たいていの場合で最低限必要なのはプロジェクトの種類を指定する release-type と、プロジェクト名を指定する package-name です。今回は Angular アプリケーションのリリースのためにセットアップするので、 package.json のバージョンをリリースバージョンとして使うために release-type: node を指定しています。Node.js以外の場合や、Monorepoの場合などはまた違った指定になるので、READMEを頼りに適切な設定をしましょう。

このワークフローだけでリリースPRの作成ができたので、CHANGELOG.md ファイルの更新・バージョンファイル(この場合は package.json の version)の更新・GitHub Releases の作成が行われるようになりました。しかし、これだけではアプリケーションのデプロイが実行されていません。

今回のアプリケーションは、Amazon S3に格納したファイルをAmazon CloudFront によって配信しています。GitHub Actions からデプロイするためには、Angular アプリケーションをビルドし、その成果物をAmazon S3へアップロードするステップが必要です。リリースPRのマージ後にこれらのプロジェクト独自のステップを追加で実行するため、もう少しワークフローを記述します。

リリースPRマージ後のデプロイ実行

google-github-actions/release-please-action が作成したリリースPRがマージされると、当然ですがマージコミット(以後リリースコミット)がプッシュされます。このリリースコミットに対しても release-please ワークフローが実行されますが、リリースコミットに対して実行されたときに限って release_created という特別な出力を持ちます。この出力を読み取ることで、リリースPRがマージされたあとにだけ実行したいジョブを記述できます。

次の例のように、google-github-actions/release-please-action を呼び出したステップの出力を release-please ジョブの出力として再定義し、次の deploy ジョブの実行条件に利用します。 needs フィールドでジョブの完了を待ち、 if フィールドでリリースコミットのときだけ実行されるようにしています。

name: release-please
on:
  push:
    branches:
      - main
jobs:
  release-please:
    runs-on: ubuntu-latest
    outputs:
      release_created: ${{ steps.release.outputs.release_created }}
    steps:
      - uses: google-github-actions/release-please-action@v3
        id: release
        with:
          release-type: node
          package-name: my-app

  deploy:
    runs-on: ubuntu-latest
    needs:
      - release-please
    # リリースコミットに対してのみ実行する
    if: ${{ needs.release-please.outputs.release_created }}
    steps:
      - uses: actions/checkout@v3
      - ... # Amazon S3 へのアップロードなど

このようにワークフローを定義することで、Release Please が提供するデフォルトの機能に加え、プロジェクト独自のステップを加えたリリースフローを自動化できます。

リリースの流れ

このワークフローがあることで、普段は次のような開発からリリースまでの流れになっています。

  • main ブランチからトピックブランチを作り、コードを書いてコミットする。
  • 変更がまとまったところでプルリクエストをつくり、後に main ブランチにマージする。
    • 当該のレポジトリでは常に Squash Merge をするので、マージコミットのメッセージで Conventional Commits に従う。
  • main ブランチにリリースが必要なコミット(fixやfeat)がプッシュされると、リリースPRが作成される。
    • まだ追加の変更を加えたい場合はそのままにする。
  • リリースする内容が固まったら、リリースPRをマージする。
    • あとはすべて自動でリリースフローが進行する。

コミットメッセージのルールを守ることと、リリースPRをマージする作業以外は自動化されており、レポジトリへの書き込み権限さえあれば誰でもリリースを開始できます。

緊急時の手動リリース

型化され自動化されたリリースはすべてがうまくいっている間は便利ですが、時には例外的なリリースをすることもあります。リリース直後に致命的な問題が発覚したときなど、なんらかの理由で過去のバージョンをリリースし直す必要が生まれます。そのようなケースに備えて手動で任意のバージョンを再リリースできる脱出ハッチも用意しています。

手動リリース専用のワークフローファイルを作成し、実行トリガーに workflow_dispatch を指定します。このトリガーは GitHub のWeb画面やAPI経由などから呼び出せて、呼び出し時に入力変数を渡すことができます。この入力として ref を受け取っており、任意の Git コミットへの参照を渡します。Release Please のリリースコミットにはそのリリースのバージョンが Git タグとして紐づいているため、任意のバージョンへのロールバックが簡単にできます。

name: manual-deploy-production
run-name: Deploy Production from ${{ inputs.ref }}

on:
  workflow_dispatch:
    inputs:
      ref:
        description: 'Git ref to deploy (e.g. main, v1.0.0, c91ee3c, etc.)'
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          ref: ${{ github.event.inputs.ref }}
      - ... # デプロイ処理

まとめ

学習トレーニング機能の内部CMS開発でリリース自動化のために活用している Release Please の紹介をしました。似たような機能を持ったツールは他にもありますが、Google 製であり、多くのOSSのリリースで使われていることから長く使えそうだと感じて採用しました。実際に使ってみるととてもよくできていて、今回はあまり説明していませんがMonorepoへの対応が充実しているところにはGoogleらしさを感じています。(内部CMSとは別のレポジトリでは実際にMonorepoで管理している社内ライブラリのリリース自動化にも使っています)

ところで、Release Please のバージョニング規則はデフォルトで Semantic Versioning (Semver)に従いますが、ライブラリはともかくアプリケーションのバージョンに Semver は合わないだろうという意見はあるだろうと思います。内部CMSのフロントエンドでは Semver をそのまま使っており、これは「何をもって破壊的変更とするか」の基準によって、アプリケーションのリリースにおいてもそれなりに意味のあるバージョニングができるように感じているからです。具体的には、内部CMSのフロントエンドでは「アプリケーションの使い方を学び直さないといけない」ような変更をアプリケーションにとっての破壊的変更とみなして運用しています。ロジカルではないですが、この基準であればUI設計やユーザーストーリーの定義の時点で判断できますし、リリース内容をユーザーにアナウンスする上では変更の規模を伝えるサインとして十分に機能すると考えています。

もしアプリケーションやライブラリのリリース自動化のツール選びで困っている方がいれば、ぜひ Release Please を一度試してみてください。

© 2020 Classi Corp.