2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitHub ActionsでPythonのDocker imageをBuildするときに考えるCacheのこと

Last updated at Posted at 2025-03-09

課題

GitHub ActionsでDocker イメージをビルドしてる際にどうもうまくCacheされてなくて、Library変更やDockerfileをいじってないのに毎回 pip installをしてビルド時間が長いケースがたまにあると思います。

Checkpoints

  • Dockerfileの書き方
  • docker/build-push-action
    • cache type:
      • gha <- GitHub Runnerの場合はghaを使う✅️ (ただし、self-hostedの場合は遅くなる可能性もあるので別途考慮⚠️)
      • inline <- simple but min のみサポート
      • registry <- inline cacheの拡張版 (imageとcacheを分離できる、 cache mode maxが使用可)
  • Docker layer cache: docker/build-push-actionを使えばOK✅️
  • GitHub Actions Cache: docker/build-push-actionのcache typeのうちの一つghaがGitHub Actions Cache. 別のCache type registryなどもあるので必ずしも使わなくてもいい
  • Pip Cache:
    • Docker imageをbuildするときのDockerfile内でのpip installはDocker layer cacheでcacheする
    • 一方、GitHub Actions上でテストを実行する場合は、GitHub Actions Cacheを使ってCacheする
    • Docker layer cacheを使う場合はrequirements.txtが変更されると全てのpackageのインストールをやり直すので時間がかかる
  • Self-hosted RunnerとGitHub Runner
    • GitHub Actions Cacheを使うとSelf-hosted RunnerのRegionによっては遠くなるので、逆に遅くなるケースもあるので注意
  • GitHub Actions Cacheの制約
    • PRで使われるCache: PR上では同一PRで実行された際に保存されたCacheまたはmain上で保存されたCacheを使うことができる
    • mainで使われるCache: mainで実行されるWorkflowはmainで保存されたCacheのみを使うことができる
    • PRで保存されるCache: PRに対して保存されるCacheはそのPRの後続の実行のときにのみ使うことができる。mainや他のPRからは使えない
    • mainで保存されるCache: mainで保存されるCacheは、他のPRやmainブランチからも使える

Dockerfile

シンプルなケースでは、multi-stageをせずに以下のように書けば良い

FROM python:3.12.3-slim

WORKDIR /app

COPY requirements.txt requirements.txt

RUN pip install -r requirements.txt

COPY . .

ENV PORT=8080
EXPOSE 8080
apt getが必要であれば

以下のような RUNCOPY requirments.txt requirements.txt 前に追加する

