業界・業務から探す
導入目的・課題から探す
データ・AIについて学ぶ
News
Hakkyについて
ウェビナーコラム
◆トップ【Hakkyの社内Wiki】Strapi
AI

執筆者:Handbook編集部

GitHub ActionsのDockerのビルドの高速化

はじめに

GitHub Actions で Docker を使用していると、ビルドをするときに毎回時間がかかります。 これは cache をうまく活用することでビルドを速くすることができます。

GitHub Actions のキャッシュとリストア

GitHub Actions には workflow の依存関係をキャッシングする機能 actions/cache が備わっており、このキャッシュとリストアを上手く使うことで、ビルド時間の短縮が図れます。

ただし、GitHub は、7 日間以上アクセスされていないキャッシュエントリを削除します。 また、保存できるキャッシュ数には上限がありませんが、1 つのリポジトリ内のすべてのキャッシュの合計サイズは 10GB に制限されます。

具体的な actions/cache の workflow のコードは以下のようになります。

- name: Cache dependencies
  uses: actions/cache@v2
  with:
    path: <<PATH>>
    key: <<KEY>>
    restore-keys: <<RESTORE-KEYS>>
  • path: (必須) ランナーがキャッシュあるいはリストアをするファイルパスです。相対/絶対パスのどちらでも利用でき、ディレクトリまたは単一ファイルのいずれかで、glob パターンをサポートしています。また、単一のパスを指定することも、別々の行に複数のパスを追加することもできます。

  • key: (必須) このキーはキャッシュの保存時に作成され、キャッシュの検索に使われます。 変数、コンテキスト値、静的な文字列、関数の任意の組み合わせが使えます。 キーの長さは最大で 512 文字であり、キーが最大長よりも長いとアクションは失敗します。

  • restore-keys: (オプション) key に対するキャッシュヒットがなかった場合にキャッシュを見つけるために使われる代理キーの順序付きリストです。

キャッシュ機能を活用した Docker のビルド高速化方法

本記事ではビルドした Docker イメージを圧縮ファイルで出力しキャッシュする方法と、イメージをビルドした際に Docker によって保存される Build Cache 自体をキャッシュする方法の 2 種類を紹介します。

Docker イメージを圧縮ファイルでキャッシュする方法

この方法では以下の 2 つの機能を利用します。

  • 指定したディレクトリのキャッシュとリストアをする GitHub アクションである actions/cache
  • 指定した Docker イメージを保存・読込をするコマンドである docker savedocker load

具体的な workflow としては、キャッシュのあり/なしで以下のように動作を分けます。

  • 「キャッシュがある」場合は、docker load で tar 出力されたイメージを読み込みます。
  • 「キャッシュがない」場合はビルドした Docker イメージを tar 出力してキャッシュします。

上記を考慮して、docker の ビルドを行う workflow の例を以下に示します。

name: "Sample Workflow"
on: [push]

env:
  # Docker run する際のコンテナにリポジトリをマウントする先のパス
  PATH_MOUNT: /workspaces/myapp
  # Docker イメージの tar アーカイブ出力先のパス
  PATH_CACHE: /tmp/docker-img-arch

jobs:
  Tests:
    runs-on: ubuntu-latest
    steps:
      # リポジトリの読み込み(fetch-depth=1)
      - name: Check out repo under workspace
        uses: actions/checkout@v2

      # キャッシュ ID の作成(イメージのハッシュと日付から作成)
      - name: Create image tag
        id: imagetag
        run: |
          : # Dockerfile からハッシュ値を作成
          HASH_IMAGE=${{ hashFiles('./Dockerfile') }}
          : # 日付と 7 文字のハッシュ値で合計 13 文字の ID を作成
          VARIANT=$(TZ=UTC-9 date '+%Y%m')${HASH_IMAGE:0:7}
          : # イメージのタグを作成
          NAME_IMAGE=myTestImage
          TAG="${NAME_IMAGE}:${VARIANT}"
          : # キャッシュする tar アーカイブ名とパスの設定
          NAME_TAR="${NAME_IMAGE}.${VARIANT}.tar"
          PATH_TAR=${{ env.PATH_CACHE }}"/${NAME_TAR}"
          : # 変数を他の run でも使えるように output
          echo "::set-output name=TAG::${TAG}"
          echo "::set-output name=PATH_TAR::${PATH_TAR}"

      # この Workflow が正常に終了したら path をキャッシュ。
      # key が存在する場合、ついでに path にリストアする。
      - name: Enable cache
        id: cache
        uses: actions/cache@v2
        with:
          path: ${{ env.PATH_CACHE }}
          key: ${{ steps.imagetag.outputs.TAG }}

      # キャッシュがある場合は tar をロードしてイメージ一覧に追加
      - name: Load Docker image if exists
        if: steps.cache.outputs.cache-hit == 'true'
        run: docker load --input ${{ steps.imagetag.outputs.PATH_TAR }}

      # キャッシュがない場合は Docker イメージをビルド後、tar アーカイブをキャッシュ先に保存
      - name: Build Docker image and save
        if: steps.cache.outputs.cache-hit != 'true'
        run: |
          : # キャッシュディレクトリを作成
          mkdir -p ${{ env.PATH_CACHE }}
          : # 安定したビルドのためにベース・イメージを pull しておく(オプション)
          docker pull mybaseimage:latest
          : # イメージのビルド
          docker build -f './.devcontainer/Dockerfile' -t ${{ steps.imagetag.outputs.TAG }} .
          : # イメージのキャッシュ(tar をキャッシュ・ディレクトリに出力)
          docker save --output ${{ steps.imagetag.outputs.PATH_TAR }} ${{ steps.imagetag.outputs.TAG }}

      # 本処理
      # イメージからコンテナを起動してテスト(entrypoint.sh)を実行
      - name: Run tests for both Go and Shell Script
        run: |
          : # コンテナ内のスクリプトのパスを作成(マウントポイントから見たパス)
          path_entrypoint=${{ env.PATH_MOUNT }}/path/to/hogefuga.sh
          : # 実行
          docker run -u root -v "$(pwd)":${{ env.PATH_MOUNT }} -w ${{ env.PATH_MOUNT }} ${{ steps.imagetag.outputs.TAG }} "$path_entrypoint"

      # その他の処理(例えばカバレッジのアップデート)
      - name: Upload coverage
        uses: codecov/codecov-action@v1
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

