ネットワークサービス部の上野です。
今回はRenovate を使って、プロジェクトで利用している外部ライブラリの更新を管理する方法についてご紹介します。
なお、前回の記事は【CI戦術編 その9】自動生成しか勝たん openapi-typescript - FJCT Tech blogでした。
外部ライブラリ詰め合わせ
ソフトウェア開発では、超絶スーパーエンジニアでもない限り外部のライブラリを多用することになると思います。
例えば、Web APIを作るために、空のプロジェクトにFastAPIをインストールしたとします。
$ poetry add fastapi $
明示的にインストールしたパッケージは1つだけですが、 6個の依存パッケージが見つかったため、合計7個のパッケージがインストールされました。
$ poetry show anyio 3.6.2 High level compatibility layer for multiple asynchronous event loop implementations fastapi 0.95.0 FastAPI framework, high performance, easy to learn, fast to code, ready for production idna 3.4 Internationalized Domain Names in Applications (IDNA) pydantic 1.10.7 Data validation and settings management using python type hints sniffio 1.3.0 Sniff out which async library your code is running under starlette 0.26.1 The little ASGI library that shines. typing-extensions 4.5.0 Backported and Experimental Type Hints for Python 3.7+
このように、単一のライブラリやフレームワークをインストールしただけでも、 そのライブラリが依存している他の多くのライブラリがインストールされることはよくあります。
Web APIとしてサービスを完成させるためにはFastAPIだけでなく、 外部サービスと接続するためのクライアントライブラリやある特定の処理を行うライブラリなど、 他にも多くのライブラリが必要になることでしょう。
また、開発を効率化させるために、本連載でも紹介してきたlintやテストのためのパッケージなどを、 devDependencies(開発時にのみ必要となるライブラリ)に追加することもあります。
結果的に、1つのプロジェクトは多くの外部ライブラリへ依存することになります。
ライブラリの更新、間に合ってますか?
メンテナンスされているライブラリでは、新しいバージョンのリリースによって機能追加や不具合修正が行われます。 外部ライブラリを利用している場合、一般的にはなるべく新しいバージョンを使うことが望ましいでしょう (もちろん、新しいバージョンに不具合がある可能性もあるため検証は必要です)。
私たちのプロジェクトでは、以前は週に1回手動で下記のコマンドを実行し、ライブラリの更新作業をしていました。
$ poetry update $
先述したように、1つのプロジェクトだけでも数多くのライブラリに依存していることが多く、 私たちのプロジェクトでも100個以上のライブラリに依存しています。 1週間の間に複数のライブラリでリリースが行われることもあり、 アップデートを1つ1つを確認して動作検証をするのに多くの工数がかかっていました。
Renovateを導入したところ、これらの作業にかかる工数を大きく削減することができました。
Renovateを使ったライブラリ更新
Renovateを使うことで、プロジェクトで利用している外部ライブラリの更新を簡単に管理できます。
RenovateはGitLabやGitLabのプロジェクトで利用しているライブラリの更新状況を確認し、 更新があればライブラリのバージョンをアップデートするMR(GitHubの場合はPR)を作成します。
これだけでも大変ありがたいことですが、CIパイプラインと組み合わせることで、Renovateはさらに効果を発揮します。 人間がMRを作成したときと同様に、Renovateが作成したMRに対してもパイプラインを実行することで、 その実行結果からライブラリのバージョンアップがプロジェクトのソフトウェアの挙動に 破壊的な変更を与えるかどうかを簡単に確認できます。
CIパイプラインでライブラリの破壊的な変更を検知ための前提条件として、 ライブラリを使ったテストを書いている必要があります。 ライブラリを利用するコードのテストが存在しなかったり、 テスト中でライブラリをモックしたりしている場合は、 ライブラリの挙動の変更に気づけないことがあります。
なお、先ほどはPoetryを使ってPythonのライブラリを管理する例を紹介しましたが、 Renovateはその他の多くのプログラミング言語、パッケージマネージャーに対応しています。
Renovateの導入
ここまでの連載でもご紹介したとおり、私たちはGitLabを使って開発しています。 このセクションではGitLabでRenovateを導入する方法をご紹介させていただきます。
GitHubや他のプラットフォームでの導入については、ここでは紹介しません(Renovateの公式ドキュメントに説明があります)。 なお、Renovateの利用にはGitLab Runnerが必要ですが、その設定方法はここでは割愛します。 検証はGitLab 15.8.3(Enterprise Edition)で実施しています。
以下の2通りの方法で、GitLabでRenovateを実行できます。
- プロジェクト単位でRenovateを実行する
- RenovateのDockerイメージを各プロジェクトのCIに組み込んで、 Renovateを実行することができます。
- Renovateを実行するための専用のプロジェクト(Renovate Runner)を作成する
- Renovate Runnerプロジェクトを作成することで、複数のプロジェクトでRenovateを利用できるようになります。
私たちのチームでは複数のGitLabプロジェクトを使って開発しているため、Renovate Runnerを作成することにしました。 Renovate Runnerはこちらのプロジェクトに従って設定することで、簡単に導入できます。 詳細は省略しますが、概略は以下の通りです。
- Renovate Runner用のプロジェクトの作成
- Renovateを利用するための、空のGitLabプロジェクトを作成します。
- CI/CD variablesの設定
- Renovateジョブの設定
.gitlab-ci.yml
にRenovateのジョブを定義します。- ドキュメントのコピー&ペーストで基本は問題ないです。
- CI/CD Scheduleの設定
- 定期的にRenovateを実行する設定をします。
- 更新対象のプロジェクトでのRenovateの初期設定
- 更新を管理する対象の各プロジェクトで、Renovateの設定を行います。
- onboardingを利用することでRenovateの設定を行うMRが自動で作成されます。
オンボーディングMR
Renovateが作成したMRの受け入れ
Renovateの導入が完了すると、外部ライブラリの更新を検知する度に更新MRが作成されるようになります。 作成されたMRには、ライブラリバージョンをアップデートするコミットが含まれます。 また、そのMRのディスクリプションにはライブラリの現在のバージョンとアップデート後のバージョン、 Release Noteなどの情報が記載されています。 開発者は作成されたMRとパイプラインの結果を見て、このMRを取り込むかどうかを判断します。
MRのディスクリプションに表示されるMerge Confidenceのバッジの情報も、 アップデートを採用するかどうかの判断材料になります。Merge Confidenceは、以下の4つのバッジからなります。
Age リリースされてからの期間が表示されます。 リリースされてから間もないライブラリは未知のバグを含む可能性が高く、アップデートにはより慎重な判断が必要です。 ただし、脆弱性対応など、緊急性の高いアップデートでは即時対応が必要な場合もあります。
Adoption このパッケージを使っているRenovateユーザ(プロジェクト)のうち、このアップデートを受け入れたユーザの割合が表示されます。 多くのユーザ(プロジェクト)によって利用されているバージョンであれば、すでに十分な検証を受けている可能性が高いです。
Passing このパッケージに依存しているプロジェクトがこのバージョンを受け入れた場合に、 アップデートのための変更なしにプロジェクトのテストがパスした割合を表します。 メジャーバージョンアップなどでライブラリの仕様の変更がある場合は、Passingの割合は低くなります。
リリースノートなどで明示的に破壊的な変更が宣言されていないにもかかわらず、 Passingの割合が低い場合、宣言されていない仕様の変更やバグが含まれる可能性があります。
- Confidence 上記の3項目などから決定された、このアップデートの「おすすめ度」が表示されます。 おすすめ度が低い方から順に、 Low << Neutral << High << Very High と定義されています。
パイプラインが失敗している場合は、破壊的なアップデートによってライブラリの使い方や動作が変更された可能性があります。
その場合は開発者がMRに修正コミットを重ねて、マージできるようにしてください。
また、開発者が不要だと判断したアップデートは、MRをクローズすることで、今後そのバージョンのアップデートは検知されなくなります。
依存しているライブラリの数が多い場合、ほぼ毎日のペースで何かしらのライブラリはアップデートされます (CI/CDに設定したScheduleの間隔を調整することで、MR作成の頻度は制限できます)。 アップデートがたまってくると身動きができなくなってしまうので、RenovateのMRはため込まずにその都度確認しましょう(自戒)。
また、automergeの設定をすることで、 人間がMRを確認せずに、パイプラインが通過したら自動的にMRを取り込むこともできます。 私たちのプロジェクトでは、本番の動作には影響しないdevDependenciesのライブラリに関してはautomergeする設定を入れています。 他にも、マイナーバージョンアップの場合はautomergeするなど、柔軟な設定を行えるため、プロジェクトの規模や要望に応じて、 人間の確認するMR数が適切な数となるようにRenovateの設定を調整するとよいでしょう。
導入の効果と今後の課題
私たちのチームでRenovateを導入してからちょうど1年ほど経ちますが、 これまでRenovateがアップデートを検知して作成したMRは約400件になります。 手動でアップデートの変更内容とプロジェクトへの影響を調べてMRを作成するのに、1件あたり30分かかるとすると、 Renovateの導入によって年間で200時間程度の工数を削減できたと言えそうです。
私たちのプロダクトではバックエンド(Python)のテストは充実しているため、 CIの結果を見てアップデートを取り込む判断をしやすいのですが、 フロントエンド(TypeScript)のテストはあまり充実していません。 そのため人間が手動でUIの動作確認をして、アップデートを取り込むかどうかを判断しているのが現状です。 今後の課題として、UIでの描画をCIでテストできるようにすることで、 フロントエンドのライブラリのアップデートを取り込むかどうかの判断をより簡単にしたいと考えています。
おわりに
今回はRenovateを使ってGitLabのプロジェクトのライブラリを最新に保つ方法をご紹介しました。 Renovateを導入することでライブラリのアップデートの検知とMRの作成が自動で実行されるため、 定期的に人間が手動でアップデートを確認する必要がなくなり、最新のライブラリへの追従が容易になります。
RenovateはGitHub, GitLab等様々な環境で動作させるためのdocsが充実しており、 セルフホスティング環境を含むGitLabをご利用の方には特におすすめです。
次回は脆弱性検知ツールのTrivyについてご紹介します。 長らく10回も連載してきたCI戦術編も、いよいよ次回で最後となります。 ぜひお楽しみに。
連載バックナンバー
CI戦術編
- 【CI戦術編 その1】 alembic check コマンドを活用したマイグレーションスクリプト生成忘れ防止
- 【CI戦術編 その2】コードレビューを充実したものにする方法、あるいは一生残る恥ずかしい履歴を作らないように
- 【CI戦術編 その3】Pythonのimportのことならまかせろ isort
- 【CI戦術編 その4】Blackを利用したコーディングスタイルの統一
- 【CI戦術編 その5】Pythonで明示的に型を書く理由
- 【CI戦術編 その6】Python開発の強い味方 Pylint
- 【CI戦術編 その7】 pyupgradeを使って最新の記法に対応してみた
- 【CI戦術編 その8】OAS(OpenAPI Specification)で仕様書を自動生成しよう
- 【CI戦術編 その9】自動生成しか勝たん openapi-typescript
- 【CI戦術編 その10】 Renovateで依存ライブラリのアップデートに負けない方法 ←イマココ