GitHub Actionsのキャッシュを活用してGoのビルド時間を短縮してみる
目次
こんにちは、クラウドビルダーズのきむです。
GitHub Actionsを使用してGoのDockerイメージをビルドしているのですが、毎回それなりの時間がかかってしまうのが悩みでした。
GitHub Actionsではキャッシュ機能が存在し、うまく活用することでビルドの時間を短縮することができます。
今回はキャッシュを使用することでどれくらいの時間が短縮できるか確認していきます。
キャッシュなし
まずはキャッシュを使用しない場合の実行時間を見ていきます。
定義ファイル
下記が今回使用するDockerfileとワークフローの定義です。
FROM golang:1.22.5 AS builder
WORKDIR /work
COPY server/go.mod server/go.sum ./
RUN go mod download
COPY server/ ./
RUN go build -ldflags="-s -w" -o main .
FROM alpine:3.19
RUN apk add --no-cache tzdata
WORKDIR /app
COPY --from=builder /work/main .
EXPOSE 8080
CMD ["./main"]
name: No Cache Build
on:
push:
branches: [main]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata for Go Image
id: meta
uses: docker/metadata-action@v5
with:
images: go-image
tags: value=${{ github.sha }}
- name: Build Go Image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/arm64
push: false
provenance: false
tags: '${{ steps.meta.outputs.tags }}'
file: docker/go/Dockerfile
Dockerfileではマルチビルドステージを使用して最終的なイメージの軽量化を行い、Dockerイメージのビルドにはbuild-push-actionを使用しています。
1回目
1回目の実行結果は下記のとおりとなりました。
イメージのビルドで12分半ほど時間がかかっており、そのうち9割以上がgo buildにかかっている時間です。
2回目
2回目の実行結果は下記のとおりです。
当然キャッシュを使用していないので1回目と同等の実行時間となりました。
キャッシュあり(Dockerレイヤー)
build-push-actionではcache-toオプションを使用することでDockerレイヤーのキャッシュをエクスポートすることができます。キャッシュを使用する場合はcache-fromオプションを使用します。
定義ファイル
Dockerfileの内容に変更はありません。
下記がキャッシュオプションを追加したワークフローの定義です。
name: Cache Docker Layer
on:
push:
branches: [main]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata for Go Image
id: meta
uses: docker/metadata-action@v5
with:
images: go-image
tags: value=${{ github.sha }}
- name: Build Go Image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/arm64
push: false
provenance: false
tags: '${{ steps.meta.outputs.tags }}'
file: docker/go/Dockerfile
cache-from: type=gha,scope=go-build
cache-to: type=gha,scope=go-build,mode=max
1回目
1回目の実行結果は下記のとおりです。
1回目の実行ではキャッシュが存在しないのでビルド時間に大きな変更はありません。
キャッシュをエクスポートする処理が追加されているので20秒ほど実行時間が増加しています。
2回目
ソースコードの変更は行わずに実行した結果が下記の通りとなります。
Dockerレイヤーのキャッシュが効いて大幅に時間が短縮されました。
3回目
3回目の実行ではソースコードの変更を行なってからワークフローを実行します。
後続の処理ではキャッシュが効いていますが、ソースコードを変更してしまうとgo buildでのキャッシュが効かなくなるので通常の実行時間となります。
キャッシュあり(Dockerレイヤー & ビルドキャッシュ)
ソースコードの変更を行った場合でもビルド時間を短縮させるためにGoのビルドキャッシュを使用します。
Goのビルドキャッシュについては公式ドキュメントを参照してみてください。
定義ファイル
下記がGoのビルドキャッシュを使用するための変更を追加したDockerfileとワークフローの定義です。
FROM golang:1.22.5 AS builder
WORKDIR /work
COPY server/go.mod server/go.sum ./
RUN go mod download
COPY server/ ./
RUN --mount=type=cache,target=/root/.cache/go-build \
go build -ldflags="-s -w" -o main .
FROM alpine:3.19
RUN apk add --no-cache tzdata
WORKDIR /app
COPY --from=builder /work/main .
EXPOSE 8080
CMD ["./main"]
name: Cache Docker Layer and Go Build Cache
on:
push:
branches: [main]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Go Build
uses: actions/cache@v4
id: go-build-cache
with:
path: go-build-cache
key: ${{ runner.os }}-go-build-cache-${{ hashFiles('docker/go/Dockerfile') }}
- name: Inject BuildKit Cache
uses: reproducible-containers/buildkit-cache-dance@v3
with:
cache-map: |
{
"go-build-cache": "/root/.cache/go-build"
}
skip-extraction: ${{ steps.go-build-cache.outputs.cache-hit }}
- name: Extract metadata for Go Image
id: meta
uses: docker/metadata-action@v5
with:
images: go-image
tags: value=${{ github.sha }}
- name: Build Go Image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/arm64
push: false
provenance: false
tags: '${{ steps.meta.outputs.tags }}'
file: docker/go-build-cache/Dockerfile
cache-from: type=gha,scope=go-build
cache-to: type=gha,scope=go-build,mode=max
Go のビルドキャッシュは/root/.cache/go-buildに保存されます。
buildkit-cache-danceを使用することで/root/.cache/go-buildに保存されているキャッシュのエクスポートと使用が可能になります。
Dockerfileでは–mount=type=cache,target=/root/.cache/go-buildを使用することでGoのビルドキャッシュディレクトリをマウントしています。
1回目
1回目の実行結果です。
2回目
ソースコードの変更は行わずにもう一度ワークフローを実行します。
キャッシュあり(Dockerレイヤー)の場合と同じくキャッシュが効いて時間が短縮されています。
3回目
ソースコードの変更を行なってワークフローを実行します。
go build時にDockerレイヤーのキャッシュは効いていませんが、ビルドキャッシュが効いているので1回目のビルドと比較して大幅に実行時間を短縮することができました。
キャッシュ利用時の注意
GitHub Actionsのキャッシュ機能には下記の制限・制約が存在するので注意が必要です。
- キャッシュはブランチごとに保存される
- リポジトリ内のすべてのキャッシュの合計サイズは10GB
- 7日間以上アクセスされていないキャッシュは削除される
上記を意識して適切にキャッシュを管理しましょう。
さいごに
GitHub Actionsのキャッシュを活用することでワークフローの実行速度を大幅に改善することができました。
特にGoのビルドにおいてはbuildkit-cache-danceを使用することでビルドを効率化できるので同じ様な悩みを持っている方はぜひ活用してみてください。