RUN --mount=type=cache,target=/var/cache/apt \
    --mount=type=cache,target=/var/lib/apt \
    apt-get update && \
    apt-get install -y --no-install-recommends \
    curl \
    git \
    && rm -rf /var/lib/apt/lists/*

この場合、このLayerでCacheできないとそのあとにある pip installもすべてやり直しになるので、こういったケースではmulti-stageにしたほうが良いはず。

GitHub Actions

GitHub Actions の設定では、cacheはGitHub Runnerを使ってる場合は、ghaを選択すれば良い。

      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        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 全体の例:

name: build-and-push

on:
  release:
    types:
      - published
  push:
    tags:
      - 'v*'
    branches:
      - main # this is necessary for pr to utilize the cache
  pull_request:
    paths:
      - .github/workflows/build-and-push.yml
      - docker-layer-cache/*

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push-image:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - 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: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Cacheのケース

PRでのCacheは他のPRからは使われないという点は注意が必要 (Restrictions for accessing a cache)

  • PR
    • PR上の1回目のWorkflow
      • mainにCacheがある&使える -> mainのCacheを使う
      • それ以外 -> No Cache
    • 同じPRの2回目以降のWorkflow -> PR上で前回実行されたときに保存されたPRのCacheを使う
  • mainにmerge
    • 初めてCacheを入れたときのPush -> No cache (PRで保存したCacheはmainや他のPRからは使われない)
    • 前のmainでCacheされたCacheが使える -> mainのCacheを使う
    • Dockerfileが更新されている -> No cache or lower cache rate (layer cacheなので部分的に使えるケースもある)

Cacheされたケースの例: Cacheされて pip installが丸々Skipされるので5sで終わった

Screenshot 2025-03-09 at 10.21.47.png

Tips

キャッシュ容量の上限 10 GiBにいってしまうケース

mainだけでCacheを保存するように設定する

Dockerfileやrequirements.txtを変更してないのにCacheがされてない

apt-get update などが前にあって変更されると後続のLayerがすべてBuildされ直す。

RUN --mount=type=cache,target=/var/cache/apt \
    --mount=type=cache,target=/var/lib/apt \
    apt-get update && \
    apt-get install -y --no-install-recommends \
    curl \
    git \
    && rm -rf /var/lib/apt/lists/*

pip install requirements.txt の前に入っているとLayer Cacheが効かないことがある。apt-get installがある場合はmult-stageにしたほうがよいかも。

poetryなど requirements.txtで直接管理してない場合

      - name: Export requirements.txt
        run: |
          poetry self add poetry-plugin-export
          poetry export -f requirements.txt --output requirements.txt --without-hashes

というStepをBuild前にいれてたのですが、どうもこれだとchecksumが変わってしまうようだった modification time は変更されても大丈夫と書いてあったが、creation timeも変わってしまうとCacheが効かなくなってしまうようです。

この場合は、 poetry.lockが変更されたら requirements.txtを更新してrepo内へPushするようにしました。

GitHub Actions Cacheをマウントする (更に高速化)

上で紹介したDockerfileとGitHub ActionsではシンプルにDocker Layer Cacheを使って高速化できるのですが、requirements.txtが一つでも更新されると、すべてDownloadし直すためとても遅くなってしまいます。
この部分を改善するためにGitHub Actions CacheとDockerのmountを活用する方法を紹介します。

Dockerfileは以下のように書きかえます。みそは、RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \ pip install -r requirements.txtになります。cacheから/root/.cache/pipにマウントして pip install を実行します。

FROM python:3.12.7-slim

WORKDIR /app

COPY requirements.txt .

RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \
    pip install -r requirements.txt

COPY . .

ENV PORT=8080
EXPOSE 8080

CMD ["python", "app.py"]

これに対応してGitHub Actions側も以下のように変更します。

... # metadata の後を以下のようにする
      - name: Restore pip cache
        uses: actions/cache/restore@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
        id: pip-cache
        with:
          path: root-dot-cache-pip
          key: pip-cache-${{ hashFiles('requirements.txt') }}
          restore-keys: |
            pip-cache-

      # buildkit-cache-dance を使ってキャッシュを Docker ビルドに注入
      - name: Inject cache into Docker build
        uses: reproducible-containers/buildkit-cache-dance@5de31fc1534ed8789e63d41ea933c5df9944a261 # v3.1.0
        with:
          cache-map: |
            {
              "root-dot-cache-pip": "/root/.cache/pip"
            }
          skip-extraction: ${{ steps.pip-cache.outputs.cache-hit }}

      # Docker イメージをビルドして必要に応じてプッシュ
      - name: Build and push Docker image
        uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
        with:
          context: docker-layer-cache
          file: docker-layer-cache/Dockerfile.cache
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            BUILDKIT_INLINE_CACHE=1

      - name: Save pip cache
        uses: actions/cache/restore@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
        if: github.ref_name == 'main'
        with:
          path: root-dot-cache-pip
          key: ${{ steps.pip-cache.outputs.cache-primary-key }}

このようにすることで、一度InstallしたPackageをGitHub Actions Cacheに保存することで毎回Build時にDownloadしなくてよくなるので高速化することができます。

一方で、requirements.txt の変更がないときには、Docker layer cacheを直接使ったほうが断然速くなるので一長一短かなと思います。 cacheからmountする場合は、毎回cacheから取得する部分が実行されてしまうのでDocker layer cacheを使うよりも遅くなってしまいます。

References

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?