DependabotとRenovateってどっちがいいの?
この記事は第二のドワンゴ Advent Calendar 2019の10日目の記事です。
この記事の概要
Webフロント開発をしている際に、npmライブラリのマイグレーションって結構コストかかるので自動化したいよねって動機の元、そのためのツールとしてDependabotとかRenovateとかあるけど、どっち使うのが良さそうかなという検討をしました。
ただし、あくまでもnpmライブラリの更新という側面からの記事のため、他の言語やパッケージ管理システムからの側面についての検討はされていないことをご留意ください。
また、時間に追われて書きなぐった内容になっていて後で書き直すかもしれませんがご了承いただければと思います。
結論
先に結論だけ書いてしまうと、Github EnterpriseやArtifactoryなどのprivate npm registryを使っている自分たちの環境ではRenovateが良さそうでした。
その理由としては以下の通りです。
- Dependabotはgithub.com上で使うには良さそうだが、Github Enterpriseだと更新内容をPull Requestに含めてくれない(自分でそういう実装をする必要がある)
- DependabotよりRenovateの方がPull Requestのグルーピングがしやすい
つまり、機能的にRenovateの方が充実していてRenovateの方がやりたいことがすぐ実現できる、というのが理由です。
この結論に至るまでの話を以下長々と書いていきます。
ライブラリ自動更新のために求められていたこと
開発環境や自分たちで作っているライブラリなどの構成、npmレジストリ等の都合から以下のことができる必要がありました。
そのため、以下の項目を満たすことを条件としています。
- 社内のGithub EnterpriseのAPIを利用できること
- Artifactory等のprivate npm registryのAPIを利用できること
- 指定した条件にマッチするライブラリだけを更新できること(ホワイトリストあるいはブラックリストが指定できる)
- モノレポなど複数のライブラリが一つのリポジトリで管理されているものもうまくバージョンを揃えて更新できること
この4つのうち、最後のものについては少々イメージしにくいかもしれないので補足します。
モノレポの構成
内製のライブラリが多く、ライブラリごとにリポジトリを作っていると管理が大変になるので、似たようなもの、一つの機能を実現する際に同時に変更する必要があるものを一つのリポジトリにしてモノレポとして扱っています。
APIクライアント系
APIクライアントはシステムごとにAPIクライアントを作るため、システム数分のクライアントができます。
個々のAPIクライアントは概ね独立していますが、各クライアントで共通化したベース部分にあたる api-client-kit というものも存在し、そのコードに手が入るときは他のクライアントも影響を受ける場合があります。
ディレクトリ構造としては以下のようなイメージです。
api-clients
├─ packages
│ ├─ api-client-kit (各クライアントのベース的なライブラリ)
│ ├─ foo-api-client
│ ├─ bar-api-client
│ └─(以下多数)
├─ package.json
├─ lerna.json
├─ README.md
└─ yarn.lock
Component系
Atomic Designに基づくReactのView Componentや、ドメイン知識を持ったもの、Container Componentなど、ある機能を実装するときにはだいたい一緒に変更する必要があるコード群を一つのモノレポにしています。
一見すると役割が別のものが一緒になっていて気持ち悪さを感じますが、役割に応じて作業者が変わるわけではないので、別リポジトリになっていると個々のリポジトリで作業してpublishして取り込んで、といった流れは作業効率がとても悪くなるのでモノレポにしています。
ディレクトリ構造としては以下のようなイメージです。
components
├─ packages
│ ├─ container-component
│ ├─ definitions
│ ├─ view-component
│ └─(以下略)
├─ package.json
├─ lerna.json
├─ README.md
└─ yarn.lock
モノレポ系パッケージの自動更新
前述のようないくつかのモノレポが存在し、各アプリケーションはそれらのパッケージをバージョンの齟齬なく上げる必要があります。
例えば、container-component@1.0.1 に相当するのはview-component@2.0.2であったりすることもあり(これは元は別リポジトリだった歴史的経緯もあり)、全部同じバージョンでいいという状態ではありませんでした。
さらに、container-componentはview-componentに依存していることもあり、バージョン自動更新時にcontainer-componentだけ更新したPull Request(以下PRと略)が作られても、CIが通らず実装内容の問題なのか、view-componentと分かれてPRが出ているだけで内容自体に問題はないのかわからずマージできないという問題がありました。
api-clientsは中のパッケージがほぼ独立なので個別のPRに分けられていてもそんなに問題はありませんが、ベースとなるclient-kitに手が入る場合は依存関係が発生してしまうのと、一気に複数のクライアントのバージョンが上がった際、個別にPRが出てCIのマシンパワーが自動更新系のPRで吸われてしまう、という問題を避けたい事情もありました。
そのため、基本的にモノレポ内のパッケージは1つのPRにまとめて更新する必要がありました。
両ツールを使ってみての所感
前置きが長々となってしまいましたが、Dependabot, Renovate両ツールを使ってみてどうだったかという話をします。
Depentabot
先に使ってみたのはDependabotでした。というのもwebpackがdependabotを使ってマイグレーションをしていたので、それに倣ってやってみようというわりと軽いノリで試しました。
使い方
より細かい使い方に関しては他の方のドキュメントや公式のドキュメントを参考にしてもらえると良いかと思いますが、自分たちの環境(Github EnterpriseやArtifactoryを使っている)ではDockerイメージとしてある dependabot-core そのものやGithubマーケットプレイス版が使えないので、 dependabot-script の実装をベースとする使い方をしました。
具体的には generic-update-script.rb を自分たち用に以下のようなカスタマイズすることで利用しています。
- デフォルトブランチの指定を環境変数から渡せるようにする
- ArtifactoryのトークンやregistryのURLを環境変数から渡せるようにする
- 更新対象のパッケージをホワイトリスト管理できるようにする
特に3つ目に関しては、依存しているライブラリすべてを対象にするとPRの数が多くなり管理しきれなくなってしまうので、運用が慣れるまでは更新頻度が高い内製ライブラリに絞るようにしています。
所感
Dependabotだと1つのパッケージにつき1つのPRが作られるので、前述のモノレポ系のパッケージ更新がうまくできないという問題にぶつかりました。
また、PRのdescriptionに Bumps @nicolive/definitions from 3.2.0 to 4.0.1.
といった簡易的な記述しかされないのも何が変わったのかよくわからない、という問題もありました(これはgithub.comではないからかと思っています)。
ひとまず前者の問題に関して、以下2点のdependabot-script改修を検討しました。
- 指定したパッケージを1つにまとめてPRを投げる
- 指定したパッケージは別のブランチにPRを向ける
指定したパッケージを1つにまとめてPRを投げる
dependabot-coreのPRを作る部分の実装を見ると、1つのPRに複数のパッケージを混ぜるような設計にはなっていないように見受けられました。
また、内部の実装もDIしやすいようには作られておらず、利用する側でちょっとカスタマイズするといったことも困難でした。
そのためこの方法は検討を打ち切りました。
指定したパッケージは別のブランチにPRを向ける
例えばデフォルトブランチはdevelopブランチで、通常の更新はdevelopブランチへPRを投げるが、モノレポ系パッケージの更新は別途ブランチを用意してそちらにPRを投げ、そのブランチである程度まとめてしまってからdevelopに対してPRを投げなおす、という方針を検討しました。
しかしその場合、別途作ったブランチとdevelopブランチでコンフリクトが起こってしまう場合に自動で解消するのが困難(あるいはそれ用の実装をしなければならない)だったり、パッケージごとにベースブランチを分けられるようにするという実装は元のdependabot-scriptでは想定されておらず、フルスクラッチで書き直すレベルで手を加えないといけないといけないことがわかりました。
そうなるとこの方法もコストが高く、この段階からDependabotの使いづらさにつらみを感じるようになってきました。
結果的にはこのあたりでDependabotに見切りをつけてRenovateを試し、Renovateの方が良さそうという結論になってしまったためそれ以上Dependabotに深入りしないままになってしまいましたが、より良い使い方があるという知見のある方がいらっしゃいましたら、ぜひ教えていただければと思います。
Renovate
順番的にはDependabotの次に試したのでDependabotのつらいところが色々解消された印象が強く、ややバイアスがかかっている感じは否めませんが、設定が細かくできてとても使いやすかったです。
所感
何より packagePatterns によってPRのグループ設定ができるのが良いです。
サンプルとしてアプリケーション側で入れている設定を以下に示します。
{
"extends": [
"config:base",
":respectLatest",
":prHourlyLimitNone",
":semanticCommits",
"group:linters"
],
"packageRules": [
{
"packagePatterns": [
"^@nicolive/container-component$",
"^@nicolive/view-component$",
"^@nicolive/definitions$"
],
"groupName": "components"
},
{
"packagePatterns": [
"@nicolive/.*api-client"
],
"groupName": "api-clients"
},
{
"excludePackagePatterns": ["@nicolive"],
"enabled": false
}
],
"encrypted": {
"npmToken": "****"
},
"npmrc": "@nicolive:registry=https://****/****/\n//****/****/:_auth=${NPM_TOKEN}"
}
これにより、以下のことが苦も無く行えるようになりました。
- モノレポ系パッケージのグルーピング
- 対象としたいパッケージの指定
- PR descriptionから差分情報が辿れる
- 追加で更新が来た場合の同一PR内での追従
- コンフリクトした場合の自動rebase/retry
- PR発行数のコントロール
最後のPR発行数のコントロールですが、これは prConcurrentLimit や prHourlyLimit によって制御できるものです。
グルーピングや対象パッケージを絞れるといっても日々業務中に容赦なくRenovateからバシバシPRが飛んでくる(PRが作られるとSlackに通知が来る)のがプレッシャーに感じるメンバーも少なくなかったため、量をコントロールできることも地味に有益な機能と感じました。
気になったところ
Renovateで唯一気になったのは、各リポジトリの設定でnpmrcを全部入れてあげないといけないことでした。
トークン類は基本的にはリポジトリに含めたくないため、リポジトリ内の.npmrcにはregistryのURLのみ記載し認証情報は各自ローカルマシンやCIサーバの特定のもののみに含めるという運用ですが、Renovateにはまだ各所に配置したnpmrcの内容をマージすることはまだできないらしく、仕方なくトークンをencryptして乗せるという運用になっています。
総評
冒頭で書きましたが、結論としてはDependabotよりRenovateの方が使いやすい、という結論に至りました。
Depentabotもgithubマーケットプレイス版ならある程度は使えそうな気がしますが、込み入った運用になっている内製ライブラリの更新には向いてなさそうでした。
今後の発展に期待したいと思います。
Renovateは設定も細かくでき痒い所にも手が届く感じがとてもよかったです。
.npmrcのマージだけはできるようになって欲しいですが気長に待つことにします。
本記事に関して間違い、ご指摘などありましたらコメントいただけますと幸いです。