はじめに
この記事では、github actionsでdockerのbuildをキャッシュする方法を4つ調べたので、その比較を書いていきたいと思います。
結論だけ知りたい方は、こちら
ベースコード
今回の比較で使うベースのサンプルコードです。
dockerのビルドにはbuild-push-actionを使い、push先にはgithub container registry(ghcr)を利用します。
Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /myApp COPY ./myApp/myApp ./ RUN dotnet restore RUN dotnet publish -c Release -o /app --no-restore FROM mcr.microsoft.com/dotnet/aspnet:8.0 ENV ASPNETCORE_HTTP_PORTS 5077 WORKDIR /app COPY --from=build /app ./ ENTRYPOINT ["dotnet", "myApp.dll"]
.github/workflow/ci.yaml
name: test on: pull_request: env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: build-container-and-push-image: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to the Container registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }}
キャッシュの比較
github actionsで利用できるdockerキャッシュには、inlineキャッシュ・registryキャッシュ・githubキャッシュ・localキャッシュの4つがあります
inlineキャッシュ
inlineキャッシュは、作成するdocker imageにキャッシュを埋め込んで、コンテナレジストリに一緒にpushする方法です。
Build and push Docker imageのステップを以下の様に書き換えます。
- name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=registry,ref=${{ steps.meta.outputs.tags }} cache-to: type=inline
4つの方法の中で最も高速です。
しかし、4つの方法の内、inlineキャッシュのみ、レイヤーキャッシュに対応していません。
そのため、マルチステージビルドや複雑なビルドフローをしている場合は、あまり適していません。
また、inlineキャッシュは、docker imageをコンテナレジストリにpushする必要があり、ローカルにイメージファイルを作成する(outputs: type=docker,dest=/tmp/myimage.tarと指定する)場合は利用できません。
build-push-actionは現状outputsを1つしか指定できないので、コンテナレジストリにpushしつつ、ローカルにtarファイルも作ることはできません。
実験結果
キャッシュ無し
キャッシュ有り
プログラムを変更
レイヤーキャッシュ不可で、全部再ビルドになるため、キャッシュ無しと同じ時間がかかる
registryキャッシュ
registryキャッシュは、作成するdocker imageとは別に、キャッシュ用のイメージを作成してコンテナレジストリにpushする方法です。
Build and push Docker imageのステップを以下の様に書き換えます。
- name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=registry,ref=${{ steps.meta.outputs.tags }}-cache cache-to: type=registry,ref=${{ steps.meta.outputs.tags }}-cache,mode=max
githubリポジトリのpacagesから以下のように、作成したイメージ(pr-2)とは別に、キャッシュ用のイメージ(pr-2-cache)が生成されていることが確認できます。
実験結果
キャッシュ無し
キャッシュ用イメージをpushする分inlineキャッシュよりは遅くなります。
キャッシュ有り
キャッシュ用イメージをpushする分inlineキャッシュよりは遅くなります。
プログラムを変更
レイヤーキャッシュ可能なので、途中まで、キャッシュを利用しその後ビルドするため、キャッシュ有りとキャッシュ無しの間の時間になります。
githubキャッシュ
githubキャッシュは作成したキャッシュを、github actionsのキャッシュ機能に保存する方法です。
Build and push Docker imageのステップを以下の様に書き換えます。
- name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max
githubリポジトリのactionsタブを開き、左メニューのcachesを選択すると、複数のファイルに分かれて、キャッシュが保存されているのが確認できます。
ただし、githubキャッシュはExperimental(実験的)な機能なので注意が必要です。
実験結果
キャッシュ無し
キャッシュをgithubにアップロードする分inlineキャッシュよりは遅くなります。
キャッシュ有り
キャッシュをgithubにアップロードする分inlineキャッシュよりは遅くなります。
localキャッシュ
localキャッシュは、ファイル システム上のディレクトリにキャッシュファイルを保存する方法です。
github actionsで運用する場合は、actions/cacheを併用してそのキャッシュファイルをgithub actionsのキャッシュに保存します。
新しくキャッシュを作成しなおす時に古いキャッシュが削除されない問題があり、それを回避するために、
1. 一度別名でキャッシュファイルを作る
2. 前のキャッシュファイルを削除する
3. 新しいキャッシュファイルの名前を変更する
という、ひと手間が必要です。(https://github.com/docker/build-push-action/issues/252)
Build and push Docker imageのステップを以下の様に書き換え、Cache Docker layersとMove cacheのステップを追加します。
- name: Cache Docker layers uses: actions/cache@v3 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} restore-keys: | ${{ runner.os }}-buildx- - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max - name: Move cache run: | rm -rf /tmp/.buildx-cache mv /tmp/.buildx-cache-new /tmp/.buildx-cache
githubキャッシュと同じく、githubリポジトリのactionsタブからキャッシュファイルが保存されているのが確認できます。
キャッシュをファイルとして扱えるので、例えば、他の場所にも保存したいなど、キャッシュを細かく制御・管理したい場合に適しています。
作成されるキャッシュファイルのサイズは、githubキャッシュを採用した場合に保存されるキャッシュの合計サイズと大体同じです。
実験結果
キャッシュ無し
キャッシュ有り
今回のサンプルでは、なぜか、キャッシュ有りの方が、キャッシュファイルを生成する処理に時間がかかってしまったため、効果が無いような結果にしまいました。
アプリのビルド処理などは、キャッシュを利用して短縮できているため、より大きなプロジェクトになれば、時間短縮効果も出てくるはずです。
キャッシュの保存先の比較
キャッシュ方法によってキャッシュの保存先が異なるので、おまけとして、その比較も行いたいと思います。
github cache
作成から7日間アクセスされないと削除されます。
また、リポジトリ内で、合計10GBまでという制約があります。
そのため、多くのPRが作成され、複数のブランチで同時に開発が進行するようなプロジェクトだと、適していないかもしれません。
https://docs.github.com/ja/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
github container registory
publicだと無料です。privateの場合GitHub Freeの場合、500 MBのストレージと1GB/月のデータ転送が無料です。
https://docs.github.com/ja/billing/managing-billing-for-github-packages/about-billing-for-github-packages
おわりに
今回はgithub actionsでコンテナをビルドする際に利用できるキャッシュの方法を比較しました。
結論として、以下の様な方針で、キャッシュ方法を選択するのが良いと思います。
適切なキャッシュ手段を選択して、快適なCIを目指していきたいです。
参考