背景
CI環境でのDockerイメージビルドの速度向上は、多くの開発者にとって切実な課題です。
簡単な対策は
- CircleCIでは Docker レイヤー キャッシュ (DLC)を有効にする。
- GitHub Actionsでは
docker/build-push-action
でcache-from: type=gha cache-to: type=gha,mode=max
を使う https://docs.docker.com/build/cache/backends/gha/#using-dockerbuild-push-action
しかしこれらのキャッシュはCI側で有効期限があり、キャッシュ保存量制限もあり、CircleCIの場合はコスト(料金)もかかります。
なので次によく取られる対策は、キャッシュをイメージレジストリに保存するやり方になります。
参考: https://shepherdmaster.hateblo.jp/entry/2022/06/11/123419
これによりCI環境でもレイヤーごとのキャッシュは可能になりました。 しかしパッケージが変更された場合(例えばpackage.jsonやgo.modやGemfileが変更になった場合)はパッケージのインストールはフルで実行されてしまいます。 ローカルで実行するのであれば追加/変更したパッケージだけインストールすればすむのにCI環境では少しでも変更があると毎回フルインストールが走ってビルド時間が延びるのが悩みです。
さて、このイメージビルド時のパッケージのインストールにキャッシュを効かせて速くする方法があります。
RUN --mount=type=cache
です。
https://docs.docker.com/build/guide/mounts/
これはローカルマシンでは効果がありますが、同一マシンじゃないとキャッシュが効かないため、マシンが毎回変わるCIでは効果がありません。またキャッシュ保存先がDockerが管理するディレクトリに保存されるためキャッシュが取り出しづらいという問題があります。
reproducible-containers/buildkit-cache-danceという救世主
RUN --mount=type=cache
のキャッシュをGitHub Actions上で保存できるようにするためのActionがreproducible-containers/buildkit-cache-danceです。
これを使えば、apt-getやパッケージ(npm, Goライブラリ, Gem等)のインストール結果をキャッシュすることができるようになります。
このActionやってることはシンプル(だが泥臭い)で、
- 前処理として専用のイメージをビルドしてGitHub Actionsに保存されたキャッシュをDocker cacheにコピー
- Post Actionで専用のイメージをビルドしそのビルド内で
RUN --mount=type=cache
で使ったキャッシュを取り出しホストマシン側に保存。それがGitHub Actionsのキャッシュとして保存される https://github.com/reproducible-containers/buildkit-cache-dance/blob/main/post
という流れになっています。
apt-getのキャッシュの仕方
https://github.com/reproducible-containers/buildkit-cache-dance/tree/main#examples を参考
bundle installのキャッシュの仕方
Dockerfile:
WORKDIR /app/ COPY Gemfile Gemfile.lock ./ RUN bundle config set path .cache/bundle RUN --mount=type=cache,sharing=locked,target=/app/.cache/bundle \ bundle install && \ cp -ar .cache/bundle .bundle && \ bundle config set path .bundle
Action:
jobs: build: name: container build runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Cache bundle-install uses: actions/cache@v3 with: path: bundle-install-cache key: bundle-cache-${{ hashFiles('Dockerfile') }} - name: inject bundle-install-cache into docker uses: reproducible-containers/buildkit-cache-dance@v2.1.3 with: cache-source: bundle-install-cache cache-target: /app/.cache/bundle - uses: docker/build-push-action@v5 with: context: . tags: user/app:latest
その他
試してないですが、npmやGoライブラリなどのキャッシュも同様に出来るはずです。
注意点
CI上でのキャッシュのrestore、save、そしてイメージへのキャッシュのinject、イメージからのキャッシュのextractはキャッシュサイズが大きくなればなるほど時間がかかるようになります。
そのためRUN --mount=type=cache
のキャッシュが効くことによる削減時間よりも先述したオーバーヘッドのほうが大きくなる場合があるので注意が必要です。
まとめ
RUN --mount=type=cache
のキャッシュをGitHub Actionsのキャッシュとして保存し、イメージビルドを速くする方法を紹介しました。
キャッシュのサイズによりますがDependabotやRenovateでパッケージのアップデートを頻繁に行う場合でもイメージビルドが速くなるのは嬉しいですね。