buildkit 是一个将 source code 转换为 build artifacts 的开源工具,它于 2017 年由 docker 的创始人 tonistiigi 提议,将 docker build
的能力独立成一个项目,针对 构建 的底层技术进行协作,更好复用和定制构建技术,该将项目即为 buildkit。
简介
tonistiigi 的 proposal 针对 buildkit 提出了这样的架构设想,力图成为高效、描述构建意图强、扩展性强的基础构建工具:
- 构建过程分为 frontend 和 backend 两个环节
- frontend 描述用户的构建定义,如 Dockerfile 等
- backend 致力于找到最有效的方法解决通用的构建操作描述
- 作为 long-running 型服务使用
从 GitHub 上的描述可知,buildkit 具有如下特性:
- Automatic garbage collection
- Concurrent dependency resolution
- Efficient instruction caching
- Build cache import/export
- Extendable frontend formats
- Multiple output formats
- Nested build job invocations
- Distributable workers
- Pluggable architecture
- Execution without root privileges
总结起来,特性如下:
- 灵活的 input / output
- 灵活的 cache (local / remote)
- 并行执行
- 扩展性强
- 安全
通过图示可以直观了解 buildkit 的架构和特性:
常规用法
接下来将会介绍 buildkit 的常规用法,在进行操作前,需要提前准备好如下工具:
本地运行 buildkitd,并配置 buildctl 运行所需的环境变量 (下述以 v0.10.3
版本为例):
$ docker run -d --name buildkitd --privileged moby/buildkit:v0.10.3
$ export BUILDKIT_HOST=docker-container://buildkitd
$ buildctl build --help
构建镜像
基于 Dockerfile 构建镜像,但不导出
buildkit 默认不导出产物,下述操作仅起到构建效果:
$ cat > Dockerfile <<EOF
FROM nginx:1.21.0
RUN sed -i 's#http://deb.debian.org#https://mirrors.cloud.tencent.com#g' /etc/apt/sources.list
RUN apt-get update
RUN apt-get install -y curl
RUN mkdir -p /app/bin/
RUN cd /app/bin && curl -LO https://github.com/fullstorydev/grpcurl/releases/download/v1.8.6/grpcurl_1.8.6_linux_x86_64.tar.gz \
&& tar xvf grpcurl_1.8.6_linux_x86_64.tar.gz
WORKDIR /app/bin/
CMD ["nginx", "-g", "daemon off;"]
EOF
$ buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=.
产出不同的 output
下述操作将基于上述 Dockerfile 进行。
将输出导入到本地目录 ./tmp
$ buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. --output type=local,dest=./tmp
将输出导入到本地 tar 包
$ buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. --output type=tar,dest=out.tar
将输出导出为 docker image
$ buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. --output type=docker,name=myimage | docker load
将输出导出为 oci tar 包
$ buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. --output type=oci,dest=output.tar
cache
buildkit 将构建过程中产生的 layer 作为 cache,使用层面包含 export-cache 和 import-cache 两种操作,即导出 cache 和使用 cache。
对于 export-cache,buildkit 支持如下几种类型:
inline
:将 cache 嵌入到镜像中,并推送镜像到镜像仓库
registry
:将 cache 和镜像分别推送到镜像仓库
local
:将 cache 导出到本地目录
gha
:将 cache 导出到 GitHub Actions 的 cache 服务中
s3
:将 cache 导出到 S3 对象存储中
对于 import-cache,buildkit 支持如下几种类型:
registry
:使用镜像仓库中的 cache,对应上述使用 inline
/ registry
类型导出的 cache
local
:使用本地的 cache,对应上述使用 local
类型导出的 cache
gha
:使用 GitHub Actions 中的 cache,对应上述使用 gha
类型导出的 cache
s3
:使用 S3 中的 cache,对应上述 s3
类型导出的 cache
与此同时,buildkit 支持将不同阶段的 layer 作为 cache:
- 只导出最终镜像的 layers:对应
min
模式
- 导出所有步骤的 layers:对应
max
模式
下述给出常见的几种使用 cache 的操作。
使用 inline 类型导出缓存,使用 registry 类型使用缓存
# 首次执行时仅有 `导出缓存` 的操作,重复执行将会既有 `使用缓存` 又有 `导出缓存` 的操作
$ buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. --output type=image,name=ccr.ccs.tencentyun.com/flyer103/demo-grpcurl:0.1,push=true --export-cache type=inline --import-cache type=registry,ref=ccr.ccs.tencentyun.com/flyer103/demo-grpcurl:0.1
使用 registry 类型导出和使用缓存
# 首次执行时仅有 `导出缓存` 的操作,重复执行将会既有 `使用缓存` 又有 `导出缓存` 的操作
$ buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. --output type=image,name=ccr.ccs.tencentyun.com/flyer103/demo-grpcurl:0.1,push=true --export-cache type=registry,ref=ccr.ccs.tencentyun.com/flyer103/demo-grpcurl:buildcache --import-cache type=registry,ref=ccr.ccs.tencentyun.com/flyer103/demo-grpcurl:buildcache
使用 local 模式导出和使用缓存
# 导出缓存
$ buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. --output type=image,name=ccr.ccs.tencentyun.com/flyer103/demo-grpcurl:0.1,push=true --export-cache type=local,dest=./tmp
# 再次构建使用缓存
$ buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. --output type=image,name=ccr.ccs.tencentyun.com/flyer103/demo-grpcurl:0.1,push=true --import-cache type=local,src=./tmp
查看 buildkitd 中的缓存
$ buildctl du -v
清理 buildkitd 中的缓存
$ buildctl prune
buildkitd 中 cache 管理的策略
可参见 buildkitd.toml 配置中的描述,不赘述。
构建多平台镜像
$ buildctl build --frontend=dockerfile.v0 --opt platform=linux/amd64,linux/arm64 --local context=. --local dockerfile=. --output type=image,name=ccr.ccs.tencentyun.com/flyer103/demo-grpcurl:0.1,push=true
对 Dockerfile 语法增强
Docker 从 18.09
版本开始,新增内置 buildkitd 作为 backend 执行构建,同时基于 Dockerfile 提供的 Parser directives 特性,可以使用不同版本的 Dockerfile 语法。
如下为不同版本的 Dockerfile 增强的语法:
实现简单的 frontend
buildkit 的强大不仅体现在灵活的 output 和 cache、并发执行等特性,还体现在灵活的 input,即可类似 Dockerfile,自定义不同的构建描述文件,使用自定义的 frontend 来解析描述文件,由 buildkitd 的 backend 执行构建意图。
在 buidlkitd 的实现中,衔接 frontend 和 backend 的核心是名为 LLB (Low-Level Builder)
,它是一种二进制格式,用来描述构建操作的依赖关系图,定义可参见 buildkit/solver/pb/ops.proto。
实现 frontend 的整体思路如下:
下述将给出几个示例,由浅入深理解如何实现自定义的构建描述和 frontends。前 2 个示例关注点在步骤 2) 上,感受如何裸写 LLB,第 3 个示例关注整个链路,走通全部环节。
所有示例源码均在该仓库中:https://github.com/flyer103/buildkit-demo
使用 LLB 硬编码镜像
代码逻辑比较简单,基于 LLB 生成构建的执行动作,包括诸如:
- 设定执行环境
- 拷贝文件
- 执行 shell 命令
- etc.
源码:link
执行如下命令可查看 json 格式的 LLB 描述:
$ go run cmd/hardcode-image/main.go | buildctl debug dump-llb | jq .
执行构建并导出:
$ go run cmd/hardcode-image/main.go | buildctl build --local context=. --output type=tar,dest=out.tar
可以解开上述 tar 包查看构建的产物。
使用 LLB 打包文件
该示例逻辑和上述实例类似,区别在于使用不同的执行环境,由于执行环境的限制带来了可进行的构建操作的限制。
源码:link
可通过上述相同的操作分析 LLB 和执行构建。
实现自定义构建描述
该示例重点参考 An Alternative to the Dockerfile 和 r2d4/mockerfile,针对 buildkit 当前的版本 (v0.10.3
) 做了适配和部分操作的简化。
构建描述文件:link
apiVersion: v1alpha1
images:
- name: demo
from: ubuntu:22.04
package:
install:
- ca-certificates
- curl
- build-essential
- git
- gcc
- lsb-release
external:
- src: https://storage.googleapis.com/kubernetes-release/release/v1.10.0/bin/linux/amd64/kubectl
dst: /usr/local/bin/kubectl
- src: https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v4.5.5/kustomize_v4.5.5_linux_amd64.tar.gz
dst: /usr/local/bin/kustomize
- src: https://get.helm.sh/helm-v3.9.1-linux-amd64.tar.gz
dst: /tmp/helm
install:
- install /tmp/helm/linux-amd64/helm /usr/local/bin/helm
- src: https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-217.0.0-linux-x86_64.tar.gz
dst: /tmp
可以简单把构建描述文件类比为 配置文件,其中的关键字定义了支持的构建操作,可修改的值表征了意图操作的数据。 完整的实现源码参见:link
使用前需要将该 frontend 构建为镜像,作为 buildkitd 内置的 gateway.v0
(对接自定义 frontends 的内置服务) 的 source,用法如下:
$ buildctl build --frontend=gateway.v0 --opt source=<FRONTEND IMAGE> --local context=. --local dockerfile=./yaml/frontend-mockerfile/ --output type=oci,dest=output.tar
完整的操作过程参见 frontend-mockerfile。
小结
本篇文章主要从宏观视角理解 buildkit 的架构和特性,通过实践 buildkit 常见操作,直观感受 buildkit 的作用和可能性。
buildkit 灵活的输入和输出能力,在 应用管理领域 带来了令人兴奋的想象力,实现 代码+配置+XXX
-> 交付物
的最短路径,站在 应用
的维度进行构建和交付。
接下来几篇文章将会研究 buildkit 的核心实现,探索在应用管理中的使用模式。
参考