💚

Renovate の Best Practices から学ぶ依存関係管理の考え方

2024/04/17に公開
2

Renovateはプロジェクトの依存関係の更新を自動化するツールです。

npmライブラリやGitHub Actions、Dockerイメージのバージョンなどを監視し、ライブラリアップデート用のPRの作成を行います。

https://docs.renovatebot.com/

Renovateは豊富な構文から柔軟な設定ができますが、プリセットと呼ばれるカスタマイズされた設定集を利用することもできます。

その中でconfig:best-practicesというRenovateが考えるベストプラクティスのプリセットが用意されています。

https://docs.renovatebot.com/upgrade-best-practices/#upgrade-best-practices

このプリセットを読み解くことで、Renovateがどのようにライブラリのバージョンを指定し運用するべきか知ることができます。
この記事ではconfig:best-practicesの設定を読み解き、どのようにライブラリの依存関係を扱うかを考えていきます。

config:best-practice で設定されている項目

執筆時点のv37.303.2では、config:best-practicesに以下の4つのプリセットが含まれています。

{
  "configMigration": true,
  "extends": [
    "config:recommended",
    "docker:pinDigests",
    "helpers:pinGitHubActionDigests",
    ":pinDevDependencies"
  ]
}

https://docs.renovatebot.com/presets-config/#configbest-practices

この中でconfig:recommendedはRenovateを運用する上での便利な設定集であるため、今回は触れません。
それ以外の3つの設定項目は、それぞれのライブラリに対してバージョンをどう指定するべきかを示したものです。

各項目についてそれぞれ順番に解説します。

Dockerイメージはダイジェスト(ハッシュ値)でバージョンを固定する

docker:pinDigestsとは、Dockerイメージのバージョン指定にダイジェスト(ハッシュ値)を使う設定です。node@sha256:d938c1761e3afbae9242848ffbb95b9cc1cb0a24d889f8bd955204d347a7266eのようにバージョンを指定し、この値が更新されたタイミングでライブラリアップデート用のPRが作成されます。

なぜ、Dockerイメージでは通常のバージョンタグ(v1.0.0など)で指定しないかというと、Dockerイメージのバージョンタグは上書きできるためです[1]
そのため、同じバージョンであってもイメージの内容が変わってしまう可能性があります。

一方、ダイジェストはイミュータブル(不変)な値です。つまり、この値を変更しなければDockerイメージの内容が変更されることはありません。このようにバージョンを固定することで、意図せずにイメージの内容が変更されるリスクを防ぐことができます。

https://docs.renovatebot.com/docker/#digest-pinning

ちなみにJavaScriptのnpmパッケージはv1.0.0といったバージョンはイミュータブルです。
そのためダイジェストによる固定は不要となります。

ただ自分の普段の運用では、Dockerイメージはダイジェストではバージョンを固定せずに、通常のバージョンタグを利用しています。
普段使うイメージは基本的に公式のイメージが多く、v1.x.xのようなバージョンで中身が変わるようなイレギュラーな事象は起きないと思っているためです。

ただし、latestタグのような明らかにミュータブルなタグは利用しないようにしています。
なぜイミュータブルなタグがダメなのかは、良いブログがあったので興味がある方は以下の記事を読むのがおすすめです。

https://progret.hatenadiary.com/entry/2020/01/23/012349

3rdパーティのGitHubActionsはダイジェストでバージョンを固定する

helpers:pinGitHubActionDigestsは、3rdパーティのGitHub Actionsをワークフローで利用する際にそのバージョンをダイジェストで指定する設定です。Dockerイメージと同様に、ダイジェストが更新されたタイミングでライブラリアップデート用のPRが作成されます。

GitHub Actionsでもバージョン指定の方法によっては、同じバージョンで中身を書き換えることができます。

GitHub Actionsではワークフローごとに発行するGITHUB_TOKENという使い捨てトークンを利用することで、ワークフローに権限を付与しさまざまなCIを実行できます。
PrivateレポジトリのRead/Writeなどの権限を渡すこともあるため、不用意に悪意のあるGitHub Actionsに利用されるのは非常に危険です。

そのため、GitHub公式のセキュリティガイドでも、信頼できるライブラリ作者のGitHub Actionsを利用する場合以外はダイジェストを指定することで、意図しない変更が入らないことを検知するように推奨されています(そもそも信頼しない作者のツールを使わないという話もあります)。

https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions

ただ、実際問題いくらバージョンを固定してトラッキングしたとしても、そもそもセキュリティポリシーを守られているかを全てのソースコードを読んで判断するのは大変です。

GitHub Actionsのセキュリティ上のプラクティスをちゃんと守れている検知するツールとしてghalintを利用するのも良いと思います。

https://zenn.dev/shunsuke_suzuki/articles/github-actions-ghalint

https://github.com/suzuki-shunsuke/ghalint

DevDependenciesではパッチバージョンまで固定する

:pinDevDependenciesとは、開発環境で使用するnpmライブラリのバージョンをパッチバージョンまで固定する設定です。開発環境では再現性を確保するために、厳密にバージョンを指定することが推奨されています。

https://docs.renovatebot.com/dependency-pinning/

一方、本番環境で使用する依存関係(dependencies)については、アプリケーションとライブラリで扱いが異なります。

アプリケーションの場合は、再現性を確保するためにパッチバージョンまで固定することが望ましいです。
ライブラリの場合は、ユーザー側で同じライブラリの異なるバージョンが重複してインストールされる可能性があるため、バージョン範囲( ~ や ^ )を指定して柔軟にバージョン管理を行うことが推奨されます。

また、最近だと新しいパッケージマネージャー(pnpm、bunなど)に移行することもあると思います。
その際にロックファイルも合わせて移行する必要がありますが、事前に全てのパッケージをパッチバージョンまで固定しておくと意図しないバージョンアップを防ぐことができます。

おわりに

RenovateのベストプラクティスのプリセットからnpmライブラリやGitHub Actions、Dockerイメージのバージョン管理をどのように行うのが良いかを読み解きました。

このようにツールのベストプラクティスの設定を読み進めると、そのツールからみた考え方が学べるのは面白いなと思いました。

脚注
  1. AWS ECRでは--image_tag_mutabilityをIMMUTABLEにすることでイミュータブルなタグを指定できます。 ↩︎

Discussion