github actionsでdockerビルドする時のキャッシュ方法を比較してみた

はじめに

この記事では、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を目指していきたいです。

参考

https://docs.docker.com/build/ci/github-actions/cache/

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

1人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です