🤖

🤖

:gijutsu_burogu:

GitHub ActionsでのDockerビルドをキャッシュで高速化する

はじめに

GitHub Actions 上での Docker イメージのビルド高速化について情報が少なかったので自分なりに調査してみました。 Jenkins のように自分でホストするのではなく、GitHub Actions や CircleCI ではステートレスにジョブが行われます。 そのため、ローカルにキャッシュが残らないため、外部にキャッシュを持つようにします。

試す選択肢

1.普通のdocker build🐭

キャッシュが効かない普通のdocker build .です。 GitHub Actionsはステートレスなのでこのままではレイヤーキャッシュは効きません。

2.普通のdocker build + BuildKit🐶

🐭と同様ですが、BuildKitを効かせます。 環境変数にDOCKER_BUILDKIT=1をセットしdocker build .するだけです。

3.actions/cache + docker save & load 🦊

GitHub Actionsでは、actions/cacheを使うことでキャッシュできます。 また、docker save とdocker loadにより、イメージをtar形式のファイルにセーブしたりロードできます。

docs.docker.jp

4.外部レジストリのキャッシュdocker build --cache-from🐷

これは外部キャッシュです。 今回はDocker Hubですが、AWSのECRへも同様にキャッシュできます。 これによりレイヤーキャッシュが効きます。 --build-arg BUILDKIT_INLINE_CACHE=1のオプションも必須です。

docs.docker.com

試してみた

以下のDockerfileで今回は試してみました。 マルチステージビルドではありません。 もっと、重くビルド時間がかかるようなものでやると違う結果が得られると思います。

# Dockerfile
FROM ruby:2.6.3

ENV APP_ROOT /app

WORKDIR $APP_ROOT

# mysqlのクライアントをインストール
RUN apt-get update && apt-get install -y \
    default-mysql-client \
    --no-install-recommends && \
    rm -rf /var/lib/apt/lists/*

ADD Gemfile $APP_ROOT
ADD Gemfile.lock $APP_ROOT

RUN gem install bundler:2.0.2 && \
    bundle install && \
    rm -rf ~/.gem

ADD . $APP_ROOT

EXPOSE  4567
CMD ["bundle", "exec", "ruby", "app.rb", "-o", "0.0.0.0"]

github.com

1.普通のdocker build🐭

build_no_cache:
  runs-on: ubuntu-18.04
  steps:
    - uses: actions/checkout@master
    - name: Build
      run: docker build .

53 秒程度でした。 https://github.com/kotaroooo0/githubactionsbuild/runs/846073213?check_suite_focus=true

2.普通のdocker build + BuildKit🐶

build_no_cache_buildkit:
  runs-on: ubuntu-18.04
  steps:
    - uses: actions/checkout@master
    - name: Build
      env:
        DOCKER_BUILDKIT: 1
      run: docker build .

45 秒程度でした。 https://github.com/kotaroooo0/githubactionsbuild/runs/846073213?check_suite_focus=true

3.actions/cache + docker save & load 🦊

build_with_docker_save_load:
  runs-on: ubuntu-18.04
  steps:
    - uses: actions/checkout@master
    - id: cache-docker
      uses: actions/cache@v1
      with:
        path: /tmp/docker-save
        key: docker-save-${{ hashFiles('Dockerfile') }}
    - run: docker load -i /tmp/docker-save/snapshot.tar || true
      if: steps.cache-docker.outputs.cache-hit == 'true'
    - run: docker build . -t thing --cache-from=thing-cache
    - run: docker tag thing thing-cache && mkdir -p /tmp/docker-save && docker save thing-cache -o /tmp/docker-save/snapshot.tar && ls -lh /tmp/docker-save || true
      if: always() && steps.cache-docker.outputs.cache-hit != 'true'

キャッシュがヒットしない 1 回目は 53 秒でした。 しかし、別途docker saveに 1 分程度かかりました。

キャッシュがヒットする 2 回目は 19 秒でした。 しかし、別途docker loadや cache の復元に 1 分程度かかりました。 https://github.com/kotaroooo0/githubactionsbuild/runs/846270916?check_suite_focus=true

4.外部レジストリのキャッシュdocker build --cache-from🐷

cache-from-with-build-arg:
  runs-on: ubuntu-18.04
  steps:
    - uses: actions/checkout@master
    - name: Build and Push
      env:
        DOCKER_BUILDKIT: 1
        DOCKERHUB_USER: ${{ secrets.DOCKER_USERNAME }}
        DOCKERHUB_PASS: ${{ secrets.DOCKER_PASSWORD }}
      run: |
        docker login -u $DOCKERHUB_USER -p $DOCKERHUB_PASS
        docker build -t adachikun/githubactionsbuild:latest --cache-from=adachikun/githubactionsbuild:latest --build-arg BUILDKIT_INLINE_CACHE=1 .
        docker push adachikun/githubactionsbuild:latest

キャッシュがヒットしない 1 回目は 50 秒程度でした。 別途docker pushに 10 秒程度かかりました。 https://github.com/kotaroooo0/githubactionsbuild/runs/854242620?check_suite_focus=true

キャッシュがヒットする 2 回目は 30 秒程度でした。 https://github.com/kotaroooo0/githubactionsbuild/runs/854249995?check_suite_focus=true

プラグイン編

GitHubでDocker公式のビルド用プラグインを見つけたので使ってみました。

github.com

Buildkit が使えたり、build_argsやcache_fromsのオプションが設定できるため同じことができるのではと思い試してみました。 pushオプションもあり、デフォルトではtrueになっています。

build-push-action-cache:
  runs-on: ubuntu-18.04
  steps:
    - uses: actions/checkout@master
    - name: Build and Push
      uses: docker/build-push-action@v1
      env:
        DOCKER_BUILDKIT: 1
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
        repository: adachikun/githubactionsbuild
        tags: latest
        cache_froms: adachikun/githubactionsbuild:latest
        build_args: BUILDKIT_INLINE_CACHE=1

自分で手書きで書いて外部キャッシュを利用する🐷と同様の結果でした。 1回目は50秒程度でした。https://github.com/kotaroooo0/githubactionsbuild/runs/854302398?check_suite_focus=true 2回目は30秒程度でした。https://github.com/kotaroooo0/githubactionsbuild/runs/854310734?check_suite_focus=true

まとめ

actions/cache + docker save & load 🦊、または外部レジストリのキャッシュdocker build --cache-from🐷が高速でした。 🦊はビルド単体の速度だけで見れば最速でしたが、docker saveとdocker loadによるオーバーヘッドが大きかったです。 🐷の方が無難に高速そうですが、これは場合によって使い分けるのが適切だと思います。 もしかしたら、普通にBuildKitでビルドするのが高速な場合もあるかもしれませんし、計測するのが大事だと思います。 マルチステージビルド編は次回やろうと思います。

過去に書いたDockerビルド関係の記事

Go製CLIツールを使うDockerイメージをダイエットしてみた - 🤖

DockerイメージのビルドをBuildKitで並列実行し高速化する - 🤖

Dockerイメージのビルドで使うキャッシュの種類(レイヤーキャッシュ, BuildKit, CI) - 🤖

Dockerfileを正しく書けるように指摘してくれる静的解析ツール「hadolint」 - 🤖

参考

dev.to

docs.github.com