RustのDockerイメージビルドを高速にする
要約
以下を参考にしましょう。
はじめに
Rustは素晴らしい言語ですが、弱点もあります。その弱点の一つが、コンパイルが遅いことです。
特にDockerイメージのビルドにおいては、キャッシュをうまく使わないと、依存関係を含めて毎回すべてをビルドすることになってしまいます。
本稿ではDockerイメージのビルドを高速化する方法について解説します。
従来の方法とその問題点
レイヤーキャッシュの利用
Dockerにはレイヤーキャッシュが存在します。これは入力が全く同じであれば、以前ビルドしたレイヤーを使い回すというものです。
以前から知られている方法として、レイヤーキャッシュをできるだけ有効活用できるように工夫する、というものがあります。例えば以下のような方法です。
簡単に説明すれば、依存ライブラリのコンパイルだけ先に済ませておき、その後で本体のソースコードのコンパイルをするという2段階に分けます。こうすることで、Cargo.tomlとCargo.lockが変わらない限りは、依存ライブラリのコンパイルをスキップすることができます。
使ったことがありませんが、cargo-chefも同じアプローチのようです。
問題点
Cargo.tomlが変更された際は、結局フルコンパイルが走ることになります。
できることなら、依存関係が追加されたり更新されても、その差分だけをコンパイルしてくれると嬉しいものです。
またCargo.tomlのバージョンフィールドが変更されただけでフルコンパイルが走るというのもストレスです。
レイヤーキャッシュのためにダミーのmainファイルを用意するなど、少し無理矢理感の漂うハックであることも確かです。
RUN --mount=type=cacheを使う
上記問題を解決する方法が存在します。それがRUN --mount=type=cache
を利用するというものです。
方法
幸い、Docker公式がドキュメントを用意してくれています。これを参考にしましょう。
上記の例を書き換えると以下のようになります。
解説します。以下のRUN --mount=type=cache
という機能が使われています。
これは、指定したディレクトリの内容を、レイヤーやビルド間をまたいでキャッシュするためのものです。レイヤーキャッシュが無効化されてもこちらのキャッシュは残るので、うまく使えば強力な武器になります。
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を利用すると良いです。
実例は以下のようになります。
まとめ
これからは RUN --mount=type=cache
をうまく使って、高速にDockerイメージをビルドしましょう。
参考
-
https://docs.docker.com/guides/language/rust/develop/
- 記事中でも参照しました。
-
https://future-architect.github.io/articles/20240726a/
- Dockerfileの
RUN --mount=type=cache
等、新しめの機能が解説されています。
- Dockerfileの
Discussion