CI 里使用 buildkit 构建镜像
CI 里用一个常见的场景是将代码提交后,打上 tag,CI 就可以检查代码并且自动构建镜像。
但是在 CI 里构建镜像比较复杂,因为 CI 的任务一般是运行在容器里,而需要在一个容器里运行 docker build,就需要在容器里再启一个 docker daemon 进程。这种形式叫 DinD(docker in docker)。
DinD 模式有一些问题
- 安全问题,支持 DinD 模式的容器必须以特权模式运行
- 性能问题,两层驱动,导致磁盘使用效率低下
基于这个原因,出现了 DooD(docker outside of docker),将宿主机的 docker socket 挂载到容器里,容器里的 docker 客户端直接于宿主机的 docker daemon 进程通信。DooD 会比 DinD 好一些,但是还是有问题
- 安全问题,容器里的 docker 客户端拥用宿主机 docker daemon 进成的所有权限,也有些危险
- Docker 构建上下文路径问题,如果在容器里用 git clone 一些文件用于构建,但是实际的构建是宿主机上的 docker daemon 进程,它的路径里可能没有这些文件,会导致构建失败。
所以人们开始尝试一种不需要 docker daemon 进程的构建方法,毕竟我们只需要构建一个镜像就行,不是真的要运行 docker 容器。基于此,出现了一些产品。比较出名的有 kaniko, buildah,buildkit。今年(2025年)kaniko 已经归档了,buildah 看起来开发也不是很活跃,而 buildkit 是 moby 和 docker 一起开发的产品,估计以后的市场就是 buildkit 的了。
buildkit 被称为是下一代的 docker 构建工具,提升了构建速度,同时提供了一种 daemonless 的构建方式,非常适合在 CI 里使用。
在 CI 里配置 buildkit 需要注意以下几项内容。
使用 buildkit 代替 docker build
指定镜像,改掉 entrypoint
image:
name: moby/buildkit:rootless
entrypoint: [""]
默认情况下,这个镜像启动时就会启动一个 daemon 进程。如果不改掉 entrypoint,CI 就会卡在 running server on /run/user/1000/buildkit/buildkitd.sock
这样一条日志上。
指定构建环境变量
variables:
BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
启用这个命令不让 buildkit 再造一个沙合来执行 docker 的命令。
配置私有仓库权限
before_script:
- mkdir -p ~/.docker
- |
echo "{
\"auths\": {
\"${CI_REGISTRY}\": {
\"auth\": \"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"
},
\"$(echo -n $CI_DEPENDENCY_PROXY_SERVER | awk -F[:] '{print $1}')\": {
\"auth\": \"$(printf "%s:%s" ${CI_DEPENDENCY_PROXY_USER} "${CI_DEPENDENCY_PROXY_PASSWORD}" | base64 | tr -d '\n')\"
}
}
}" > ~/.docker/config.json
创建一个配置文件,将私有仓库的账号填进去。
配置 dockerhub 代理
before_script:
- cat <<'EOF' > /tmp/buildkit.toml
[registry."docker.io"]
mirrors = ["mirror.example.com"]
EOF
修改 docker 构建命令
script:
- |
buildctl-daemonless.sh build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=. \
--output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true
将 docker build 命令改成上面的命令,注意修改镜像名称,另外最后也可以配置要不要 push。
配置缓存
待补充
build 参数详解
待补充