彻底搞懂容器镜像构建


内容总结自 万字长文:彻底搞懂容器镜像构建 博文。

Docker 整体上是个 C/S 架构,平时使用的 docker 命令只不过是一个 cli 的客户端,而它的服务端是  dockerd  在 Linux 系统中,通常我们是使用  systemd  进行管理,所以我们可以使用  systemctl start docker  来启动服务。

docker-cli -> rest-api -> dockerd

1. 接口:API

APIApplication 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  归档文件
    • 可选择无压缩、gzipbzip2xz  压缩等形式
  • 请求头
    • Content-type  默认是  application/x-tar
    • X-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  支持三种不同的输出格式  tarlocal  或正常的存储于 Docker 的目录中。另外是在  buildkit  中新增的高阶特性,可以配置  secrets  和  ssh  密钥等功能。最后,再调用 API 与  dockerd  交互完成镜像的构建。

  • 创建长连接会话
  • 选择输出模式,localtarcacheonlydockerd
  • 使用  .dockerignore  忽略不需要的文件
  • 读取  auths  中的内容进行拉取镜像时的身份校验
    • 高阶特性:mount secretsssh
  • 调用 API 进行实际构建任务

3. 服务端:dockerd

接收来自客户端的请求,并调用 API 进行构建任务。

CLI 通过  /build  接口发送请求后,dockerd 将通过 API 提交过来的参数转换为构建动作实际需要的参数形式,并从请求头拿到认证信息,这个时候需要注意了: 真正的构建过程要开始了。

使用 backend 的  Build  函数来完成真正的构建过程,函数看着比较长,但主要功能就以下三点。到这个函数之后,就分别是  builder  与  buildkit  对  Dockerfile  的解析,以及对  build context  的操作了。

  • NewTagger  是用于给镜像打标签,也就是我们的  -t  参数相关的内容
  • 通过判断是否使用了  buildkit  来调用不同的构建后端
  • 处理构建完成后的动作

文章作者: Escape
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Escape !
  目录