首页
/ 从源码构建 Docker Registry(distribution):Moby 仓库内 BUILDING 指南的完整实战解读

从源码构建 Docker Registry(distribution):Moby 仓库内 BUILDING 指南的完整实战解读

2026-09-06 19:02:31作者:仰钰奇

导读:本篇文章围绕 Moby 仓库中随源码一起 vendored 的 Docker Distribution 项目构建文档(vendor/github.com/docker/distribution/BUILDING.md),系统讲解如何从源码编译出 Registry 2.0 的 registry 二进制、如何搭建可重复的 Go 构建环境、如何解读官方 Makefile 的完整构建流水线,并结合仓库源码厘清 distribution 库与 Moby daemon 镜像拉取/推送(pull/push)之间的关系。读完你将掌握一套可直接复现的 Registry 源码构建方法,并理解 Moby 内核中该模块的真实落点。

适用场景:为什么要从源码构建 Registry?

官方文档开宗明义地指出:“从源码构建 registry”这个场景只适用于打算积极参与 Registry 项目开发的人,即你需要在源码层面修改、调试、跟踪 Docker Registry 2.0(即 distribution 项目)的行为。

在使用 Moby(本仓库)的人群中,这个主题尤其值得关注,因为 Moby daemon 对镜像的拉取与推送功能正构建在 distribution 这一套代码之上。即便你并不直接编译 registry 二进制,理解其构建路径也能帮助你:

  • 定位 Moby 中与 Registry 相关的镜像分发问题;
  • 在需要 fork 或扩展 registry 行为时,拥有可编译、可测试的基线;
  • 理解 Moby 内部 daemon/internal/distribution 等目录所依赖的底层库是如何被组织与构建的。

大多数用户不需要从源码构建

BUILDING.md 同时提醒读者评估自己的真实需求:

  • 普通用户应优先使用官方 registry Docker 镜像,一条 docker run 即可拉起一个 Registry 2.0 服务,无需任何编译;
  • 有高级运维需求(如定制日志、认证插件、存储驱动等)的用户,可以通过自定义 Dockerfile 并继承 FROM registry:2 来“包一层”自己的镜像,而不是重编源码;
  • macOS 用户希望本地原生运行时,可以参照官方 registry 部署指南中专门为 OS X 编写的安装步骤。

文档的结论非常务实:若你是没有开发经验、没有 Go 基础的普通使用者,从源码构建大概率“不是你的好选择”。这正是本文所涉流程的真正定位——面向开发者与贡献者。

前提条件与踩坑提示(Gotchas)

BUILDING.md 明确给出两条预期:

  1. 你需要对 Go 与 Git 有基本的操作能力;
  2. 整个过程假设你已经熟悉 Go 模块、GOPATHgo get 等工作流,而不是零基础入门教程。

另一个贯穿全文的关键约束是 GOPATH 路径约定

尽管不强制使用 go get 来检出 distribution 工程,但要让文档中的构建步骤生效,工程必须被检出在 GOPATH 中的正确位置,几乎总是 $GOPATH/src/github.com/docker/distribution

在 Go Modules 之前的传统开发流中,这个位置决定了一切 import 路径能否解析。当前 Moby 仓库以 vendor 方式把该工程放在 vendor/github.com/docker/distribution 目录下,供 daemon 内部代码直接引用;如果你要单独构建 registry 主程序,则需要按上述路径自行检出完整工程(详见后文“在 Moby 仓库内的真实落点”一节)。

搭建 Go 开发环境

构建 distribution 目标的前提是先有一个正确配置的 Go 开发环境。官方指引了 Go 官方的《How to Write Go Code》,核心要求是环境中已正确设置 GOROOTGOPATH 两个变量:

# 检查环境变量是否已设置
echo $GOROOT
echo $GOPATH

当 Go 环境就绪后,一条 go get 即可从最新代码安装 registry 命令:

go get github.com/docker/distribution/cmd/registry

这条命令会把源码仓库安装到 GOPATH 中,并为 cmd/registry 编译生成二进制。

准备 registry 数据目录

接下来创建 registry 数据存储目录(可能需要调整权限):

mkdir -p /var/lib/registry

如果你希望把数据放在其他位置,可以通过环境变量覆盖默认存储根目录:

export REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/somewhere

这一环境变量的命名规则对应 registry 配置中的 storage.filesystem.rootdirectory 层级:运行时可被环境变量 REGISTRY_<SECTION>_<KEY> 覆盖,是 distribution 项目经典配置机制的体现。

验证二进制与首次启动

编译出的 registry 二进制可用 --version 验证版本信息:

