🦀

RustのDockerイメージビルドを高速にする

2024/09/15に公開

要約

以下を参考にしましょう。

https://docs.docker.com/guides/language/rust/develop/

はじめに

Rustは素晴らしい言語ですが、弱点もあります。その弱点の一つが、コンパイルが遅いことです。

特にDockerイメージのビルドにおいては、キャッシュをうまく使わないと、依存関係を含めて毎回すべてをビルドすることになってしまいます。

本稿ではDockerイメージのビルドを高速化する方法について解説します。

従来の方法とその問題点

レイヤーキャッシュの利用

Dockerにはレイヤーキャッシュが存在します。これは入力が全く同じであれば、以前ビルドしたレイヤーを使い回すというものです。

以前から知られている方法として、レイヤーキャッシュをできるだけ有効活用できるように工夫する、というものがあります。例えば以下のような方法です。

https://stackoverflow.com/a/58474618

https://github.com/hiterm/bookshelf-api/blob/f9e098eac9fd201dbfdc0e317abe6c16aac72727/Dockerfile#L1-L24

簡単に説明すれば、依存ライブラリのコンパイルだけ先に済ませておき、その後で本体のソースコードのコンパイルをするという2段階に分けます。こうすることで、Cargo.tomlとCargo.lockが変わらない限りは、依存ライブラリのコンパイルをスキップすることができます。

使ったことがありませんが、cargo-chefも同じアプローチのようです。

https://github.com/LukeMathWalker/cargo-chef

問題点

Cargo.tomlが変更された際は、結局フルコンパイルが走ることになります。

できることなら、依存関係が追加されたり更新されても、その差分だけをコンパイルしてくれると嬉しいものです。

またCargo.tomlのバージョンフィールドが変更されただけでフルコンパイルが走るというのもストレスです。

レイヤーキャッシュのためにダミーのmainファイルを用意するなど、少し無理矢理感の漂うハックであることも確かです。

RUN --mount=type=cacheを使う

上記問題を解決する方法が存在します。それがRUN --mount=type=cacheを利用するというものです。

方法

幸い、Docker公式がドキュメントを用意してくれています。これを参考にしましょう。

https://docs.docker.com/guides/language/rust/develop/

上記の例を書き換えると以下のようになります。

https://github.com/hiterm/bookshelf-api/blob/da74975169746398b168519dc0edfe120f103ab8/Dockerfile#L1-L50

解説します。以下のRUN --mount=type=cacheという機能が使われています。

https://docs.docker.com/reference/dockerfile/#run---mounttypecache

これは、指定したディレクトリの内容を、レイヤーやビルド間をまたいでキャッシュするためのものです。レイヤーキャッシュが無効化されてもこちらのキャッシュは残るので、うまく使えば強力な武器になります。

Rustの公式ベースイメージを利用する場合、/usr/local/cargo/registry/をキャッシュしましょう。また開発対象のアプリケーションのtargetディレクトリもキャッシュしましょう。こうすることで、依存ライブラリのビルド結果がキャッシュされるようになります。

この機能を使うためには、BuildKitが有効化されている必要があります。現在の大抵のローカル環境では有効化されているでしょう。ただし、GitHub Actionsでは https://github.com/docker/setup-buildx-action を使って明示的に有効化する必要があることに注意してください。

利点

依存ライブラリやCargo.tomlが変更されても、キャッシュが効きやすくなります。

また、面倒なハックが要らなくなり、Dockerfileがシンプルになります。

GitHub Actionsにおいて

実は、RUN --mount=type=cacheはGitHub Actionsでは公式にサポートされていません。より正確に言えば、異なるビルドをまたいだキャッシュの永続化がサポートされていません。これではCIでイメージをビルドするたびに多大な時間を消費することになってしまいます。

GitHub Actionsでもキャッシュを永続化させるためには、以下の3rd partyのActionを利用すると良いです。

https://github.com/reproducible-containers/buildkit-cache-dance

実例は以下のようになります。

https://github.com/hiterm/bookshelf-api/blob/da74975169746398b168519dc0edfe120f103ab8/.github/workflows/ci.yml#L30-L57

まとめ

これからは RUN --mount=type=cache をうまく使って、高速にDockerイメージをビルドしましょう。

参考

Discussion