Kengo's blog

Technical articles about original projects, JVM, Static Analysis and TypeScript.

リリース自動化の嬉しみとその手法

DevOpsやCIOps、GitOpsなどを通じて生産性向上を突き詰めていくと、コンパイルやテストだけではなくリリースまで自動したくなってきます。リリースには必要な作業が多く、また頻度も高くないため毎回思い出したり間違えたりが発生するためです。

特に変更内容をまとめて文書化する作業は、利用者に対する影響度もその煩雑さも高いため、自動化できれば文書の品質向上やリリース頻度の向上に大きく貢献できます。本記事では、筆者がNode/Java界隈でよく見るリリース自動化手法について紹介することで、リリース自動化の敷居を下げたいと思います。

なお本記事で言う「リリース」は、jarファイルやコンテナイメージなどビルドの成果物をリポジトリGitHub Releasesにアップロードして他プロジェクトやデプロイ環境で利用できるようにすることを指しています。環境に対する「デプロイ」や、エンドユーザへの公開を意味する「リリース」とは区別します。

自動化の前に

1. Changelogの要件を検討する

ソフトウェアのリリース時にその変更内容について説明する文書のことを、ChangelogまたはRelease Noteと呼びます。組織によってはChangelogとRelease Noteに異なる意味を持たせることもありますが、複数コミュニティから自動化手法を紹介する関係上、この記事では区別しません。

ChangelogにはKeep a Changelogというひろく知られた書式があります。自動化手法にはこの書式を意識したものも多いので、特に困らなければこれを採用します。

OSSでありがちなChangelogの保管場所としては、プロジェクトルートに CHANGELOG.md などのテキストファイルとして配置するか、GitHub Releasesの本文に記載する方法があります。Dependabotなどの依存管理手法はGitHub Releasesを参照して変更内容をユーザーに伝えるので、選択できるのであればOSSでなくともGitHub Releasesを使用することが望ましいでしょう。もちろんテキストファイルと併用しても構いませんし、GitHub PagesやWiki、チャットツールといった他の手法での公開も検討できます。

なおJava界隈ではChangelogに成果物のチェックサムを併記することもあります。これは利用者が成果物の整合性を検証する際に役立ちます。

自動化手法を選択する

1. Release Drafter

以下の記事にて紹介されているため詳細は割愛します。特徴はPRを使って開発しているプロジェクトであれば言語に依存せずほとんどのプロジェクトで採用できることでしょう。Maven開発のような非常に歴史の長いプロジェクトでも採用が検討されているようです

zenn.dev

2. GitHub ReleasesのRelease Note自動生成機能

GitHub Releases自体にも、Pull RequestをもとにRelease Noteを生成する機能が備わっています。 特に設定せずに導入が可能なため、とりあえず使ってみるには便利です。

docs.github.com

ラベルを使ってPRを分類するため、ラベルをPRに抜け漏れなく貼る運用が欲しくなるでしょう。actions/labelerのようなラベル管理を自動化する仕組みをあわせて検討すると良いかもしれません。

想定される利用手法は主に2つです:

  1. GitHub ReleasesからGUIを使ってリリースする手法。Releases作成時にGUI上に生成されるRelease Noteを目視確認できるため、安心して導入できるでしょう。Releasesのドラフトを作成→GitHub Actionsを発火し成果物をビルド→成果物をReleasesにアップロード→Releasesを公開 という流れです。
  2. CLIを使って自動化する手法。リリースプロセスから人手を廃するのに適しています。GitHub Actionsを発火し成果物をビルド→Releasesのドラフトをタグに対して作成、成果物をアップロード→Releasesを公開 という流れです。GitHub Actionsの発火にはタグ、あるいはリリースブランチへのpushを使うことが多いのではないでしょうか。

懸念があるとすれば、GitHubに対するロックインでしょうか。他の自動化手法と比べてバージョンが自動で決定されないのも特徴ですが、これはSemVer以外のバージョニングポリシー(例えばCalVer)を採用しやすいというメリットだと取ることも可能です。

3. semantic-release

SemVer2Conventional Commitsの利用を前提として、コミットメッセージをもとにリリースを自動実行する仕組みです。デフォルトブランチやリリースブランチに対するすべてのpushを契機としてリリースを行います。

semantic-release.gitbook.io

運用に柔軟性を持たせつつも、極力自動化し人の手を入れさせないための工夫が要所に見受けられるのが特徴です。なんせ、使っているロゴがこれです:

f:id:eller:20220216093216p:plain
「人間に作業させるとロクなことにならん」とか言ってそう

例えば運用には各開発者がConventional Commitsに従う必要がありますが、commitizenを使うことで導入障壁を下げることもできますし、commitlintを使うことでコミット時にコミットメッセージ書式の検証を行うこともできますし、semantic-pull-requestsを使うことでコミットメッセージが書式に従っていない場合にGitHub Checksを失敗させることもできます。またPRをsquash mergeすることで、コミットメッセージの決定をマージ時まで遅延することもできます。

またプラグイン機構による拡張も可能ですし、Shareable configurationsを使えば複数リポジトリをまたぐプロジェクトにも一貫した設定を行えますので、ある程度大きな規模の組織でも運用しやすいかもしれません。

利用方法はビルドのワークフローに npx semantic-release を埋め込むだけです。ブランチ名などの情報からリリースを行うべき状況だとsemantic-releaseが判断したら、Changelog生成やリリースが自動的に実行されます。リリースには npm publish./gradlew publish などのすでにコミュニティで利用されている手法が利用されるため、既存のリリース手順を再利用できます。