$ $GOPATH/bin/registry --version
$GOPATH/bin/registry github.com/docker/distribution v2.0.0-alpha.1+unknown

上述输出是 BUILDING.md 写作年代的真实样例(当时尚处于 v2.0.0-alpha 阶段),当前实际输出中的版本号会随检出的代码版本变化;Moby 仓库内 Makefile 会通过 git describe --match 'v[0-9]*' 动态注入版本号(详见后文)。

使用默认配置启动 registry:

$ $GOPATH/bin/registry serve $GOPATH/src/github.com/docker/distribution/cmd/registry/config-example.yml
INFO[0000] endpoint local-5003 disabled, skipping        app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
INFO[0000] endpoint local-8083 disabled, skipping        app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
INFO[0000] listening on :5000                            app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
INFO[0000] debug server listening localhost:5001

如果工作正常,你应该能看到上述日志:

  • 两个 endpoint ... disabled 说明示例配置中声明的额外调试端点被跳过;
  • listening on :5000 表示 registry HTTP 服务已在 5000 端口就绪;
  • debug server listening localhost:5001 表示调试服务(默认绑定 localhost:5001)也已启动。

此时你的本地 Registry 2.0 即可接受 docker pull / docker push(配合 insecure-registry 或 TLS 配置)。

可重复构建:官方 Makefile 全解析

BUILDING.md 强调,cd 进入工程目录后,常规的 go test 等命令应对每个包直接可用;但为了获得**可重复(Repeatable)**的完整构建体验,项目提供了 Makefile

使用该 Makefile 前,文档建议先在 GOPATH 中安装静态检查工具:

go get github.com/golang/lint/golint

随后直接执行 make 即可触发完整构建链:

$ make
+ clean
+ fmt
+ vet
+ lint
+ build
github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar
github.com/sirupsen/logrus
github.com/docker/libtrust
...
github.com/yvasiyarov/gorelic
github.com/docker/distribution/registry/handlers
github.com/docker/distribution/cmd/registry
+ test
...
ok    github.com/docker/distribution/digest 7.875s
ok    github.com/docker/distribution/manifest 0.028s
ok    github.com/docker/distribution/notifications  17.322s
?     github.com/docker/distribution/registry [no test files]
ok    github.com/docker/distribution/registry/api/v2  0.101s
?     github.com/docker/distribution/registry/auth  [no test files]
...
+ /Users/sday/go/src/github.com/docker/distribution/bin/registry
+ /Users/sday/go/src/github.com/docker/distribution/bin/registry-api-descriptor-template
+ binaries

Makefile 内部机制:构建目标逐个击破

对照 Moby 仓库中的真实 Makefile 源码,我们可以精确还原这套“可重复构建”的流水线:

阶段 实际执行内容(对应 Makefile 目标/代码)
clean 删除产物,rm -f $(BINARIES)
fmt / vet 对项目包执行 Go 格式化与静态检查(fmtvet 目标)
lint 调用 golangci-lint --build-tags "${BUILDTAGS}" run(对应 check 目标,默认关闭 GO111MODULE)
build go build 全项目包,编译参数由 GO_GCFLAGSGO_LDFLAGSGO_TAGS 组合注入
test go test -test.short ...,默认追加 -v,通过 TESTFLAGS_RACE 可开启竞态检测
binaries 生成 $(BINARIES),即 bin/registrybin/digestbin/registry-api-descriptor-template 三个二进制

值得注意的几个实现细节:

  1. 版本与修订号注入VERSION 通过 git describe --match 'v[0-9]*' --dirty='.m' --always 获取(工作区有未提交改动时附加 .m 后缀),REVISION 来自 git rev-parse HEAD;最终经 -X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) 注入到 version 包。这就是 registry --version 能显示诸如 v2.0.0-alpha.2-80-g16d8b2c.m 这种“版本+提交数+commit 前缀+脏标记”格式的原因。
  2. 模块内的目录即产物目录。二进制统一落到工程根目录下的 ./bin/,因而可以像文档演示那样直接验证产物:
$ ./bin/registry --version
./bin/registry github.com/docker/distribution v2.0.0-alpha.2-80-g16d8b2c.m
  1. 基于 vendor 目录的可重复性。文档指出这套构建“使用 vendor 目录中的内容,涵盖格式化、vet、lint、构建、测试以及生成打标签的二进制”,保证了团队成员在同一 commit 上得到一致结果。

测试矩阵:短测、竞态、全量与集成

Makefile 中提供了分层测试目标,文档里的 make 默认串行执行的是短测(-test.short):

  • test:单元测试(短模式);
  • test-race:追加 -race 的竞态检测版本;
  • test-full:不带 -test.short 的全量单测(跳过集成测试);
  • integration:以 -parallel 8 并发运行集成测试包;
  • coverage:遍历非 storage-driver 的包,逐包生成 -covermode=atomic 的覆盖率文件并汇总为 coverage.txt

