Moby 引擎构建与交叉编译实战:基于 Docker Buildx Bake 的 docker-bake.hcl 全解
【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby
本篇基于 ctn-build.md 贡献指南展开,讲解 Moby(Docker Engine 上游项目)如何利用根目录的 Dockerfile 与 docker-bake.hcl 构建定义,通过docker buildx bake完成 dockerd 及辅助工具的二进制构建与多平台交叉编译,并逐条拆解 bake 变量、target 与 Makefile、hack/make.sh 底层脚本的协作关系,读完即可在本地复现官方 CI 同款构建链路。
构建体系总览
Moby 的构建体系围绕三个文件组织:
- Dockerfile:根目录多阶段构建文件,声明了 Go 工具链、交叉编译助手
tonistiigi/xx、以及 containerd、runc、tini、rootlesskit、crun、containerutility 等依赖组件的独立构建阶段; - docker-bake.hcl:Buildx Bake 定义文件,声明了所有构建变量、target 与平台矩阵,是
docker buildx bake命令的“配方”; - Makefile:把
docker buildx bake封装成make binary、make cross等便捷入口,并负责把本地环境变量透传进构建容器。
正如 ctn-build.md 所述:Dockerfile 支持使用 Docker Buildx 与 BuildKit 构建和交叉编译 docker daemon 及额外工具,而 bake 定义正是为了简化这一过程而存在的。文档同时指出,set-up-dev-env.md 中“development container”一节介绍了如何在 Linux 容器中开发和编译改动,而二进制构建与交叉编译则由 bake 命令直接完成。
核心构建命令全解
以下是 ctn-build.md 给出的全部官方命令,按用途分组:
# build binaries for the current host platform # output to ./bundles/binary-daemon by default docker buildx bake # or docker buildx bake binary # build binaries for the current host platform # output to ./bin DESTDIR=./bin docker buildx bake # build dynamically linked binaries # output to ./bundles/dynbinary-daemon by default DOCKER_STATIC=0 docker buildx bake # or docker buildx bake dynbinary # build binaries for all supported platforms docker buildx bake binary-cross # build binaries for a specific platform docker buildx bake --set *.platform=linux/arm64 # build "complete" binaries (including containerd, runc, etc.) docker buildx bake all # build "complete" binaries for all supported platforms docker buildx bake all-cross # build non-runnable image wrapping "complete" binaries # useful for use with undock and sharing via a registry docker buildx bake bin-image # build non-runnable image wrapping "complete" binaries, with custom tag docker buildx bake bin-image --set "*.tags=foo/moby-bin:latest" # build non-runnable image wrapping "complete" binaries for all supported platforms # multi-platform images must be directly pushed to a registry docker buildx bake bin-image-cross --set "*.tags=foo/moby-bin:latest" --push逐条说明:
docker buildx bake(不带 target)等价于bake binary。原因在 docker-bake.hcl 中:group "default"的targets = ["binary"],bake 默认执行 default group。- 输出目录由
DESTDIR变量控制。docker-bake.hcl 定义了DESTDIR变量和一个bindir函数:DESTDIR != "" ? DESTDIR : "./bundles/${defaultdir}",所以默认落在./bundles/binary-daemon(静态)或./bundles/dynbinary-daemon(动态),设置DESTDIR=./bin后则落到./bin。 DOCKER_STATIC=0生成动态链接二进制,与bake dynbinary等价。默认DOCKER_STATIC=1,即静态构建;动态目标会输出到bundles/dynbinary-daemon。binary-cross覆盖所有支持平台。平台清单定义在 docker-bake.hcl 的_platformstarget 中:linux/amd64、linux/arm/v5、linux/arm/v6、linux/arm/v7、linux/arm64、linux/ppc64le、linux/s390x、windows/amd64。--set *.platform=linux/arm64是 Buildx bake 的变量覆盖语法,可把任意 target 的平台收窄到单个平台做定向构建(例如只构建 Windows 版)。all/all-cross构建“完整”二进制集,除 dockerd、docker-proxy 外还打包 containerd、runc 等配套工具,对应 Dockerfile 中的all阶段。bin-image/bin-image-cross把“完整”二进制封装进一个不可运行(scratch 基底)的镜像,便于配合undock解包分发,或直接推送到 registry 共享。bin-image-cross使用type=image输出,多平台镜像必须--push直接推送(因为多平台 manifest 本地无法落盘)。
深入 docker-bake.hcl:变量与 Target 矩阵
构建变量
docker-bake.hcl 顶部声明了以下变量,均可通过环境变量注入:
| 变量 | 默认值 | 作用 |
|---|---|---|
DOCKER_DEBUG | "" | 非空时开启调试:hack/make.sh会加-gcflags="all=-N -l"禁用内联与优化,并打印构建命令 |
DOCKER_STATIC | "1" | 1为静态链接,0为动态链接(影响 dockerd 本体与 containerd、runc 等组件的构建方式) |
DOCKER_LDFLAGS | "" | 追加给go build -ldflags,可用于构建期注入值(见下文 Makefile 的 graphdriver 优先级示例) |
DOCKER_BUILDTAGS | "" | 追加 Go build tag |
DOCKER_GITCOMMIT | null | 指定写入二进制的 git 提交号 |
VERSION | "" | Docker 版本号,如23.0.0-dev,通常由 Git ref 自动生成 |
PLATFORM | "" | 平台名,如Docker Engine - Community |
PRODUCT | "" | 产品名,用于 BuildKit 的ExportedProduct,在不支持某特性的版本上给出友好报错 |
DEFAULT_PRODUCT_LICENSE | "" | 对应version.DefaultProductLicense,可放置商业引擎的许可证摘要 |
PACKAGER_NAME | "" | 打包者名称,用于 Windows 清单的CompanyName |
DESTDIR | "" | 覆盖输出目录(见上) |
SYSTEMD/FIREWALLD | false | 仅用于devtarget,决定开发容器是否安装 systemd、firewalld |
GOVULNCHECK_FORMAT | null | govulnchecktarget 的输出格式 |
所有核心变量(除DESTDIR外的前 11 个)都被_commontarget(docker-bake.hcl)以args形式统一注入 Dockerfile 构建,实现“一处声明、全局生效”。
Target 依赖关系
从源码结构看,各 target 通过inherits形成清晰的继承链:
binary:继承_common,构建 Dockerfile 的binary阶段,输出bindir(DOCKER_STATIC == "1" ? "binary" : "dynbinary");dynbinary:继承binary,强制DOCKER_STATIC=0,输出固定为dynbinary目录;binary-cross:继承binary+_platforms,即“对全平台矩阵重复 binary 构建”;binary-smoketest:继承_common,构建smoketest阶段且output = ["type=cacheonly"](不落盘产物,仅验证可编译可运行);all:继承_common,构建all阶段(完整工具集),输出目录规则同binary;all-cross:all+_platforms;bin-image:继承all与docker-metadata-action(tags 为moby-bin:local),输出type=docker(本地镜像);bin-image-cross:继承bin-image,改type=image并枚举 7 个平台(含windows/amd64);dind:继承_common,构建dind阶段,tag 为docker-dind,输出本地镜像;dev:继承_common,构建dev阶段(即开发容器),额外透传SYSTEMD/FIREWALLD,tag 为docker-dev;govulncheck:使用独立的 hack/dockerfiles/govulncheck.Dockerfile,用于 Go 漏洞检查。
此外,defaultgroup 只包含binary,这就是裸docker buildx bake的默认行为来源。
深入 Dockerfile:构建阶段如何工作
Dockerfile 是 bake 命令真正执行的构建脚本,关键设计点:
1. 交叉编译基础:tonistiigi/xx
Dockerfile 第一行即FROM --platform=$BUILDPLATFORM tonistiigi/xx阶段,把xx交叉编译工具集注入base阶段。之后各组件统一使用xx-go build、xx-apt-get install、xx-verify等命令完成目标平台的 Go 编译、依赖安装和产物校验——这正是binary-cross能“一次 bake 出八平台产物”的底层机制。基础镜像由GO_VERSION=1.26.8与 Debian bookworm 的 golang 官方镜像构成(Dockerfile)。
2. 配套组件的独立阶段
每个辅助工具都是一个“src + build + OS 选择”三段式:
- containerd:Dockerfile 从 containerd v2.3.4 源码构建
containerd、containerd-shim-runc-v2、ctr;DOCKER_STATIC=1时以make STATIC=1、CGO_ENABLED=0编译并xx-verify --static校验静态链接。Windows 平台则回退到binary-dummy空阶段。 - runc:Dockerfile 构建 runc v1.5.1,静态时执行
make static。 - tini(docker-init):Dockerfile 以 cmake 构建
tini-static,供容器--init使用。 - rootlesskit:Dockerfile 静态时用
CGO_ENABLED=0纯 Go 编译,动态时-linkmode=external;同时把 contrib/dockerd-rootless.sh 与 contrib/dockerd-rootless-setuptool.sh 拷入产物目录。 - crun / containerutility(Windows)/ criu:分别见 Dockerfile、Dockerfile、Dockerfile(criu 阶段当前因 opensuse 仓库稳定性被注释停用)。
3.build、binary与all阶段
build(Dockerfile):安装 clang/lld 等依赖后,在$BUILDPLATFORM上执行PKG_CONFIG=$(xx-go env PKG_CONFIG) ./hack/make.sh "$target",其中DOCKER_STATIC=1时target=binary(静态),否则target=dynbinary;构建完成后用xx-verify校验/tmp/bundles/<target>-daemon/dockerd与docker-proxy,并把产物搬到/build/。ENV PREFIX=/tmp是为了规避只读工作目录导致的 make.sh 输出失败。binary(Dockerfile):FROM scratch,仅拷贝 dockerd + docker-proxy;all(Dockerfile):FROM scratch,额外COPY --link引入 tini、runc、containerd、rootlesskit、containerutility 各阶段的/build/产物——这正是bake all所称“complete binaries”的由来。smoketest(Dockerfile):在 base 上运行file dockerd && dockerd --version && docker-proxy --version,用于 CI 上binary-smoketest的冒烟验证。dind(Dockerfile):基于官方docker:dind镜像,把 cli、buildx、compose 插件与all阶段产物叠加进去,产出docker-dind开发用 DinD 镜像。
4. 从 make.sh 到 go build 的真实调用链
Dockerfile 的build阶段最终落到 hack/make.sh:
- hack/make.sh 的
bundle()函数会source hack/make/<bundle名>; - hack/make/binary-daemon 以
DOCKER_STATIC=1、GO_PACKAGE='github.com/moby/moby/v2/cmd/dockerd'、BINARY_NAME='dockerd'调用 hack/make/.binary,并在目标平台与宿主机一致时把 runc、containerd、docker-init 等“嵌套可执行文件”一并拷入 bundle 目录; - hack/make/.binary 处理了动态构建的细节:非静态时在多数平台追加
-buildmode=pie(mips*/ppc64 除外);hack/make/.binary 中 Windows 下移除netgotag 以使用系统 DNS 解析器;hack/make/.binary 针对 arm/v5 交叉编译补充-Wno-atomic-alignment与-latomic;hack/make/.binary 最终执行带-tags "netgo osusergo static_build nri_no_wasm ..."的go build,并把DOCKER_LDFLAGS拼入-ldflags。
版本号方面,hack/make.sh 会解析VERSION(支持 tag/分支/PR ref 归一化),hack/make.sh 则取DOCKER_GITCOMMIT或git rev-parse HEAD,工作区不干净时自动追加-unsupported后缀——这解释了 bake 变量为何要提供VERSION与DOCKER_GITCOMMIT两个注入点。
Makefile 封装与环境变量透传
不想手写docker buildx bake时,Makefile 提供了等价入口:
binary: $(BAKE_CMD) binary # 静态 linux 二进制 dynbinary: $(BAKE_CMD) dynbinary # 动态链接 linux 二进制 cross: $(BAKE_CMD) binary-cross # 全平台交叉构建 win: $(BAKE_CMD) --set *.platform=windows/amd64 binary(见 Makefile 与 Makefile。)
Makefile 中default: binary,且BAKE_CMD := $(BUILDX) bake,DOCKER_GITCOMMIT在 Makefile 顶部即由git rev-parse HEAD计算并export,因此make binary与docker buildx bake binary完全等价。Makefile 的DOCKER_ENVS列表把DOCKER_LDFLAGS、VERSION、PLATFORM、PRODUCT等变量以-e形式透传进运行容器——其中注释给出了一个典型用法(Makefile):
make DOCKER_LDFLAGS="-X github.com/moby/moby/v2/daemon/graphdriver.priority=overlay2,zfs" dynbinary即在构建期通过-ldflags -X改写内置的 graphdriver 优先级列表。
值得注意的约束:Makefile 明确说明不能把DOCKER_BUILDTAGS加入-e列表,因为即使 shell 中未设置,-e也会遮蔽 Dockerfile 内的ENV DOCKER_BUILDTAGS,而后者对官方构建很重要。
输出位置与产物结构速查
综合 bake 定义与 Dockerfile,各命令的产物去向如下:
| 命令 | 构建阶段 | 产物/输出位置 |
|---|---|---|
docker buildx bake/bake binary | binary | ./bundles/binary-daemon/(DESTDIR覆盖之) |
docker buildx bake dynbinary | binary(动态) | ./bundles/dynbinary-daemon/ |
DESTDIR=./bin docker buildx bake | binary | ./bin/ |
docker buildx bake binary-cross/all-cross | 全平台矩阵 | 各平台对应目录(本地type=local输出) |
docker buildx bake all | all | ./bundles/{binary,dynbinary}-daemon/,含 containerd、runc 等 |
docker buildx bake bin-image | all | 本地镜像moby-bin:local(scratch 基底,不可运行) |
docker buildx bake bin-image-cross --push | all | 多平台 manifest 推送至 registry |
make win | binary(windows/amd64) | dockerd.exe 等 |
小结
Moby 的二进制构建遵循“Dockerfile 定义能力,docker-bake.hcl 编排场景,Buildx bake 触发执行”的三层结构:DOCKER_STATIC、DESTDIR、PLATFORM、VERSION等变量贯穿 bake 与 Dockerfile 两个文件,_commontarget 保证了变量注入的一致性;binary/allscratch 阶段与tonistiigi/xx工具链支撑了八平台的静态/动态交叉编译,而 hack/make.sh 与 hack/make/.binary 则把 Go 编译细节(build tags、PIE、ARM 标志、版本注入)封装在 bake 背后。贡献者按 docs/contributing/ctn-build.md 的命令操作,即可与 CI 使用完全相同的构建链路产出可复现的引擎二进制。
【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考