注意点として、Docker のイメージに更新があった場合にはキャッシュされているイメージを上書きすると思いますが、key が同じ場合には更新されないため、異なる key で保存する必要があります。

Build Cache 自体をキャッシュする方法

Docker 自体にもビルド内容をキャッシュし、同じイメージを再度ビルドする際にはキャッシュを読み込むことでビルドを高速化する機能である Build Cache があります。この時出力される Build Cache 自体を GitHub Action でキャッシュすることで workflow が実行される場合に Build Cache が利用可能になります。

この方法では以下の 2 つの機能を利用します。

  • 指定したディレクトリのキャッシュとリストアをする GitHub アクションである actions/cache
  • GitHub Actions 上で Docker イメージのビルドや Push を行うアクションである docker/build-push-action

具体的な workflow としては、以下の通りです。

  • Image をビルドする際に、キャッシュされた Build Cache を読み込みます。
  • イメージの変更など、ビルド中に生成される Build Cache を出力します。
  • キャッシュされた Build Cache を更新します。

上記を考慮して、docker の ビルドを行う workflow の例を以下に示します。

name: "Sample Workflow"
on: [push]

env:
  # Dockerのbuild cacheを保存するパス
  PATH_CACHE: /tmp/.buildx-cache
  # Dockerのイメージ名
  NAME_IMAGE: myTestImage

jobs:
  Tests:
    runs-on: ubuntu-latest
    steps:
      # build-push-actionに必要なツールやセットアップ
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      # リポジトリの読み込み
      - name: Check out repo under workspace
        uses: actions/checkout@v2

      # キャッシュ ID の作成(イメージのハッシュと日付から作成)
      - name: Create image tag
        id: imagetag
        run: |
          : # 日付とハッシュからID を作成
          VARIANT=$(TZ=UTC-9 date '+%Y%m')-${{ github.sha }}
          : # イメージのタグを作成
          TAG="${NAME_IMAGE}:${VARIANT}"
          : # 変数を他の run でも使えるように output
          echo "::set-output name=TAG::${TAG}"

      # キャッシュを有効化
      - name: Enable cache
        id: cache
        uses: actions/cache@v2
        with:
          path: ${{ env.PATH_CACHE }}
          key: ${{ steps.imagetag.outputs.TAG }}
          restore-keys: ${{ env.NAME_IMAGE }}

      # イメージをビルド
      - name: Build
        id: docker_build
        uses: docker/build-push-action@v2
        with:
          push: false
          load: true # イメージを保存する
          tags: ${{ steps.imagetag.outputs.TAG }}
          cache-from: type=local,src=${{ env.PATH_CACHE }}
          cache-to: type=local,dest=${{ env.PATH_CACHE }}-new,mode=max

      # 本処理
      # イメージからコンテナを起動してテスト(entrypoint.sh)を実行
      - name: Run tests for both Go and Shell Script
        run: |
          : # コンテナ内のスクリプトのパスを作成(マウントポイントから見たパス)
          path_entrypoint=${{ env.PATH_MOUNT }}/path/to/hogefuga.sh
          : # 実行
          docker run \
            -u root \
            -v "$(pwd)":${{ env.PATH_MOUNT }} \
            -w ${{ env.PATH_MOUNT }} \
            docker.io/library/${{ steps.imagetag.outputs.TAG }} \
            "$path_entrypoint"

      # その他の処理(例えばカバレッジのアップデート)
      - name: Upload coverage
        uses: codecov/codecov-action@v1
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

      # キャッシュが蓄積される問題を解消
      # https://github.com/docker/build-push-action/issues/252
      # https://github.com/moby/buildkit/issues/1896
      - name: Move cache
        run: |
          rm -rf ${{ env.PATH_CACHE }}
          mv ${{ env.PATH_CACHE }}-new ${{ env.PATH_CACHE }}

注意点として現時点(2022/07)ではキャッシュの上書き機能が未実装であるため、古いキャッシュも溜まってしまう問題があります。そのため、一時ディレクトリに Build Cache を出力し、キャッシュされた古い Build Cache を削除して移動するような処理を最後に加えています。

Docker のイメージの軽量化

  • ビルドする Docker のイメージのサイズを軽量化することもビルドの高速化につながります。
  • イメージのサイズを軽量化する方法は次の Handbook の記事を参考にしてください。

参考

info
備考

Hakky ではエンジニアを募集中です!まずは話してみたいなどでも構いませんので、ぜひお気軽に採用ページからお問い合わせくださいませ。

2025年06月02日に最終更新
読み込み中...