- 云原生
【免费下载链接】distroless
🥑 Language focused docker images, minus the operating system.
导读
本文以 distroless 仓库的 CONTRIBUTING.md 为骨架,完整还原该项目的贡献者工作流:从签署 Contributor License Agreement、使用 Bazel 构建镜像、通过./knife test运行测试、借助oci_load将镜像加载进本地 Docker、到修改 Debian 包清单后执行./knife lock重新生成锁文件,以及最终提交 Pull Request 的完整流程。读完本文,你将掌握这个基于 Bazel + rules_oci 构建的 distroless 镜像仓库的本地开发环境搭建、构建测试方法与代码规范,能够独立提交一个合规的补丁。
一、贡献前的法律前置:Contributor License Agreements
与 Google 系开源项目一致,distroless 接受补丁(patch)前必须先跨过法律门槛:签署 Contributor License Agreement(CLA)。具体按贡献者身份分两种:
- 个人 CLA(Individual CLA):适用于以个人身份编写原创源代码、且确定自己拥有其知识产权的开发者,签署地址为 Google 的 individual CLA 页面;
- 公司 CLA(Corporate CLA):适用于受雇于某家公司、代表公司贡献代码的开发者,需要由公司签署 corporate CLA。
签署完成后,维护者即可接受你的 Pull Request。这一步是所有后续开发动作的前提,任何补丁合入前都要求 CLA 已就绪。
二、构建项目:bazel build的正确用法
distroless 仓库是一个纯 Bazel 工程,模块依赖在 MODULE.bazel 中声明,包括rules_oci 2.2.7、rules_distroless 0.8.0、container_structure_test 1.19.1、rules_go 0.63.0、rules_rust 0.63.0、rules_python 1.5.3等。整个项目的构建入口有二:
全量构建
bazel build //...这会构建仓库中所有可构建目标。由于镜像按「架构 × 发行版 × 用户模式 × 调试模式」做了矩阵化展开(见下文),全量构建耗时较长,日常开发更推荐定向构建单个镜像。
定向构建单个镜像
以构建base镜像为例:
bazel build //base:static_root_amd64_debian17目标名由//base:static_root_amd64_debian17构成,语义可拆解为:static(镜像类型)+root(用户模式)+amd64(架构)+debian17(发行版)。这里的命名规则可以从 base/BUILD 与 base/base.bzl 中验证:base_image/base_image_index/base_nossl_image等规则通过列表推导式对BASE_DISTROS、BASE_ARCHITECTURES、USERS、DEBUG_MODE做笛卡尔积展开,生成base{mode}_{user}_{arch}_{distro}形式的目标名。例如 common/variables.bzl 定义了USERS = ["root", "nonroot"]与DEBUG_MODE = ["", "_debug"],因此每个发行版/架构组合都会产出root、nonroot、debug(即_debug+ root)与debug-nonroot四类目标。
当前仓库的发行版与架构矩阵定义在 base/config.bzl:
BASE_DISTROS = ["debian13"] BASE_ARCHITECTURES = { "debian13": ["amd64", "arm64", "arm", "s390x", "ppc64le", "riscv64"], }也就是说,本文写作时该仓库基于 Debian 13(trixie),支持 amd64、arm64、arm、s390x、ppc64le、riscv64 六种架构。
三、运行测试:为什么必须用./knife test而不是bazel test //...
CONTRIBUTING.md 特别强调了一个容易踩坑的点:bazel test //...并不会运行全部测试,因为大量测试目标被标记为manual,而./knife test才是完整的测试入口。
原因藏在仓库根目录的 knife 脚本中,其cmd_test实现如下:
function cmd_test () { local arch=$(uname -m) if [ ${arch} == "x86_64" ]; then arch="amd64" fi arch_specific_targets=$(bazel query "attr(\"tags\", "$arch", \"//...\")") bazel test --test_timeout=900 //... $arch_specific_targets }可以看到,./knife test做了两件事:
- 检测本机架构(
x86_64映射为amd64,其余直接沿用uname -m输出); - 用
bazel query "attr(\"tags\", \"<arch>\", \"//...\")"查询出所有打了对应架构 tag 的目标,与//...一起交给bazel test,并设置--test_timeout=900兜底超时。
这与镜像的结构测试机制相吻合:例如 base/test.sh 通过container_structure_test的ext_run.sh运行,而 base/testdata/base.yaml 用fileExistenceTests(检查/etc/passwd、/etc/ssl/certs/ca-certificates.crt、/usr/share/zoneinfo、/var/lib/dpkg/status.d/libc6等文件是否存在)和fileContentTests(断言/etc/passwd中仅含root、nobody、nonroot三个用户,/etc/os-release的PRETTY_NAME="Distroless")对镜像内容做精确校验。这类测试按架构标记,因此只有用./knife test才能把「当前架构」的镜像测试全部拉起来。
环境提示:在 macOS 上运行
knife需要先执行brew install coreutils gnu-sed,脚本会自动把 GNU 工具链路径注入PATH(见 knife 第 20-25 行)。
四、把镜像加载到本地 Docker:oci_load规则
如果你改完代码想在本机 Docker 引擎里实际跑一下镜像,需要给该镜像在对应 BUILD 文件中新增一条oci_load规则。CONTRIBUTING.md 给出了完整模板:
load("@rules_oci//oci:defs.bzl", "oci_load") oci_load( name = "local_build", image = "//base:static_root_amd64_debian17", repo_tags = [], )关键点说明:
load语句从rules_oci(版本 2.2.7,见 MODULE.bazel)引入oci_load规则;image字段指向你要加载的镜像目标,即上文bazel build使用的目标名;repo_tags为空列表时,镜像加载后不附加额外 tag,可自行填写如["myimage:latest"]的形式。
然后运行:
bazel run //:local_buildbazel run会先构建目标再执行加载动作,把 OCI 镜像导入本地 Docker daemon,之后即可用docker run直接启动验证。这也是本地调试镜像改动(例如增删依赖包)最直接的闭环。
五、增删 Debian 包:manifest 与 lock 文件的联动机制
distroless 镜像的底层包依赖由 Debian 仓库清单驱动。每当你修改了common/*.yaml下的 manifest(包清单),必须执行锁定步骤重新生成 lock 文件,否则构建会因锁文件与清单不一致而失败。
./knife lock这一机制的实现细节值得展开:
- manifest 文件存放在 private/repos/deb/ 目录,当前有
trixie.yaml、trixie_java.yaml、trixie_adoptium.yaml、trixie_python.yaml四份清单。以 trixie.yaml 为例,其结构包含sources(指向snapshot.debian.org的 main/updates/security 三个 channel 及精确时间戳快照)、archs(六种架构)与packages(base-files、ca-certificates、libc6、libssl3t64、tzdata、zlib1g等); - 每个 manifest 对应一份
<名字>.lock.json锁文件。仓库通过 deb.MODULE.bazel 中的apt.install扩展把 manifest、lock 文件与package.BUILD.tmpl模板绑定起来(resolve_transitive = False),再用version.from_lock扩展把锁定版本导出为versions仓库供构建引用; ./knife lock的底层实现(knife 第 27-60 行)会先按是否引用snapshot.debian.org把仓库分为 snapshot 与 non-snapshot 两类,然后对每个仓库执行bazel run "@${repo}//:lock",失败时等待 20 秒重试,最多 10 次。
此外,./knife还提供了与包版本管理相关的辅助命令:
./knife deb-versions [-a architecture] [-p package] [-c codename]:从private/repos/deb/*.lock.json中按架构、包名或发行版代号过滤并打印各包的精确版本;./knife update-snapshots:查询 snapshot.debian.org 上最新的 Debian 快照,逐仓库更新debian/<时间戳>与debian-security/<时间戳>引用,交互终端下会请求确认;./knife update-non-snapshots:只对非快照仓库重新锁定,并顺带刷新 Java 版本(update_java_versions_debian13)。
六、代码风格:buildifier 与 pylint
Bazel 文件:buildifier
仓库的 BUILD/WORKSPACE/bzl 文件统一用buildifier格式化,CONTRIBUTING.md 推荐安装 3.2.0 版本并执行:
# Install buildifier version 3.2.0 go install github.com/bazelbuild/buildtools/buildifier@latest # This will automatically fix files. buildifier -mode=fix $(find . -name 'BUILD*' -o -name 'WORKSPACE*' -o -name '*.bzl' -type f)-mode=fix会直接自动修正格式问题。knife也内置了 lint 命令(knife 第 147-160 行):./knife lint以 fix 模式运行,./knife lint --check则只检查不修改;若本机未安装buildifier,脚本会提示「No buildifier executable was found. Did you follow the ./CONTRIBUTING.md ?」并退出。
Python 文件:pylint
仓库中的 Python 代码(如 private/pkg 下的 SPDX 生成器debian_spdx.go/oci_image_spdx.go的伴生逻辑与工具脚本)使用 pylint 检查:
# Install pylint sudo pip install pylint # Or sudo apt-get install pylint # Or on macos brew install pylint # Identify python style issues. find . -name "*.py" | xargs pylint --disable=R,C--disable=R,C表示关闭重构(Refactor)与约定(Convention)类告警,只保留错误(Error)与警告(Warning)级别的检查,避免风格噪音淹没真正的问题。
七、补丁提交流程(Patch Contribution Workflow)
CONTRIBUTING.md 给出了标准化的六步提交流程:
- 提交 Issue:先在对应仓库提交一个描述你拟改动内容的 issue;
- 等待响应:仓库所有者会及时回复该 issue;
- 签署 CLA:若改动方案被接受且尚未签署 CLA,按第一部分完成签署;
- Fork 并开发:fork 目标仓库,在本地完成代码修改与测试(构建、测试、lint 均通过);
- 提交 Pull Request:将改动以 PR 形式提交,等待维护者 review 与合入。
其中第 4 步建议完整走一遍本文第二至六节描述的流程:bazel build //...或定向构建确认编译通过,./knife test确认当前架构下全部结构测试通过,buildifier/pylint确认代码风格合规;若改动涉及 Debian 包清单,别忘了./knife lock重新生成锁文件并随 PR 一并提交。
八、给贡献者的额外提示:基于仓库源码的验证要点
结合仓库源码,以下三点能显著提高 PR 一次通过率:
- 镜像目标命名是矩阵化的:新增镜像或修改 base/config.bzl 中的
BASE_PACKAGES/BASE_NOSSL_PACKAGES(base 镜像默认包含libc6、libssl3t64、libzstd1、zlib1g;nossl 变体仅含libc6)时,先在bazel query或bazel build中核对生成的目标名,避免引用不存在的目标; - 结构测试与镜像内容强绑定:base/testdata/base.yaml 等测试数据断言了
/etc/passwd用户集合、/etc/group组集合、os-release 内容等细节,任何改变文件系统内容的改动都可能击穿测试,务必在本地跑./knife test; - 锁文件是构建的硬依赖:deb.MODULE.bazel 中
apt.install把 lock 文件作为构建输入,修改 private/repos/deb/ 下任何*.yaml后忘记./knife lock,CI 与本地构建都会失败。
遵循以上流程与规范,你就可以顺畅地为 distroless 项目贡献高质量的补丁了。
- 云原生
【免费下载链接】distroless
🥑 Language focused docker images, minus the operating system.
相关推荐
Bitcoin Core 贡献者指南:从提交补丁到 Peer Review 的完整工作流
Bitcoin Core 贡献者指南:从提交补丁到 Peer Review 的完整工作流 本文以 Bitcoin Core 仓库根目录的 CONTRIBUTIN
区块链金融科技网络密码学Skia 贡献指南:从报 Bug、写测试到提交补丁的完整开发流程
Skia 贡献指南:从报 Bug、写测试到提交补丁的完整开发流程 Skia 是一个用于绘制文本(Text)、几何图形(Geometries)和图像(Images
图形学jQuery Core 贡献者实战指南:从 Bug 报告到补丁提交的完整工作流
jQuery Core 贡献者实战指南:从 Bug 报告到补丁提交的完整工作流 jQuery 是一个被数以百万计站点依赖的开源 JavaScript 库,本文以
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考