Github Actions上でキャッシュを効かせてDocker Composeを実行する

内山浩佑

Docker Composeは複数のDockerコンテナを効率的に管理・運用するためのツールです。ローカルでの開発ではもちろん、CI/CD環境でのテスト実行にも利用されます。この記事では、Github Actions上でDocker Composeをキャッシュを効かせて実行する方法について解説します。

1. Github ActionsでのDocker Composeの利点

Docker Composeを使用することで、データベースなどの依存サービスを持つアプリケーションのテストが簡単になります。Github Actionsでもこの利点はそのまま活かせます。

今回は、データベースにMySQLを利用し、Go言語で書かれたプログラムをテストするケースを紹介します。

Goのコードとテストは、DBに接続してpingを送るだけの内容となっています。

package main

import (
        "fmt"
        "os"

        "gorm.io/driver/mysql"
        "gorm.io/gorm"
)

func ping() (bool, error) {
        dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/?charset=utf8mb4&parseTime=true&loc=Local",
                os.Getenv("DB_USER"),
                os.Getenv("DB_PASSWORD"),
                os.Getenv("DB_HOST"),
                os.Getenv("DB_PORT"))
        conn, err := gorm.Open(mysql.Open(dsn))
        if err != nil {
                return false, err
        }

        db, err := conn.DB()
        if err != nil {
                return false, err
        }
        if err := db.Ping(); err != nil {
                return false, err
        }

        return true, nil
}
package main

import "testing"

func TestPing(t *testing.T) {
        _, err := ping()
        if err != nil {
                t.Fatal(err)
        }
}

2. Github ActionsでのDocker Composeの問題点

毎回のビルドでDocker Composeを使用すると、同じ手順でのコンテナのビルドが繰り返されるため、ビルドに多くの時間がかかってしまいます。これは特に大規模なプロジェクトや複数のサービスを持つアプリケーションで顕著です。

そこで、キャッシュ機能を活用し、ビルド時間の短縮を行います。キャッシュを利用したビルドを行うために、Buildxというツールを利用します。

3. Buildxとは?

https://matsuand.github.io/docs.docker.jp.onthefly/buildx/working-with-buildx/

Buildxは、Dockerのプラグインの一つで、キャッシュ機能やマルチプラットフォームのビルドをサポートしています。この機能を利用することで、前回のビルド結果を再利用し、ビルド時間を大幅に短縮することができます。

Docker ComposeとBuildxのビルド

Docker ComposeからBuildxのコマンドを実行できますが、細やかな引数をしていできることができません。そこで、Github Actionsでは、Buildxを直接使用してビルドを行い、作成されたDockerイメージをDocker Composeで使用するように設定します。

4. Github ActionsでのBuildxとキャッシュの設定

では、Github ActionsでBuildxを利用してキャッシュを有効にする方法を解説します。以下は、Github Actionsワークフロー設定となります。

name: Test

on:
  push:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      # 1. Buildxのセットアップ
      - name: Set up Docker Buildx
        id: buildx
        uses: docker/setup-buildx-action@v1

      # 2. キャッシュ設定
      - name: Cache Docker layers - App
        uses: actions/cache@v2
        with:
          path: /tmp/.buildx-cache-app # Buildxのキャッシュを指定
          key: ${{ github.ref }}-${{ github.sha }} # キャッシュのキーを指定
          restore-keys: |
            ${{ github.ref }}
            refs/head/main

      - name: Cache Docker layers - DB
        uses: actions/cache@v2
        with:
          path: /tmp/.buildx-cache-db # Buildxのキャッシュを指定
          key: ${{ github.ref }}-${{ github.sha }} # キャッシュのキーを指定
          restore-keys: |
            ${{ github.ref }}
            refs/head/main

      # 3. Buildxでイメージをビルドする
      - name: Build images - App
        uses: docker/build-push-action@v4
        with:
          push: false
          builder: ${{ steps.buildx.outputs.name }} # Buildxでビルドすることを指定
          tags: testapp-app-cached:latest # イメージ名を指定/docker-compose.ymlで参照する名前
          load: true
          context: .
          file: ./docker/app/Dockerfile
          cache-from: type=local,src=/tmp/.buildx-cache-app # キャッシュを指定
          cache-to: type=local,dest=/tmp/.buildx-cache-new-app # キャッシュを指定

      - name: Build images - DB
        uses: docker/build-push-action@v4
        with:
          push: false
          builder: ${{ steps.buildx.outputs.name }} # Buildxでビルドすることを指定
          tags: testapp-db-cached:latest # イメージ名を指定/docker-compose.ymlで参照する名前
          load: true
          context: ./docker/db
          cache-from: type=local,src=/tmp/.buildx-cache-db # キャッシュを指定
          cache-to: type=local,dest=/tmp/.buildx-cache-new-db # キャッシュを指定

      # 4. docker composeビルド/起動
      - name: docker compose build & up
        run: |
          docker compose -f docker-compose.ci.yml build
          docker compose -f docker-compose.ci.yml up -d

      # 5. テスト実行
      - name: Test
        run: |
          docker compose -f docker-compose.ci.yml run --rm app go test -v ./...

      # 6. 肥大化対策
      # https://github.com/docker/build-push-action/issues/252
      # https://github.com/moby/buildkit/issues/1896
      - name: Update cache
        run: |
          rm -rf /tmp/.buildx-cache-app
          rm -rf /tmp/.buildx-cache-db
          mv /tmp/.buildx-cache-new-app /tmp/.buildx-cache-app
          mv /tmp/.buildx-cache-new-db /tmp/.buildx-cache-db