導入における主な課題は2つ。monorepoがbuilt-inではサポートされていないことと、Node.JS最新のLTSを必要とすることです。semantic-release自体はCI環境で実行するものなので開発者の手元にNode.JSを入れる必要はないのですが、前述のcommitizenやcommitlint, huskyといった関連ツールもほぼNode.JSコミュニティによって管理保守されているため、Node.JSを入れる判断をすることもあるでしょう。そのためNode.JS以外の環境を対象に開発しているプロジェクトではプロジェクトセットアップが若干複雑化するかもしれません。

なお似たものにstandard-versionrelease-itがあります。私は中の人を尊敬しスポンサーしているので、semantic-release推しです。spotbugs-gradle-pluginなど複数のOSSプロジェクトで使っていて、貢献受け入れやリリースを含め問題なく便利に回せています。

4. changesets

Atlassian発のmonorepoに特化した仕組みです。Node.JSを使ったmonorepoを開発しているのであれば検討しても良いかと思いますが、私はまだ試せていません。既存ユースケースもstandard-versionやsemantic-releaseと比べると1桁少ないです。

github.com

5. JReleaser

Node.JSではなくJVMを用いて動く仕組みです。他の仕組みと比べてまだ若いですが、既にMavenやGradleのサポートも用意されています。

jreleaser.org

f:id:eller:20220216093124p:plain
https://jreleaser.org/guide/latest/index.html より引用

Gradle用のクイックスタートを見た感じでは、 maven-publish プラグインではなく自前でリリース用の設定を持つようです。ここが ./gradlew publishnpm publish用の設定が完成されたプロジェクトに外付けする semantic-release とは大きく思想が異なる点です。コミュニティの進歩にJReleaserが自前でついていく必要があるため、保守コストが高くなる選択だと言えます。個人的には期待しつつもちょっと様子見です。

リリース自動化の果てに

1. 手動作業が残る部分

Changelog以外の文書は引き続き手動で作成する必要があります。例えば以下のようなものです:

  • エンドユーザ向けに変更内容を説明する文書
  • メジャーリリース時のマイグレーションガイド
  • マニュアル、プレス、その他

TwitterやSlackなどでの更新通知は自動化が可能ですが、もしブログ記事やメール通知のような手の込んだ文章を作成しているのであれば、それも残るでしょう。

ただこうした文書や通知はパッチリリース時にはあまり作らないはずで、パッチリリースの高速化・安定化・高頻度化は自動化によって充分に実現できると期待できます。

2. リリース自動化に向いたプロジェクト構成

CIやリリース自動化を推し進めると、ビルドやリリースに手作業が必要なプロジェクト構成はやりにくくなります。たとえば依存ライブラリを手でダウンロードしないとビルドできないとか、バージョン番号を手で書き換える必要があるとかです。

依存ライブラリについては、幸いJava界隈ではMaven Centralからほとんどのライブラリをダウンロードできます。昔では考えられなかったライブラリ、例えばOracle JDBC Driverもありますので、一度探してみるといいでしょう。
Maven Centralやその他のパブリックリポジトリに置いてないライブラリを使う場合は、自前でMaven Private Repositoryを管理してそこに置くことになるでしょう。これはNode.JSにおいても同様です。

バージョン番号の更新は、自動的に更新できるようにする必要があります。今回紹介した仕組みでサポートされているケースもありますし、Maven Release Pluginなどの機能を使うこともできます。ビルドツール設定以外のファイル、例えば META-INF/MANIFEST.MFBundle-Versionなどは手動ではなくビルドツールが自動で生成するようにしましょう。

3. リリース自動化に向いたブランチ戦略

リリース自動化は多くの場合、デフォルトブランチやリリースブランチが「常時リリース可能」であることを前提としています。 これは極力従うことが好ましいでしょう。

もしブランチが常時リリース可能でなかったら、リリース作業前に「リリース可能かどうか」を人間が検証する必要性が出てしまうためです。 もともと「自動化により人間の関与を減らしリリースの安定性と頻度を増やす」ことを目的に自動化しているのですから、ここに人間による作業を入れてしまうのは本末転倒です。少なくともリリース可能性検証のプロセスを自動化して、マージ前ビルドないしリリースビルドで自動的に検証されるようにするべきでしょう。

注意点として「マージしたらリリースされてしまう」ことを「完成するまでマージするべきではない」と受け止めない事が必要です。これはトピックブランチの寿命は短いほうが開発効率に良い影響があるためです。リリースできないとわかっている変更を公開することは避けつつ高頻度に変更をマージするために、Feature Toggleを使うなどの工夫が必要になるかもしれません。

まとめ

本記事では、筆者がNode/Java界隈でよく見るリリース自動化手法について紹介しました。

自動化手法 特徴 注意点
Release Drafter PRを使っていれば言語やビルドツールに関係なく利用可能。 リリースビルド用のワークフローを別に用意する必要がある。
GitHub ReleasesのRelease Note自動生成機能 設定不要。ラベルをもとにPRを分類してRelease Noteに反映。 バージョン番号を自分で決める必要がある。Release Note生成だけで、リリース作業自体は別に実行が必要。
semantic-release 柔軟な拡張性と徹底した自動化を両立。 実行にNode.JS最新のLTSが必要。標準ではmonorepoに非対応。
changesets 標準でmonorepoに対応。 Node.JSプロジェクト用。
JReleaser MavenやGradleといったビルドツールと統合。 JVM言語プロジェクト用。

リリース自動化はリリースの安定性と頻度を増やせる強力な仕組みです。ものによってはChangelogやコミットコメント、ブランチ戦略にリリース検証可能性検討の自動化といったものまで見直しをかける必要がありますが、それらも開発効率や生産性に寄与すると考えられているものがほとんどなので、開発体験向上のため検討してみてはいかがでしょうか。