内容总结自 万字长文:彻底搞懂容器镜像构建 博文。
Docker 整体上是个 C/S 架构,平时使用的 docker 命令只不过是一个 cli 的客户端,而它的服务端是 dockerd 在 Linux 系统中,通常我们是使用 systemd 进行管理,所以我们可以使用 systemctl start docker 来启动服务。
docker-cli -> rest-api -> dockerd
1. 接口:API
API 即 Application Programming Interface,应用程序接口。
Docker 维护团队在每个版本正式发布之后,都会将 API 文档发布出来,可以通过 Docker Engine API 在线浏览,也可以自行构建 API 文档。
$ docker version |grep API
API version: 1.42
API version: 1.42 (minimum version 1.12)
通过 API 我们也知道了该接口所需的请求体是一个 tar 归档文件(可选择压缩算法进行压缩),同时它的请求头中会携带用户在镜像仓库中的认证信息。这提醒我们, 如果在使用远程 Dockerd 构建时,请注意安全,尽量使用 tls 进行加密,以免数据泄漏。
- 请求地址和方法
- 接口地址是
/v1.41/build - 方法是
POST
- 接口地址是
- 请求体
- 请求体是一个
tar归档文件 - 可选择无压缩、
gzip、bzip2、xz压缩等形式
- 请求体是一个
- 请求头
Content-type默认是application/x-tarX-Registry-Config对应auths信息保持一致- 执行
docker login后自动写入$HOME/.docker/config.json文件内 - 拉取镜像的认证信息使用
auths->$HOME/.docker/config.json(base64) - 解密登录信息内容
echo -n "xxx" | base64 -d
- 执行
- 请求参数
- 通过
docker build --help基本都可以看到对应含义
- 通过
# eg:
$ curl -X POST --unix-socket /var/run/docker.sock localhost/v1.41/build
# eg:
$ curl -X POST --unix-socket \
--host /var/run/docker.sock localhost/v1.41/build
2. 客户端:cli
docker客户端是docker生态中最重要的部分,它提供了docker大部分功能。
docker 是我们所使用的客户端工具,用于与 dockerd 进行交互。docker cli 目前有两个构建系统,一个是 v1 版本的 builder 和 v2 版本的 BuildKit。
关于构建相关的部分, 我们所熟知的便是 docker build 或者是 docker image build,在 19.03 中新增的是 docker builder build ,但其实他们都是同一个只是做了个 alias 罢了。
// cmd/docker/docker.go#L237
if v, ok := aliasMap["builder"]; ok {
aliases = append(aliases,
[2][]string{{"build"}, {v, "build"}},
[2][]string{{"image", "build"}, {v, "build"}},
)
}
在 /etc/docker/daemon.json 中添加如下内容,并重启 dockerd 即可,就可以在 dockerd 中开启 buildkit 了。在 docker cli 上也可开启 buildkit 的支持,并且 cli 的配置可覆盖服务端配置。通过 export DOCKER_BUILDKIT=1 即可开启 buildkit 的支持,设置为 0 则关闭。
{
"features": {
"buildkit": true
}
}
从入口函数 runBuild 开始,经过判断是否支持 buildkit ,如果不支持 buildkit 则继续使用 v1 的 builder。接下来读取各类参数,按照不同的参数执行各类不同的处理逻辑。最后当构建结束后,cli 根据参数决定是否要显示构建进度或者结果。
docker cli -> rest api -> 参数校验 -> BuildKitEnabled -> runBuild
-> 排除文件 .dockerignore
-> 组织 build context
- builder v1
大致经历了执行构建阶段:最开始的部分是一些对参数的处理和校验,然后使用 .dockerignore 忽略不需要加入到镜像内的文件和目录,生成真正的 build context 内容用来打包构建。然后 docker cli 还会去读取 ~/.docker/config.json 中的内容,将认证信息通过 X-Registry-Config 头传递给 dockerd 用于在需要拉取镜像时进行身份校验。当一切所需的校验和信息都准备就绪之后,则开始调用 dockerCli.Client 封装的 API 接口,将请求发送至 dockerd,进行实际的构建任务。之后,便是按照传递的参数进行进度的输出或是将镜像 ID 写入到文件之类的。
- 对参数的处理和校验
stream和compress不可同时使用- 不可同时使用
stdin读取Dockerfile和build context build context支持四种行为,包括tar/git/url/stdin
- 使用
.dockerignore忽略不需要的文件 - 读取
auths中的内容进行拉取镜像时的身份校验 - 调用
API进行实际构建任务
# 通过stdin传递Dockerfile的方式 OK
$ cat Dockerfile | DOCKER_BUILDKIT=0 docker build -f - .
# 通过stdin传递build context的方式 OK
$ cat x.tar | DOCKER_BUILDKIT=0 docker build -f Dockerfile -
# 报错 Error
$ DOCKER_BUILDKIT=0 docker build -f - -
- buildkit v2
大致经历了执行构建阶段:从入口函数 runBuild 开始,判断是否支持 buildkit ,如果支持 buildkit 则调用 runBuildBuildKit。与 v1 的 builder 不同的是,开启了 buildkit 后,会首先创建一个长连接的会话并一直保持。其次,与 builder 相同,判断 build context 的来源,格式之类的,校验参数等。当然,buildkit 支持三种不同的输出格式 tar、local 或正常的存储于 Docker 的目录中。另外是在 buildkit 中新增的高阶特性,可以配置 secrets 和 ssh 密钥等功能。最后,再调用 API 与 dockerd 交互完成镜像的构建。
- 创建长连接会话
- 选择输出模式,
local、tar、cacheonly、dockerd - 使用
.dockerignore忽略不需要的文件 - 读取
auths中的内容进行拉取镜像时的身份校验- 高阶特性:
mount secrets和ssh
- 高阶特性:
- 调用
API进行实际构建任务
3. 服务端:dockerd
接收来自客户端的请求,并调用
API进行构建任务。
当 CLI 通过 /build 接口发送请求后,dockerd 将通过 API 提交过来的参数转换为构建动作实际需要的参数形式,并从请求头拿到认证信息,这个时候需要注意了: 真正的构建过程要开始了。
使用 backend 的 Build 函数来完成真正的构建过程,函数看着比较长,但主要功能就以下三点。到这个函数之后,就分别是 builder 与 buildkit 对 Dockerfile 的解析,以及对 build context 的操作了。
NewTagger是用于给镜像打标签,也就是我们的-t参数相关的内容- 通过判断是否使用了
buildkit来调用不同的构建后端 - 处理构建完成后的动作