1. Buildxのセットアップ

こちらでは、 docker/setup-buildx-action@v1 を利用して、Buildxのセットアップを行っています。後ステップで、この結果を参照するため、 id: buildx を設定しています。これにより、 steps.buildx.outputs で、参照することができるようになります。

2. キャッシュ設定

actions/cache@2 を使用して、キャッシュの設定を行っています。アプリコンテナ用とDBコンテナ用の2つを設定しています。 path ではキャッシュを保存するディレクトリを指定してます。 key はキャッシュを特定するキーを指定しています。 restore-keys では、 key の値でキャッシュがヒットしなかった場合に参照する「キャッシュキーのプレフィクス」を指定してます。

ここで、 keyrestore-keys の働きを整理します。 key は、github.ref と github.sha の組み合わせで指定してます。そのため、pushするタイミングで、 key の値が変化し、キャッシュがヒットしません。代わりに restore-keys が参照され、前に作成されたキャッシュが参照されます。この流れで、以前のビルド結果が再利用されることになります。

3. Buildxでイメージをビルドする

docker/build-push-action@v4 を利用して、Buildxを使用したビルドを行っています。

builder では、前述の steps.buildx.outputs.name を指定してます。

tags では、Docker Composeで参照するイメージ名を指定しています。

cache-from cache-to では、キャッシュを指定指定しています。 cache-to では、後述する理由で、キャッシュ名を新しい名前に指定しています。

4. docker composeビルド/起動

docker-compose.ci.yml を以下のように記述しています。

version: '3'

services:
  db:
    image: testapp-db-cached # Buildxのビルド時に指定したイメージ名
    build:
      context: ./docker/db
      dockerfile: Dockerfile
    healthcheck:
      test: mysqladmin ping -h 127.0.0.1 -u$MYSQL_USER -p$MYSQL_PASSWORD

  app:
    image: testapp-app-cached # Buildxのビルド時に指定したイメージ名
    build:
      context: ./
      dockerfile: ./docker/app/Dockerfile
    depends_on:
      db:
        condition: service_healthy
    environment:
      - DB_HOST=db
      - DB_PORT=3306
      - DB_USER=test
      - DB_PASSWORD=test

コメントを付けている部分で、Buildxを使用してビルドしたDockerイメージを指定しています。

5. テスト実行

ビルドが完了している状態なので、ここではGoのテストコマンドを実行しているだけとなります。

6. 肥大化対策

Buildxのビルドに不具合がある関係で、キャッシュの読み書きが同一のディレクトリの場合、肥大化してしまうそうです。そのため、ここでは、新旧のキャッシュを置き換えるようにしています。

キャッシュの確認

上記の設定で、Githubにpushすると、Github Actionsのワークフローが実行されます。

Github上のUIで作成されたキャッシュを確認することができます。

file

Goコンテナ用とMySQLコンテナ用のキャッシュが2つ作成されていることが確認できます。

また、2回目以降のワークフローのログを見ると、Dockerのビルド時に CACHED という表記が確認できます。

file

今回の例では、アプリ自体はシンプルなものにしたので、ビルド時間が大幅に改善するわけではありませんが、実プロジェクトで同じ設定を適用したところ、数分の時間短縮の改善が見られました。

CIではなるべく実行時間を短くしたいので、ぜひキャッシュを設定したいですね。

5. まとめ

Github ActionsでDocker Composeをキャッシュを効かせて使用することで、テストの実行時間を短縮することができます。特に大規模なプロジェクトや多くのサービスを持つアプリケーションでの利用は、非常に効果的です。Buildxを使用することで、さらなるビルドの高速化を実現することができます。

参考

AUTHOR
uchiko
uchiko
記事URLをコピーしました