はじめに
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倍以上速いビルドも可能です。
以下は 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倍速いビルドも能です。
コンテナを運用していく場面では、以下の構成のように CI 上でイメージのビルドを行うことが多いと思います。
CI 上ではローカルのビルドキャッシュが使いにくいため、--cache-from
などで外部ソースによるキャッシュを使うことで効率的にイメージを取得することでがきます。
以下の記事でCircleCIでの外部ソースによるキャッシュについて触れられており分かりやすかったです。
参考
Best practices for writing Dockerfiles | Docker Documentation
Docker 18.09 新機能 (イメージビルド&セキュリティ) | by Akihiro Suda | nttlabs | Medium
www.slideshare.net
www.slideshare.net