Docker 构建系统中,默认情况下为了加快构建的速度,会将构建过程中的每层都进行缓存,我们建议在编写 Dockerfile 的时候,将更新最为频繁的步骤写到最后面,以避免因为该步骤的内容变更,进而导致后续步骤的缓存失效(缓存的控制是 Docker 固定的行为,我们在之后的 Chat 中会进一步深入内部进行分析)。
而同时,我们通过深入到 Docker 镜像内部,发现了它内部的组织形式,对于镜像而言,它其实是使用配置元信息,将对应内容的层(layer)组织起来的一个集合。
那么在使用 Dockerfile 构建镜像的时候,除了上次 Chat 聊到的内容外,有哪些值得掌握的高级技巧呢? 我们来正式开始本次 Chat 。
打开 BuildKit 支持
在上次 Chat 的最后,我们提到可以通过 BuildKit 以提高构建效率,这里我们来对它进行更加详细的解读和分析。
首先,我们知道 Docker 是一个典型的 C/S 架构模型,我们平时使用的 docker
命令,是它的 CLI 客户端,而它的服务端是 dockerd ,在 Linux 系统中,通常它是由 systemd 进行管理的,我们可以通过 systemctl status docker
查看当前 dockerd 的运行状态。
对于构建镜像而言,它同样是需要将待构建的内容(我们称之为 context),发送给 dockerd,并由 dockerd 的特定模块最终完成构建。
builder
这里我们需要引入一个概念 builder .
builder 就是上面提到的特定模块,也就是说构建内容 context 是由 Docker CLI 发送给 dockerd;并最终由 builder 完成构建。
在 docker
的顶级命令中,我们可以看到有一个 builder
的命令组。它有一个子命令 prune
用于清理所有构建过程中的缓存。
以下是 Docker 18.09 的输出信息。
/ # docker builder
Usage: docker builder COMMAND
Manage builds
Commands:
prune Remove build cache
Run 'docker builder COMMAND --help' for more information on a command.
而在 Docker 19.03 中,它新增了一个子命令:
/ # docker builder
Usage: docker builder COMMAND
Manage builds
Commands:
build Build an image from a Dockerfile
prune Remove build cache
Run 'docker builder COMMAND --help' for more information on a command.
这里新增的这个 build
子命令,其实就是我们平时使用的 docker build
或者是 docker image build
,现在将它放到 builder 的子目录下也是为了凸显 builder 的概念。
builder 其实很早就存在于 Docker 当中了,我们之前在使用或者说默认在使用的就是 builder 的 v1 版本(在 Docker 内部也将它的版本号定为 1),但是由于它太久了,有一些功能缺失和不足,由此诞生了 builder 的 v2 版本,该项目被称之为 BuildKit 。
BuildKit
BuildKit 的产生主要是由于 v1 版本的 builder 的性能,存储管理和扩展性方面都有不足(毕竟它已经产生了很久,而且近些年 Docker 火热,问题也就逐步暴露出来了), 所以它的重点也在于解决这些问题,关键的功能列在下面:
- 支持自动化的垃圾回收
- 可扩展的构建格式
- 并发依赖解决
- 高效的缓存系统
- 插件化的架构
这些功能我们暂且略过,先回到我们的主线上来。
BuildKit 在 Docker v18.06 版本之后可通过 export DOCKER_BUILDKIT=1
环境变量来设置是否启用。对于 Docker v18.06 需要将 dockerd 也以实现性模式运行。即,修改 /etc/docker/daemon.json 文件,增加 "experimental": true
配置,然后使用 systemctl restart docker
重启 dockerd 。
如果将 /etc/docker/daemon.json 文件中添加以下配置:
{
"experimental": true,
"features": {
"buildkit": true
}
}
则会默认使用 BuildKit 进行构建,就不再需要指定环境了。
小结
- 在上面的内容中,我们知道了 Docker 是 C/S 架构,而我们通常使用的
docker
命令便是它的 CLI 客户端,服务端是 dockerd 通常由 systemd 进行管理; - 我们介绍了一个概念 builder,它是 Docker 构建系统中的实际执行者;用于将构建的上下文 context 按照 Dockerfile 的描述最终生成 Docker 镜像(image);
- BuildKit 是 v2 版本的 builder ;
- 我们可以通过增加
export DOCKER_BUILDKIT=1
的环境变量,或是修改 dockerd 的配置文件来临时启用或者默认启用 BuildKit 作为 builder。
我们来体验一下开启 BuildKit 的镜像构建:
(MoeLove) ➜ ~ docker build -t local/spring-boot:buildkit https://github.com/tao12345666333/spring-boot-hello-world.git
[+] Building 0.2s (0/1)
[+] Building 0.6s (0/1)
...
[+] Building 6.4s (0/1)
=> [internal] load git source https://github.com/tao12345666333/spring-boot-hello-world.git 6.4s
=> => # 已初始化空的 Git 仓库于 /var/lib/docker/overlay2/xieo69jwu3qd18uqmuwa6er9l/diff/
898cc478c6bbec5dab019a36fdfdd2dd172cee9erefs/heads/master
[+] Building 394.0s (12/12) FINISHED
=> [internal] load git source https://github.com/tao12345666333/spring-boot-hello-world.git 6.4s
=> [internal] load metadata for docker.io/library/openjdk:8-jre-alpine 3.6s
=> [internal] load metadata for docker.io/library/maven:3.6.1-jdk-8-alpine 3.3s
=> CACHED [stage-2 1/2] FROM docker.io/library/openjdk:8-jre-alpine@sha256:f362b165b870ef129cbe730f29065f 0.0s
=> => resolve docker.io/library/openjdk:8-jre-alpine@sha256:f362b165b870ef129cbe730f29065ff37399c0aa8bcab 0.0s
=> [builder 1/6] FROM docker.io/library/maven:3.6.1-jdk-8-alpine@sha256:16691dc7e18e5311ee7ae38b40dcf98e 14.3s
=> => resolve docker.io/library/maven:3.6.1-jdk-8-alpine@sha256:16691dc7e18e5311ee7ae38b40dcf98ee1cfe4a48 0.0s
=> => sha256:e4ef40f7698347c89ee64b2e5c237d214cae777f33735c52039824eb44feb796 2.18MB / 2.18MB 2.7s
...
=> => extracting sha256:c2274a1a0e2786ee9101b08f76111f9ab8019e368dce1e325d3c284a0ca33397 0.7s
=> [builder 2/6] WORKDIR /app 0.3s
=> [builder 3/6] COPY pom.xml /app/ 0.0s
=> [builder 4/6] RUN mvn dependency:go-offline 352.4s
=> [builder 5/6] COPY src /app/src 0.1s
=> [builder 6/6] RUN mvn -e -B package 16.3s