SODA Engineering Blog
🧑‍🎓

DockerイメージをCIでキャッシュするなら知っておきたいDocker Buildの基礎知識

2024/08/18に公開

TL;DR

CIでビルドキャッシュを利用するにはファイルシステムの適当な箇所にビルドキャッシュを入出力する必要がある。それをするには、Buildx + BuildKit + docker-container driver の3点セットが必要になる。しかし2024年8月現在Docker Compose Buildはこれらを利用していないので、明示的に利用させなければならない。

Docker 23以降のdocker buildの仕組み

Docker Engineは2023年2月にv23を迎えた

ここで、ビルドのアーキテクチャ標準が変更になり、ビルドクライアントのBuildxとビルドバックエンドのBuildKitが標準化された

これはつまり、 docker build コマンドを実行したときデフォルトで上記らが利用されるようになったということだ。Docker 23以前とそのあとで、 docker build で実行されるビルド体系はリプレイスされたのである。

Docker Composeではどうか?

一方、 docker compose build コマンドでビルドする場合には、これらは利用されず、従来形でビルドされる。

ビルドキャッシュの入出力の指定

なぜビルドアーキテクチャを知る必要があるかを理解するために、一旦ビルドキャッシュをファイルシステムに出力する方法について説明する。

ビルドキャッシュの入出力の指定はdocker buildのオプションでできる。

% docker build --help
...
  --cache-from stringArray        External cache sources (e.g., "user/app:cache", "type=local,src=path/to/dir")
  --cache-to stringArray          Cache export destinations (e.g., "user/app:cache", "type=local,dest=path/to/dir")
...

/path/to/dir でキャッシュの入出力を行う場合は下記の通り。

docker build --cache-from=type=local,src=/path/to/dir  --cache-to=type=local,dest=/path/to/dir ...

ローカル環境 & docker buildでやってみる

ということでまずはdocker buildコマンドでビルドしてみる。
しかし筆者の環境で徐に実行したところ以下のエラーが出た。

Cache export is not supported for the docker driver.
Switch to a different driver, or turn on the containerd image store, and try again.
Learn more at https://docs.docker.com/go/build-cache-backends

このオプションは docker driverは利用できないとのこと。よって、別のdriverでビルドする。

driverの詳細な説明は公式サイトに任せるが、cache exportを使うには docker ドライバではなく docker-container ドライバを利用する必要がある。 docker は Docker デーモンに含まれている従来型のドライバで、 docker-container は BuildKitをフルに活用する新しい形のドライバである。

ビルダの一覧を確認してみる。

% docker buildx ls
NAME/NODE           DRIVER/ENDPOINT     STATUS    BUILDKIT   PLATFORMS
default             docker                                   
 \_ default          \_ default         running   v0.15.0    linux/arm64, linux/amd64, linux/amd64/v2, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
desktop-linux*      docker                                   
 \_ desktop-linux    \_ desktop-linux   running   v0.15.0    linux/arm64, linux/amd64, linux/amd64/v2, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6

2つあるビルダはどちらも docker ドライバを利用している。

これでは cache-from cache-to オプションが利用できないので、ビルダを新規に作成する。

$ docker buildx create --name mybuilder --driver docker-container
...

$ docker buildx ls
NAME/NODE           DRIVER/ENDPOINT     STATUS    BUILDKIT   PLATFORMS
mybuilder           docker-container                         
 \_ mybuilder0       \_ desktop-linux   running   v0.15.2    linux/arm64, linux/amd64, linux/amd64/v2, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
default             docker                                   
 \_ default          \_ default         running   v0.15.0    linux/arm64, linux/amd64, linux/amd64/v2, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
desktop-linux*      docker                                   
 \_ desktop-linux    \_ desktop-linux   running   v0.15.0    linux/arm64, linux/amd64, linux/amd64/v2, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6

docker-containerをドライバとして利用するビルダを作成できた。

これで改めてビルドするとキャッシュを出力することができた。

docker buildx build --builder mybuilder --cache-from=type=local,src=/path/to/dir  --cache-to=type=local,dest=/path/to/dir ...

docker compose ではどうか

ここまでで説明している通り、 docker compose build ではbuildx等を使っておらず、ビルダーのドライバも従来型で、そのままではキャッシュのファイル出力ができない。

ここで代わりに登場するのが docker buildx bake コマンドである。

bake は buildx を使ったコンテナオーケストレーション用のコマンドである。

bakeの設定はHCL形式で記述するのだが、docker-compose.ymlとも互換性があり、docker compose buildコマンドを代替することができる。

その際のコマンドは以下のようになる。

docker buildx build \
  --builder mybuilder \
  --cache-from=type=local,src=/path/to/cache \
  --cache-to=type=local,dest=/path/to/cache,mode=max \
  -f docker-compose.yml

GitHub Actions & Docker Composeで利用してみる

以下のような流れとなる。

  1. action/cache で特定のディレクトリをキャッシュするようにする
  2. buildx をセットアップする
  3. docker buildx bake を実行する。その時のキャッシュの入出力先を(1)で指定したディレクトリとする。

これを実現するために、GitHub Actionsでは docker/setup-buildx-action というカスタムアクションが提供されている。これに則ることでビルドキャッシュを利用できる。

また、キャッシュの設定をdocker-compose.ymlに記述する。

docker-compose.yml
services:
  app:
    build:
      context: .
      cache_from:
        - type=local,src=/tmp/.buildx-cache
      cache_to:
        - type=local,dest=/tmp/.buildx-cache
worker.yml
...
      - name: Setup Docker Buildx
        id: buildx
        uses: docker/setup-buildx-action@v2

      - name: Cache Docker layers
        uses: actions/cache@v3
        with:
          path: /tmp/.buildx-cache
          key: docker-build-cache-${{ github.ref }}-${{ github.sha }}
          restore-keys: |
            docker-build-cache-${{ github.ref }}
            docker-build-cache-
      - name: Setup Apps
            run: |
                # docker compose build を以下のコマンドに置換
                docker buildx bake --file docker-compose.yml --builder="${{ steps.buildx.outputs.name }}"
                # 以下、docker compose up などコンテナを起動するコマンドに続く
...

[補足]キャッシュの内容を見てみる

ローカル環境で実行したので、キャッシュの内容を見てみる。

blobsディレクトリにイメージのレイヤーごとのキャッシュが保存されているのがわかる。

index.jsonは以下のようなメタデータが記述されている。

{
    "schemaVersion": 2,
    "mediaType": "application/vnd.oci.image.index.v1+json",
    "manifests": [
        {
            "mediaType": "application/vnd.oci.image.index.v1+json",
            "digest": "sha256:c6fc9d593e92059b058b0b147be8e51...",
            "size": 4193,
            "annotations": {
                "org.opencontainers.image.ref.name": "latest"
            }
        }
    ]
}

[蛇足]おわりに

本記事ではGitHub Actions で docker compose build で生成されるイメージをキャッシュしてみたくなった筆者が、実際にキャッシュできるようになるまでに最低限身に付けた知識である。結論から言うとキャッシュしてもコンテナの起動までの時間が早くなることはなかったのでこのアイデアは不採用に終わったが、またどこかで使うことがあるかもしれないので備忘録として残しておく。

SODA Engineering Blog
SODA Engineering Blog

Discussion