6

Docker BuildKit 介绍

 2 years ago
source link: https://yanhang.me/post/2019-04-08-buildkit/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

对于 Docker 和 Kubernetes 来说,在自身发展的壮大过程中,都会经历一个因为功能不断增加导致的软件结构庞杂的问题。对于 Kubernetes 来说,出于架构上的考量,kubectl 等项目的代码都会逐渐从主项目中移除。对于 Docker 来说,事情更为复杂,它既要考虑开源,又要考虑自己的商业化,所以有了 moby 以及 *kit 等一系列项目。 下图清晰地展示出了 Docker 对于相关项目的一个架构规划:

总体来说,Docker 希望将容器技术与容器产品分离开,核心技术是开源的,可扩展的,这样允许有其他人来基于同样的技术来构建类似于 Docker CE/Docker EE 这样的产品。 BuildKit 自 2017 年年底便已经实现,但似乎关注度不是很高(在国内),在 CI/CD 系统中的应用也不是很广泛。本文是关于 BuildKit 的一个相关介绍。

为什么需要 BuildKit

除了上面说的商业以及架构上的考量,本身在构建这一块,Dockerfile 以及相应的机制也面临着一些问题

  1. 并发程度不够好

    很多步骤都是可以并行执行的,比如两部构建中的两次 pull 镜像,以及各种 run 命令

  2. 对 cache 的支持不好

    像 golang 这样的语言,每次构建都要从头开始,没法复用缓存

  3. 对 secret 的支持不好

    难以支持在 dockerfile 中通过 username-password/ssl key 等方式获取一些加密信息

  4. 可扩展性不够好

这些问题并不是说在现有的代码基础之上实现不了,而是综合来看,工作量比较大,而且到了一种必须审视现有架构的时机了。有了 moby 与 containerd 之后更是如此,这并不是一个孤立的问题,而是每个 docker 子功能(command)都会面临的机遇与挑战。

BuildKit 长什么样子

通常来讲,我们将 Dockerfile 当做一个项目里的一个用于部署的配置文件。但从某种角度来说,Dockerfile 就是一种编程语言,他有自己特定的语法,有编译器(docker),有最终的产出(image)。这样来看,docker build 做的就是编译器的工作。关于编译器的结构与性能,我们已经有了在各个语言方面的实现以及优化经验,完全可以应用于 dockerfile。

从最初的 buildkit 的 proposal来看,也正是采用这样的视角。在架构上像 LLVM 一样,采用模块化的结构,每一个部分都是可以单独扩展的,只需要保正彼此之间的接口兼容性。

BuiltKit 主要分为以下几个部分

  • frontends: build 的描述定义,比如 Dockerfile,面向用户
  • solver/low level builder: 负责语法解析,生成中间结构,cache 管理
  • exporter: 生成物导出,比如现在的 image

有了抽象的 frontends 和 exporter,BuildKit 已经不再单单是针对 dockerfile 的一个优化,而是一个通用的构建系统。像 LLVM 一样,它可以支持多语言: Dockerfile,BuildPack,以及各种可能的自定义语言。在输出端,镜像也不再是唯一的输出格式,还可以是文件,目录,plugin,其他的镜像格式等等。

而最核心的,自然是 solver 部分。类似编译器的最核心部分:将源代码专程成目标结果

LLB 指 low level builder,也即 solver。上层语言转成一些中间指令之后,形成一个DAG,它负责找到一种最有效的方式来执行指令并且管理 cache。 它的主要目标有:

1.能够复用相同的步骤 2.结果相同的 instruction 能够复用结果 3.尽量并行化

不管 Dockerfile 里支持多少指令,frontend 支持多少种语言,对于 LLB 来说,核心的操作都是类似的:在一个 snapshot 中做一些操作,记录结果。其伪代码定义大概如下所示:

type Input struct {
  Base Op
  Index int
}

type Op struct {
  Deps []Input
  Outs []snapshot.Snapshot
}

type ExecOp struct {
  Op
  Meta ExecMeta
  Mounts []Mount // mapping for inputs to paths
}

type CopyOp struct {
  Op
  Sources []string
  Dest string
}

type SourceOp struct {
  Op
  Identifier string
}

type ExecMeta struct {
  Args []string
  Env  []string
  User string
  Wd   string
  Tty  bool
  // DisableNetworking bool
}

ExecMeta部分定义了构建过程中所需要的尽量少的环境信息。SourceOp定义了构建开始时的初始数据,在 dockerfile 中就是我们常见的 FROM 语句,当然 BuildKit 并不限于此, 而是也可以使用其他的 git 仓库,本地文件/目录等数据。

有用的新功能

启用了 BuildKit 之后,除了性能上的改善,我们现在就可以在 Dockerfile 中体验到一些非常有用的新功能。比如 mount 操作,其语法格式如下所示:

RUN --mount=type=<bind|cache|secret|tempfs|ssh>

这几种类型就解决了开始所说的一些问题

  • cache: 各语言 package managers 所使用的 cache
  • secret: 用于一些需要登录才能获取的资源,在最终的生成物中也不会保留这些敏感信息
  • ssh: 直接使用本地的 ssh-agent 去做登陆认证。

Links


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK