🐋

GitHub Actions でキャッシュを利用して Docker イメージを高速にビルドする方法

2021/08/10に公開

今や本番環境に Docker を利用することは当然であり、それに伴い CI/CD パイプラインの中で Docker イメージをビルドするという機会も増えてきたのではないでしょうか🧐
皆さんご存知の通り、 Docker イメージはレイヤー構造になっており、レイヤー単位でキャッシュが行なわれる仕組みとなっております。

変更の無いレイヤーの場合は、以前のキャッシュが利用されることによってイメージのビルドが高速になるわけですが、 GitHub Actions などの CI/CD 環境では VM 上でワークフローが走るためそのままではキャッシュを利用することはできません。
全く変更の無いイメージであったとしてもキャッシュが利用されることはなく、最初から全てビルドされていきます😭
これは継続的デリバリーの観点から問題であり、迅速な開発サイクルに影響を与えてしまいます。。。

そこで今回は皆大好き GitHub Actions を例にして CI/CD 環境 でも Docker レイヤーキャッシュを利用する方法についてご紹介します😊

やる

GitHub Actions では Actions を利用することによって予め作成されたパッケージをワークフローに簡単に取り込めることが大きな利点ですが、 Docker 公式が提供している Action の中に今回の要件にそのものズバリなものがあります。
https://github.com/docker/build-push-action

Docker イメージをビルドして、そのまま push することまで可能にするアクションです!
GitHub Actions はデフォルトで docker cli が利用可能なので、ワークフローの中で

$ docker build ... 
$ docker push ... 

とやっても同じことはできます。
しかし、詳しくは Action の公式ドキュメントを見て頂きたいのですが、便利な機能が沢山あることからこの Action を利用することをおすすめします🙌

そしてこの公式ドキュメントの中にしっかり cache の項目があるのでそちらを参照します。
https://github.com/docker/build-push-action/blob/master/docs/advanced/cache.md
「どこにキャッシュしたデータを保存するか?」という観点があるのでやり方は複数あります。

どれでも良いのですが、私は
https://github.com/docker/build-push-action/blob/master/docs/advanced/cache.md#local-cache
の方法を選びました。

この方法では、
https://github.com/actions/cache
という Docker に限らず GitHub Actions でキャッシュを利用する際のデファクトスタンダードになっている公式アクションを利用します。

name: ci

on:
  push:
    branches:
      - 'master'

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      -
        name: Checkout
        uses: actions/checkout@v2
      -
        name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1
      -
        name: Cache Docker layers
        uses: actions/cache@v2
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-buildx-
      -
        name: Login to DockerHub
        uses: docker/login-action@v1 
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      -
        name: Build and push
        uses: docker/build-push-action@v2
        with:
          context: .
          push: true
          tags: user/app:latest
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
      -
        # Temp fix
        # https://github.com/docker/build-push-action/issues/252
        # https://github.com/moby/buildkit/issues/1896
        name: Move cache
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

公式はこんな感じで書いてありまして、これを要件に合わせて少し書き換えるだけでOKです!
非常に簡単。

ただ公式のサンプルだけ記述してもつまらないのでもう少しだけ😅
私の場合だと

  • GCP の Artifact Registry をイメージレジストリとして利用している
  • stg, prod 2つの環境に同じイメージを push したい

という要件だったのでこんな感じになりました。

  build:
    name: container image build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@master
        with:
          service_account_key: ${{ secrets.GCP_SA_KEY }}
          export_default_credentials: true
      - name: authenticate registry
        run: gcloud auth configure-docker $GCP_REGION-docker.pkg.dev
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1
      - name: Cache Docker layers
        uses: actions/cache@v2
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-buildx-
      - name: Build and push
        uses: docker/build-push-action@v2
        with:
          push: true
          tags: |
            ${{ env.GCP_REGION }}-docker.pkg.dev/${{ env.GCP_PROJECT_ID }}/stg-${{ env.APP_NAME }}-main/golang-api:${{ github.sha }}
            ${{ env.GCP_REGION }}-docker.pkg.dev/${{ env.GCP_PROJECT_ID }}/prod-${{ env.APP_NAME }}-main/golang-api:${{ github.sha }}
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
      - # Temp fix
        # https://github.com/docker/build-push-action/issues/252
        # https://github.com/moby/buildkit/issues/1896
        name: Move cache
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

これでキャッシュを効かせた Docker イメージのビルド + push までしてくれるようになりました。
このように認証さえ行えば、 DockerHub 以外のイメージレジストリに push することも可能です。
詳しくは こちら の 公式ドキュメントをご参照ください。

結果としては、以前
https://zenn.dev/tatsurom/articles/golang-docker-environment
の記事で書いたような Golang の Docker イメージで顕著に結果が出ました。

比較的軽量なイメージでしたが以前は毎回 2分半 ほどかかっていたのがこれを利用してからは 1分 程度になりました😋


どなたかの参考になれば幸いです🙌

Discussion