可选构建标签:BUILDTAGS

BUILDING.md 在最后提到,可以通过环境变量 BUILDTAGS 传入可选的 Go build tags。该机制的实现贯穿整个 Makefile:

  • PACKAGES=$(shell go list -tags "${BUILDTAGS}" ./... | grep -v /vendor/):项目包列表随构建标签动态变化;
  • GO_TAGS=$(if $(BUILDTAGS),-tags "$(BUILDTAGS)",):为空时不追加 -tags 参数,非空时原样传给所有 go build / go test

实际用法示例:

# 不传任何标签(默认)
make

# 传入自定义标签
BUILDTAGS="foo bar" make

文档本身并未进一步列举可用的具体标签取值——BUILDING.md 对 Optional build tags 的描述到此为止,后续能力请以完整 distribution 源码中对应的 build constraints 为准。对于本仓库读者,更重要的是理解这一约定:Moby 中所有编译与测试命令也遵循相同的 BUILDTAGS 透传约定,可参考 Moby 根目录的 hack/make 系列脚本了解如何组合使用。

在 Moby 仓库内的真实落点:vendor 库与 daemon 的镜像分发

阅读 BUILDING.md 时有一个容易产生困惑的点:本文所在 Moby 仓库中,distribution 以 vendor 依赖库的形式存在于 vendor/github.com/docker/distribution,而文档中反复提到的 cmd/registry 主程序、config-example.ymlbin/ 产物并不在该 vendor 子树内(Moby 只需其中的库代码)。因此:

  • 若想完整复现“编译 registry 二进制 + 用 config-example.yml 启动”的步骤,需要按文档要求在 $GOPATH/src/github.com/docker/distribution 检出完整的 distribution 工程后执行;
  • 而在 Moby 仓库内,distribution 的价值体现在它被 daemon 当作镜像分发库使用。

daemon 如何消费这套库

daemon/internal/distribution/config.go 为例,daemon 侧定义了 ImagePullConfig / ImagePushConfig 等结构,直接引用 github.com/docker/distribution 暴露的核心抽象:

  • distribution.Repository / ManifestService / TagService / BlobStore 等接口(定义见 registry.go);
  • manifest/schema2 中的媒体类型常量(如 schema2.MediaTypeUncompressedLayer),用于标明推送层的内容格式;
  • manifest 相关描述与 OCI schema 兼容逻辑(见 manifest 子目录下的 schema2ocischemamanifestlist 包)。

从代码结构可以推断,Moby 的镜像 pull/push 主流程(daemon/internal/distribution/pull_v2.gopush_v2.go)就是围绕这套接口完成对 Registry HTTP API V2 的客户端交互,而卷/镜像层数据则通过 xfer(LayerDownloadManager/LayerUploadManager)并发调度。也就是说,BUILDING.md 教你把 registry(服务端)从源码编译起来,而这套被编译验证过的同一份代码、同一套接口,也正是 Moby 连接 registry 服务端进行镜像分发的“半边天”

结语:一图看懂构建路线与常见误区

回顾 BUILDING.md 的全部要点,可以提炼出三条实践路线:

  1. 使用 Registry:直接运行官方 registry:2 镜像(或 FROM registry:2 定制),无需 Go 环境;
  2. 参与 distribution 开发:配置好 GOROOT/GOPATH → 在正确路径检出工程 → mkdir -p /var/lib/registrygo getmake 构建 → registry serve 启动调试;
  3. 在 Moby 内扩展镜像分发:理解 vendor/github.com/docker/distribution 是编译时依赖而非可运行服务,编译入口在 daemon 侧(如 daemon/internal/distribution/config.go),配合 Makefile 中的集成测试链路验证改动。

常见误区需要避开两点:

  • 不要把 vendor 子树当成可以 registry serve 的工程——主程序 cmd/registry 不在其中;
  • 不要忽略 GOPATH 路径约定——distribution 库的 import 路径是 github.com/docker/distribution,检出位置错误将直接导致编译失败。

总而言之,BUILDING.md 是为“Registry 的贡献者与高级用户”书写的构建指南:它教会你从零编译服务端二进制,并理解一套覆盖 fmt → vet → lint → build → test → 产出 bin/registry 的可重复流水线。掌握它之后,无论是提交上游 patch,还是在 Moby 生态中排查分发链路,你都能拥有一份自给自足的源码级控制力。

登录后查看全文
热门项目推荐
相关项目推荐