はじめに
特に新しい内容はありませんが、今のところの自分の理解を書き残しておきます。
GitHub Actions で、リモートブランチにプッシュされた時にテストを実行するとします。
この時、プッシュのたびにテスト実行のためのビルドが数分かけて行われるのは効率がよくないので、ビルドの時間を短くしたいです。
ここでは、GitHub Actions のキャッシュを使用して、テスト実行の時間短縮を図ります。
GitHub Actions のキャッシュについて
ドキュメントに以下の記載があります。
依存関係キャッシュのリファレンス - Restrictions for accessing a cache
- Workflow runs can restore caches created in either the current branch or the default branch (usually main).
- Workflow runs cannot restore caches created for child branches or sibling branches.
Google翻訳
- ワークフロー実行は、現在のブランチまたはデフォルトブランチ(通常はmain)で作成されたキャッシュを復元できます。
- 子ブランチまたは兄弟ブランチ用に作成されたキャッシュを復元することはできません。
つまり、同じブランチでプッシュを繰り返すときはよいですが、新しいブランチでコミットしてプッシュする、という操作ではキャッシュは働かない、ということになります。
新しいブランチでプッシュした時にもキャッシュが働いてほしいです。
対策
Rust 言語のプロジェクトで開発しているとします。
ビルドの処理を、外部依存クレートのビルドと、プロジェクト自身のビルドに分けて考えます。
外部依存クレートのビルドだけを行った時点の Docker レイヤーをキャッシュに保存して、次のビルドの時間短縮を図ります。
ここで、外部依存関係が頻繁に変わらないのであれば、デフォルトブランチ (ここでは main) でキャッシュを作成しておくと、他のブランチでも時間短縮が期待できるようになります。
外部依存クレートのビルドを行うだけのワークフローを作成することにします。
テストの実行
フォルダー構成
.
├── .github
│ ├── actions
│ │ └── test
│ │ ├── build
│ │ │ └── backend
│ │ │ └── action.yaml
│ │ └── run
│ │ └── action.yaml
│ └── workflows
│ ├── docker-build-test.yaml
│ └── test.yaml
├── Cargo.lock
├── Cargo.toml
├── apps
│ ├── backend
│ │ ├── .env
│ │ ├── Cargo.lock
│ │ ├── Cargo.toml
│ │ ├── Dockerfile
│ │ └── src
│ │ └── main.rs
│ └── db
│ ├── .env
│ └── Dockerfile
└── compose.yaml
| パス | 概要 |
|---|---|
apps/backend/ |
Rust のプロジェクトです。 |
.github/workflows/test.yaml |
プッシュされた時にテストを実行するワークフローです。 |
.github/workflows/docker-build-test.yaml |
外部依存クレートのビルドだけを行うワークフローです。 |
ビルド処理
Docker イメージとそのキャッシュを作成します。
docker buildx コマンドを使用します。compose.yaml に、cache-from、cache-to を記述しており、type=gha で GitHub Actions キャッシュを使用することを指定しています。
cache-from は 2つ指定しています。
scope に ${GITHUB_REF_NAME} を含めている方が、現在のブランチ用のキャッシュの読み取りを試みることを表しています。
キャッシュがなければ、次に main ブランチ (デフォルトブランチ) のキャッシュの読み取りが試みられます。
cache-to は現在のブランチ用のキャッシュを作成する、とだけ記述しています。
main ブランチでビルドを行いキャッシュが作成されると、これは前述のとおり他のブランチからでも読み取ることができるので、新しいブランチ用のキャッシュとなります。
name: Build backend
description: Build backend docker image for test
runs:
using: composite
steps:
- name: Build backend image
shell: bash
run: |
docker buildx bake backend \
-f ./compose.yaml \
--load
env:
BUILD_TARGET: test
services:
backend:
image: myapp:0.1.0
build:
target: ${BUILD_TARGET:-development}
context: apps/backend
dockerfile: Dockerfile
x-bake:
cache-from:
- type=gha,scope=myapp-${BUILD_TARGET:-development}-backend-${GITHUB_REF_NAME}
- type=gha,scope=myapp-${BUILD_TARGET:-development}-backend-main
cache-to: type=gha,scope=myapp-${BUILD_TARGET:-development}-backend-${GITHUB_REF_NAME},mode=max
volumes:
- ./apps/backend:/app
- cargo_target:/home/debian/target
- cargo_registry:/home/debian/.cargo/registry
ports:
- "3000:3000"
env_file:
- ./apps/backend/.env
command: bacon --job run-long --headless
volumes:
cargo_target:
cargo_registry:
Dockerfile はマルチステージビルドの形式にしています。
ステップの設定で環境変数 BUILD_TARGET=test としているので、test ステージのビルドが行われます。
Cargo.toml、Cargo.lock、main.rs の 3つのファイルでビルドを行います。これが、外部依存クレートのみでのビルドを表します。
RUN cargo build の Docker レイヤーのキャッシュが作成されることで、外部依存関係が変わらない間、ビルドが省略されるようになります。
FROM rust:1.91.1-slim-trixie AS base
RUN apt-get update && \
apt-get install -y libssl-dev pkg-config && \
rm -rf /var/lib/apt/lists/*
RUN useradd -m debian
FROM base AS development
RUN cargo install --locked bacon
# (中略)
FROM base AS test
USER debian
RUN mkdir -p /home/debian/target && \
mkdir -p /home/debian/.cargo/registry
ENV CARGO_TARGET_DIR=/home/debian/target
ENV CARGO_HOME=/home/debian/.cargo
WORKDIR /app
COPY Cargo.toml .
COPY Cargo.lock .
RUN mkdir -p src && \
echo "fn main() {}" > src/main.rs
RUN cargo build
ワークフロー(テスト実行)
テストを実行します。
Build backend のステップで、上述のアクション ./.github/actions/test/build/backend を使用しています。
キャッシュがあれば読み取ります。なければ改めてビルドを行います。どちらの場合でも、ここで Docker イメージが作成されます。これは、「外部依存クレートのビルドまでは終わっている状態」のイメージです。
Run test のステップでテストを実行します。Build backend で作成した Docker イメージを使用します。
「外部依存クレートのビルドまでは終わっている状態」のイメージをキャッシュから読み取るようにすることで、テストを開始できるようになるまでの時間を短くする、というのが狙いです。
name: test
run-name: Run test
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: docker-container
- name: Expose GitHub Runtime
uses: crazy-max/ghaction-github-runtime@v3
- name: Build backend
uses: ./.github/actions/test/build/backend
- name: Run test
uses: ./.github/actions/test/run
name: Run tests
description: Run tests using pre-built Docker images
runs:
using: composite
steps:
- name: Run test
shell: bash
run: |
docker compose up -d db
docker compose run --rm backend cargo test -- --test-threads=1
docker compose down
ワークフロー(ビルド)
手動でビルド処理を開始できるようにするためのワークフローです。
main ブランチ (デフォルトブランチ) でのビルド処理でキャッシュを作成することにより、それ以降の新しいブランチでも「外部依存クレートのビルドまでは終わっている状態」のイメージを使えるようにします。
name: docker-build-test
run-name: Build docker images for test
on: [workflow_dispatch]
jobs:
build-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: docker-container
- name: Expose GitHub Runtime
uses: crazy-max/ghaction-github-runtime@v3
- name: Build backend
uses: ./.github/actions/test/build/backend
補足
crazy-max/ghaction-github-runtime
ワークフローに以下のステップを追加しています。
- name: Expose GitHub Runtime
uses: crazy-max/ghaction-github-runtime@v3
これは、GitHub Actions キャッシュに接続できるようにする (認証を通す) 役割をするものです。
GitHub Actions キャッシュへの認証を行う何らかの仕組みを使用しないと、docker buildx コマンドはキャッシュの読み書きを行いません。
docker/build-push-action
この記事では docker buildx コマンドを使用していますが、もっと単純に GitHub Actions キャッシュを利用したければ、docker/build-push-action を使用するのがよいです。
個人的に、Docker に関連する設定を compose.yaml に集めたかったので、docker buildx コマンドを使用しました。
本番用ビルド
目的
テスト実行のためのビルドは cargo build で行いますが、本番デプロイ用のビルドは cargo build --release になります。
テスト用とは別のキャッシュを作成できるようにします。
※実際にデプロイできるかは確認していないので、参考程度ということでお願いします。
フォルダー構成
.
├── .github
│ ├── actions
│ │ ├── production
│ │ │ └── build
│ │ │ └── backend
│ │ │ └── action.yaml
│ │ :
│ └── workflows
│ ├── docker-build-production.yaml
: :
ビルド処理
テスト実行用のアクションとほぼ同じです。
違いは、環境変数 BUILD_TARGET=production としている部分です。
name: Build backend
description: Build backend docker image for production
runs:
using: composite
steps:
- name: Build backend image
shell: bash
run: |
docker buildx bake backend \
-f ./compose.yaml \
--load
env:
BUILD_TARGET: production
ワークフロー(ビルド)
ワークフローは、使用するアクションを本番用にしています。
main ブランチで手動により開始することで、キャッシュを作成します。
name: docker-build-production
run-name: Build docker images for production
on: [workflow_dispatch]
jobs:
build-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: docker-container
- name: Expose GitHub Runtime
uses: crazy-max/ghaction-github-runtime@v3
- name: Build backend
uses: ./.github/actions/production/build/backend
Dockerfile は builder と production のステージを追加しています。
cargo build --release を 2回行います。
1回目は外部依存クレートのみでのビルドです。
2回目はプロジェクト自身のソースコードを含めたビルドです。
1回目の Docker レイヤーがキャッシュに保存されていれば、この部分が短い時間で終わるので、ソースコードの変更分だけのビルド時間になります。
:
FROM base AS builder
USER debian
RUN mkdir -p /home/debian/target && \
mkdir -p /home/debian/.cargo/registry
ENV CARGO_TARGET_DIR=/home/debian/target
ENV CARGO_HOME=/home/debian/.cargo
WORKDIR /app
COPY Cargo.toml .
COPY Cargo.lock .
RUN mkdir -p src && \
echo "fn main() {}" > src/main.rs
RUN cargo build --release
COPY . .
RUN cargo build --release
FROM debian:trixie-slim AS production
COPY --from=builder /home/debian/target/release/backend /app/backend
WORKDIR /app
注意
GitHub Actions のキャッシュは、サイズと期間に制限があります。
依存関係キャッシュのリファレンス - Default limits
- GitHub will remove any cache entries that have not been accessed in over 7 days.
- the total size of all caches in a repository is limited. By default, the limit is 10 GB per repository
Google翻訳
- GitHubは、7日間以上アクセスされていないキャッシュエントリを削除します。
- リポジトリ内のすべてのキャッシュの合計サイズには制限があります。デフォルトでは、リポジトリあたりの制限は10GBです
おわりに
main ブランチで外部依存クレートのみでのビルドを行い、キャッシュを作成することで、新しいブランチでもビルド時間を短縮することができました。
ようやくプロダクトのリポジトリに導入できそうな感じになってきたので、活用していきたいと思います。