Docker BuildKitを使う
はじめに
Dockerの 18.09 以降にはBuildKitという機能が存在しますが、今までなんとなく使ってただけなのでちゃんとまとめてある程度理解しておきたいなと
Docker BuildKitとは
そもそもBuildKitそのものはDocker Engineとは別でMoby Projectで開発されていたものです。 キャッシュや処理の並列化、アーキテクチャの見直しによってソースコードをビルドアーティファクトへ変換する処理をより効率よく行なうことができるようになったり、Dockerfileの拡張文法を提供していたりします。Dockerの 18.09 以降はDocker Engineに拡張として取り込まれ、特に特別なインストールなどはなしに利用することができます。
何ができるん?
つまるところ、BuidKitはどういうことを可能にしてくれるのかということを以下にまとめます。
- ビルドの高速化される
- ビルドの標準出力に各ステップの実行時間が出力される
- Dockerfileの拡張文法が利用可能になる(後述)
使ってみる
環境
実行環境は以下の通り
$ docker version Client: Docker Engine - Community Version: 19.03.12 API version: 1.40 Go version: go1.13.10 Git commit: 48a66213fe Built: Mon Jun 22 15:45:36 2020 OS/Arch: linux/amd64 Experimental: false Server: Docker Engine - Community Engine: Version: 19.03.12 API version: 1.40 (minimum version 1.12) Go version: go1.13.10 Git commit: 48a66213fe Built: Mon Jun 22 15:44:07 2020 OS/Arch: linux/amd64 Experimental: false containerd: Version: 1.2.13 GitCommit: 7ad184331fa3e55e52b890ea95e65ba581ae3429 runc: Version: 1.0.0-rc10 GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd docker-init: Version: 0.18.0 GitCommit: fec3683 $ lsb_release -a LSB Version: core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch Distributor ID: Ubuntu Description: Ubuntu 20.04.1 LTS Release: 20.04 Codename: focal $ uname -srvmpio Linux 5.4.0-53-generic #59-Ubuntu SMP Wed Oct 21 09:38:44 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
Dockerfileを用意しておく
Docker BuildKitの機能を試すためにいかのDockerfileをベースにします。
特に明示してなければ以下のDcokerfileを利用してビルドを行っています。
FROM centos:7 RUN yum install -y java
BuildKit有効化
環境変数での有効化
BuildKitは環境変数にDOCKER_BUILDKIT=1
と設定しておくと有効化されます。例えば単発のビルド時に有効化したい場合は以下のようにします。
$DOCKER_BUILDKIT=1 docker build .
デフォルトでの有効化
デフォルトで有効化するためには/etc/docker/daemon.json
のfeature
の項目をtrue
に設定します。
{ "features": { "buildkit": true } }
今回はデフォルトとの対比を行いたいのでこちらは使わず環境変数の方で指定して、BuildKitを使いたいと思います。
出力結果のが変わる
まずは普通にdocker build
した場合は以下のような出力になります。
$ docker build -t java-centos . Sending build context to Docker daemon 2.048kB Step 1/2 : FROM centos:7 ---> 7e6257c9f8d8 Step 2/2 : RUN yum install -y java ---> Using cache ---> 68dff4f570e9 Successfully built 68dff4f570e9 Successfully tagged java-centos:latest
次にBuildKitを有効化してDockerBuildを実行します。
$ DOCKER_BUILDKIT=1 docker build -t java-centos . [+] Building 0.0s (6/6) FINISHED => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 37B 0.0s => [internal] load metadata for docker.io/library/centos:7 0.0s => [1/2] FROM docker.io/library/centos:7 0.0s => CACHED [2/2] RUN yum install -y java 0.0s => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:fc5361ee029af85ff2d83403afe003214d2dfcb539216b3fcfa1349852ed614a 0.0s => => naming to docker.io/library/java-centos
出力する項目がすこし変わっているのと、各工程にどのくらいの時間がかかったのかの計測結果が出力されるようになります。
また、--progress=plain
を付け加えることで出力形式をプレーン?なものに変えることもできるみたいです。
$ DOCKER_BUILDKIT=1 docker build --progress=plain -t java-centos . #2 [internal] load build definition from Dockerfile #2 transferring dockerfile: 37B done #2 DONE 0.0s #1 [internal] load .dockerignore #1 transferring context: 2B done #1 DONE 0.0s #3 [internal] load metadata for docker.io/library/centos:7 #3 DONE 0.0s #4 [1/2] FROM docker.io/library/centos:7 #4 DONE 0.0s #5 [2/2] RUN yum install -y java #5 CACHED #6 exporting to image #6 exporting layers done #6 writing image sha256:fc5361ee029af85ff2d83403afe003214d2dfcb539216b3fcfa1349852ed614a done #6 naming to docker.io/library/java-centos done #6 DONE 0.0s
(いまのところこいつの利点があんまりわかってないが、余計な=>
がなくなってるだけのかな...?)
BuildKitのフロンドエンドの置き換え
ここで言うフロントエンドとはBuildKitの中で動くビルドディフィニションをLLB(BuildKitが生成する中間バイナリフォーマット)に変換するコンポーネントのことを指します。Docker目線でいえばDockerfileをバイナリに変換するためのコンポーネントのことを指します。
Dcokerfileの先頭行に以下のような形式のコメントを追加することでこのフロントエンドを置き換えることができます。
# syntax = <frontend image>
現状Dockerfileのフロントエンドのイメージは以下の2つが用意されているようです。
後述しますが、フロントエンドを置き換えることによってDockerfile内でエクスペリメンタルな文法を利用することが可能となります。
フロントエンドを置き換えてエクスペリメンタルの機能を利用する(実験機能)
BuildKitを使ったDockerfileのビルドにおいて別フロントエンドを使うことで拡張の機能を利用することができます。
フロントエンドの置き換え
フロントエンドの置き換えに関しては前述していますが、今回使いたい機能を利用するために、Dockerfileの先頭にコメントを追加します。
# syntax=docker/dockerfile:experimental FROM centos:7 RUN yum install -y java
これで拡張文法とその機能を利用することが可能です。
拡張文法
置き換えたフロントエンドで便利そうな機能をいくつかまとめます。
RUN --mount=type=bind
--mount=type=bind
はビルドコンテクスト(Dockerfileが置かれているワーキングディレクトリ)、もしくは、ビルドされるイメージコンテナのディレクトリをバインドすることができます。バインドされるディレクトリはデフォルトはリードオンリーです。
下記のような項目を,
でつないで利用することができます。
項目 | 説明 |
---|---|
target(必須) | Dockerコンテナ内のマウント先のパス |
from | ビルドストレージ、もしくはソースのルート。デフォルトはビルドのコンテクストにバインドされます |
source | from の中のソースパス、デフォルトのソースパスはfrom のルートディレクトリ |
rw,readwrite | マウントされたディレクトリに書き込むことを許可します。ただし、書き込んだデータは後に削除されます |
例えば下記のDockerfileではビルドコンテキストにあるDockerfileの内容をtext.txt
に書き写しています。
# syntax=docker/dockerfile:experimental FROM busybox RUN --mount=type=bind,target=tmp cat tmp/Dockerfile > text.txt
下記のように実行するとビルドコンテキストがDokcerコンテナ内の/tmp
にバインディングされ、Dockerfileの内容がtext.txt
に書き移すことができます。
$ DOCKER_BUILDKIT=1 docker build -t busybox-tmp . [+] Building 2.1s (9/9) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 37B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => resolve image config for docker.io/docker/dockerfile:experimental 1.8s => CACHED docker-image://docker.io/docker/dockerfile:experimental@sha256:de85b2f3a3e8a2f7fe48e8e84a65f6fdd5cd5183afa6412fff9caa6871649c44 0.0s => [internal] load metadata for docker.io/library/busybox:latest 0.0s => [internal] load build context 0.0s => => transferring context: 31B 0.0s => [1/2] FROM docker.io/library/busybox 0.0s => CACHED [2/2] RUN --mount=type=bind,target=tmp cat tmp/Dockerfile > text.txt 0.0s => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:d75bb6670538d7af739b6f201d170a5fa38128e7cbbcf56ef9448682695d0b36 0.0s => => naming to docker.io/library/busybox-tmp $ docker run busybox-tmp cat text.txt # syntax=docker/dockerfile:experimental FROM busybox RUN --mount=type=bind,target=tmp cat tmp/Dockerfile > text.txt
この機能のユースケースとしてはDockerコンテナ内に必要は無いけど実行はしたいスクリプトがあるディレクトリをバインドしたりすることが考えられます。
ちなみにですが、target
にコンテナ内に元来存在しないディレクトリを指定すると勝手にディレクトリを作成しそこに対してビルドコンテキスト(や指定されたビルドストレージ、もしくはイメージのディレクトリ)をバインドしてくれます。
RUN --mount=type=cache
コンパイラーやパッケージマネージャーのキャッシュディレクトリをバインドすることができます。
下記のような項目を,
でつないで利用することができます。
項目 | 説明 |
---|---|
id | キャッシュを識別するためのID |
target(必須) | Dockerコンテナ内のマウント先のパス |
ro,readonly | セットされていればキャッシュがリードオンリーになります |
from | キャッシュマウントを行なうビルドストレージ。デフォルトは空のディレクトリ |
source | マウントを行なうfrom の中のサブパス。デフォルトはfrom のルート |
mode | 新しいキャッシュディレクトリのファイルモード、デフォルトは0755 |
uid | 新しいキャッシュディレクトリのUserID。デフォルトは0 |
gid | 新しいキャッシュディレクトリのグループID。デフォルトは0 |
例えば下記のDockerfileはmavenのm2ディレクトリをキャッシュして、2回目以降は.m2のキャッシュが/root/.m2
にマウントされるようになります。
# syntax = docker/dockerfile:1.1-experimental FROM maven:3.6.2-jdk-8-slim AS build-env ADD . /javasoerceroot WORKDIR /javasorceroot RUN --mount=type=cache,target=/root/.m2 mvn clean package
aptパッケージをキャッシュする場合以下のようにします。
# syntax = docker/dockerfile:experimental FROM ubuntu RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \ apt update && apt-get --no-install-recommends install -y gcc
RUN --mount=type=tmpfs
tmpfsをコンテナへバインドすることができます。
下記のような項目を,
でつないで利用することができます。
項目 | 説明 |
---|---|
target(必須) | Dockerコンテナ内のマウント先のパス |
RUN --mount=type=secret
秘密鍵のファイルようなクレデンシャルファイルに対して、をイメージの中に残すこと無くバインドすることができます。
項目 | 説明 |
---|---|
id | シークレットのID。ターゲットパスのデフォルトベース名として利用されます。 |
target | Dockerコンテナ内のマウント先のパス。デフォルトでは/run/secrets/ + id となります。 |
required | true でセットすると、シークレットが有効で無かった場合に命令がエラーとして取り扱われるようになります。デフォルトはfalse です |
mode | シークレットのファイルモードデフォルトは0400 |
uid | シークレットのユーザID。デフォルトは0 |
gid | シークレットのグループID。デフォルトは0 |
例えばAWSのS3へアクセスしたいようなユースケースでは以下のようにします。
# syntax = docker/dockerfile:experimental FROM python:3 RUN pip install awscli RUN --mount=type=secret,id=aws,target=/root/.aws/credentials aws s3 cp s3://... ...
docker build時に--secret
フラグを用いてバインドするクレデンシャルなファイルを渡します。
$ docker build --secret id=aws,src=$HOME/.aws/credentials .
その他機能はこちらをご覧ください。