🤖

🤖

:gijutsu_burogu:

Dockerイメージのビルドで使うキャッシュの種類 - レイヤーキャッシュ、BuildKitの--mount=type=cache

はじめに

Docker イメージをビルドしていく上で、イメージサイズ軽量化や並列実行はどこの環境でビルドするか気にしないで行うことができました。 しかし、キャッシュの仕組みは環境によっても異なるなど複雑です。

本記事では、キャッシュの種類について紹介します。

レイヤーキャッシュ

Docker イメージは層が重なることによってできています。 RUN hogeの層の上にCOPY fugaの層があり、その上にENTRYPOINT piyoの層があるという風になっています。

イメージのレイヤー構造はdocker history で確認できます。 例えば、マルチステージビルドでビルドしたadachikun/qiitatohatenaは以下のようになります。 さまざまなレイヤーが重なってイメージが構成されていることが分かります。

$ docker history 4a57f4eb178a
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
7f0d81f4de85        7 days ago          ENTRYPOINT ["/bin/sh" "-c" "./setup.sh"]        0B                  buildkit.dockerfile.v0
<missing>           7 days ago          COPY /go/bin/blogsync /bin/blogsync # buildk…   11.1MB              buildkit.dockerfile.v0
<missing>           7 days ago          COPY /go/bin/qiitaexporter /bin/qiitaexporte…   9.48MB              buildkit.dockerfile.v0
<missing>           7 days ago          RUN /bin/sh -c apk --no-cache add libintl &&…   115kB               buildkit.dockerfile.v0
<missing>           7 days ago          COPY config.yaml.tmp /root/.config/blogsync …   119B                buildkit.dockerfile.v0
<missing>           7 days ago          RUN /bin/sh -c mkdir -p ~/.config/blogsync #…   0B                  buildkit.dockerfile.v0
<missing>           7 days ago          RUN /bin/sh -c chmod +x setup.sh # buildkit     248B                buildkit.dockerfile.v0
<missing>           7 days ago          COPY setup.sh /Documents # buildkit             248B                buildkit.dockerfile.v0
<missing>           7 days ago          COPY blogsync.template /Documents # buildkit    214B                buildkit.dockerfile.v0
<missing>           7 days ago          WORKDIR /Documents                              0B                  buildkit.dockerfile.v0
<missing>           8 months ago        /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>           8 months ago        /bin/sh -c #(nop) ADD file:fe1f09249227e2da2…   5.55MB

Dockerfile は命令を上から順に実行していきます。 ここの命令が処理される際に、既存のレイヤーキャッシュが使えるか調べます。 存在する場合は、既に一度作ったレイヤーを作ることはしません。 一度レイヤーキャッシュが効かなくなると、以降の命令では全てのキャッシュが無効になります。

どういう時にキャッシュが効くのか

COPY,ADD以外の命令では、記述が変わらない限りは変更なしと扱われキャッシュが利用されます。 実行結果が毎回等しくないRUNである場合でも、コマンドが等しい場合はキャッシュされたものが利用されます。

COPY,ADDの命令では、対象となる全ファイルのタイムスタンプ以外の要素の変更が検知された場合キャッシュが利用されなくなります。

FROM golang

WORKDIR /go/src/github.com/kotaroooo0/goproject

# ここで COPY . . としてしまうとあらゆるファイルの変更が検知されキャッシュが効きにくくなります
COPY go.mod go.sum .
RUN go mod download
# パッケージ管理ファイルに変更がなければここまではキャッシュされます
# 依存パッケージのダウンロードは時間がかかるここまでキャッシュされると大きな時間短縮になります

COPY . .

RUN CGO_ENABLED=0 go build -o main

BuildKitの--mount=type=cacheによるキャッシュ

RUN --mount=type=cache

コンパイラやパッケージマネージャのキャッシュディレクトリをマウントすることができます。 Dockerfile の先頭に# syntax = docker/dockerfile:experimentalを書く必要があります。

RUN --mount=type=cache 命令を活用すると,従来の docker build より33倍以上速いビルドも可能です。

f:id:kotaroooo0:20200620174323p:plain
https://docs.google.com/presentation/d/1fy_xBVY15fNEOs0DbuY79wKmp_POBBHAeZaGCUNQi-M/edit#slide=id.g3c469c33e2_0_109

以下は Go のコンパイラのキャッシュを利用している例です。

# syntax = docker/dockerfile:experimental
FROM golang as builder

WORKDIR /go/src/github.com/kotaroooo0/snowforecast-twitter-bot/app

COPY go.mod go.sum .
RUN go mod download

COPY . .

RUN --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 go build -o main

前行にRUN echo hogeを追記するなどレイヤーキャッシュが効かないような状況での比較

  • --mount を指定しない BuildKit でのビルド

    • => [builder 7/7] RUN CGO_ENABLED=0 go build -o main 56.3s
  • --mount=type=cache でコンパイラのキャッシュを使うビルド

    • => [builder 7/7] RUN --mount=type=cache,target=/root/.cache/go-build 3.3s

56s から 3s まで縮まっています。

CIでの外部ソースによるキャッシュ

dindなCIではレイヤーキャッシュもコンパイラのキャッシュも通常は利用できません。 そのため、外部ソースを利用してキャッシュを効かせます。

--cache-from

レイヤーキャッシュなどのローカルのビルドキャッシュと異なり、外部キャッシュソースを利用します。 docker build--cache-fromオプションでソースを指定することで、以前のビルドで生成されたキャッシュを再利用することができます。

外部キャッシュを活用すると,従来の docker build より9倍速いビルドも能です。

f:id:kotaroooo0:20200620173900p:plain
https://docs.google.com/presentation/d/1fy_xBVY15fNEOs0DbuY79wKmp_POBBHAeZaGCUNQi-M/edit#slide=id.g3c469c33e2_0_109

コンテナを運用していく場面では、以下の構成のように CI 上でイメージのビルドを行うことが多いと思います。

f:id:kotaroooo0:20200620172849p:plain
コンテナのデプロイフロー

CI 上ではローカルのビルドキャッシュが使いにくいため、--cache-fromなどで外部ソースによるキャッシュを使うことで効率的にイメージを取得することでがきます。

以下の記事でCircleCIでの外部ソースによるキャッシュについて触れられており分かりやすかったです。

qiita.com

参考

Best practices for writing Dockerfiles | Docker Documentation

Docker 18.09 新機能 (イメージビルド&セキュリティ) | by Akihiro Suda | nttlabs | Medium

www.slideshare.net

www.slideshare.net