首页
/ 深入解析 Kubernetes 的 staging 目录:单仓库开发与 k8s.io 多仓库发布的桥接架构

深入解析 Kubernetes 的 staging 目录:单仓库开发与 k8s.io 多仓库发布的桥接架构

2026-09-06 19:18:57作者:咎岭娴Homer

staging/ 是 Kubernetes 主仓库中一块特殊的中转区:它以"目录内的 Go 模块"形式,容纳了 30 多个将被拆分到独立 k8s.io/* 顶层仓库(如 client-goapiapimachinery)的代码,并让主仓库在编译期直接引用这些本地模块,而不是引用发布到远端的外部依赖。阅读本文后,你将理解 staging/ 目录的结构与运作原理、Go workspace + replace 的引用机制、staging 到独立仓库的自动发布链路,以及如何按社区规范向 staging 新增一个仓库。

关联文档为仓库根下的 staging/README.md,文中所有源码证据均来自当前仓库快照。

staging 是什么:外部仓库的"代码中转站"

根据 staging/README.md 的定义,staging/ 目录是已被拆分为独立仓库的软件包的中转区(staging area)。这些包会周期性发布(publish)到 Kubernetes org 下各自的顶层仓库,例如 k8s.io/apik8s.io/client-go

该机制要解决的核心矛盾是:这些包被大量外部项目独立使用、需要独立版本化与发布;但 Kubernetes 主仓库自身又以极快的节奏演进,若是直接通过 go get 拉取远端已发布版本,改动落地会有发布延迟与繁琐的依赖协调。staging 的解法是——代码先留在主仓库内、以真实目录存在并被直接引用,待到合适时机再同步到独立仓库对外发布

值得注意的是文档强调:staging/ 目录中的代码是权威代码(authoritative),是这些代码的唯一副本,你可以直接修改它。这与 vendor/(纯粹拷贝的第三方依赖)有本质区别——修改 vendor 没有意义且会被覆盖,而修改 staging 就是修改 Kubernetes 上游本身。

目录布局

staging/
├── README.md                  # 本文档
├── OWNERS
├── publishing/                # 发布配置:rules.yaml、import-restrictions.yaml、OWNERS
├── src/k8s.io/<repo>/         # 每个被拆分仓库的代码(每个目录是一个独立 Go module)
└── test/                      # staging 相关辅助脚本
  • staging/publishing/rules.yaml:描述每个模块如何发布到目标仓库、分支如何映射、依赖哪些其他 staging 仓库;
  • staging/publishing/import-restrictions.yaml:声明每个 staging 模块允许导入的包清单,约束依赖方向;
  • staging/src/k8s.io/:所有被拆分包的实际代码根。本仓库快照中该目录下共登记 33 个 Go module 目录。

当前登记在案的仓库清单

README 列出的"当前被 staging 的仓库"共 32 个,可按功能分组梳理如下(括号内为仓库内路径,格式统一为 staging/src/k8s.io/<名称>):

功能类别 仓库(staging 目录名)
API 类型与工具链 apiapimachinerycode-generator
API Server 体系 apiserverapiextensions-apiserverkube-aggregatorsample-apiserver
客户端与 CLI client-gocli-runtimekubectlsample-cli-plugin
控制面组件 kube-controller-managercontroller-managercomponent-basecomponent-helpers
节点与运行组件 kubeletkube-proxykube-schedulercri-apicri-clientcri-streaming
云与存储 cloud-providercsi-translation-libmount-utils
资源与扩展 dynamic-resource-allocationendpointslicemetricscluster-bootstrap
安全 externaljwtkmspod-security-admission
示例项目 sample-controller

每个名称均对应真实目录,例如 staging/src/k8s.io/client-gostaging/src/k8s.io/apimachinery 等。此外,本快照的 go.workstaging/publishing/rules.yaml 中还额外登记了 k8s.io/streaming 模块,README 清单与其保持一致即可覆盖当前全部模块。

Kubernetes 代码如何"引用"这些仓库:Go workspace 与 replace

阅读 staging/README.md 的 "Using staged repositories from Kubernetes code" 一节可知:Kubernetes 代码通过 Go workspace 和 module replace 语句使用本目录中的仓库

例如当 Kubernetes 代码导入 k8s.io/client-go 的包时,该导入会被解析到相对项目根目录的 staging/src/k8s.io/client-go

// pkg/example/some_code.go
package example

import (
	"k8s.io/client-go/dynamic" // 解析到 staging/src/k8s.io/client-go/dynamic
)

这种"导入路径在外、代码实体在内"的效果,由两层 Go 机制共同保证:

第一层:根 go.mod 的 replace 块

仓库根目录 go.mod 中包含一段集中声明的 replace 块,把每一个 k8s.io/* 导入路径改写为仓库内相对目录:

replace (
	k8s.io/api => ./staging/src/k8s.io/api
	k8s.io/apimachinery => ./staging/src/k8s.io/apimachinery
	k8s.io/apiserver => ./staging/src/k8s.io/apiserver
	k8s.io/client-go => ./staging/src/k8s.io/client-go
	k8s.io/kubectl => ./staging/src/k8s.io/kubectl
	k8s.io/kubelet => ./staging/src/k8s.io/kubelet
	// ...(共 33 个条目)
)

第二层:根 go.work 的 use 块

根目录 go.work 通过 workspace 模式把主模块与所有 staging 模块组合为统一构建单元:

go 1.26.0

use (
	.
	./staging/src/k8s.io/api
	./staging/src/k8s.io/apimachinery
	./staging/src/k8s.io/apiserver
	./staging/src/k8s.io/client-go
	// ...
	./staging/src/k8s.io/streaming
)

第三层:每个 staging 模块自身的 go.mod

每个 staging/src/k8s.io/<repo> 又是一个独立的 Go module,其内部对兄弟模块的依赖同样以相对路径 replace 指向相邻目录。以 staging/src/k8s.io/client-go/go.mod 为例:

replace (
	k8s.io/api => ../api
	k8s.io/apimachinery => ../apimachinery
	k8s.io/streaming => ../streaming
)

因此无论在主仓库代码、staging 模块内部,还是在 k8s.io/kubernetes 主模块的导入关系中,跨模块引用都不会真的去下载远端版本,而是落在本地同一份代码上。

可以从真实调用链验证这一点:kube-apiserver 的组装代码 cmd/kube-apiserver/app/server.go 直接导入了 k8s.io/client-go/rest,在根模块的 Go 编译视角下,它实际指向的就是 staging/src/k8s.io/client-go/rest

工程上有一条铁律:根 go.modgo.work 以及各 staging 模块的 go.mod/go.sum 都是生成文件,头部明确写着 "This is a generated file. Do not edit directly."。需要改依赖时不能手工编辑,而要运行 hack/update-vendor.sh(README 第 7 步也如此强调);仓库根目录 AGENTS.md 同样注明应使用 hack/pin-dependency.shhack/update-vendor.sh,禁止直接 go mod tidy

依赖方向的治理:为什么 api 不能 import kubelet

staging 的目录本质上是多个独立发布的"伪外部模块"。为了防止它们之间形成混乱或反向依赖(例如底层 apimachinery 去依赖高层 kubelet),仓库用两层机制做静态约束:

  1. import-boss 白名单:配置集中在 staging/publishing/import-restrictions.yaml。每个 baseImportPath 都有 allowedImports 白名单。例如底层模块的边界非常清晰:

    • k8s.io/api 只允许导入 k8s.io/apik8s.io/apimachineryk8s.io/klog 等少数包;
    • ./staging/src/k8s.io/apimachinery 只允许 k8s.io/kube-openapik8s.io/streamingk8s.io/utils/* 等;
    • cli-runtime 等模块还通过 ignoredSubTrees 排除例外子树,并注明 # TODO 等待收紧的临时放宽项(例如 pkg/apis 中标注"temporary and should go away"的条目)。

    该白名单由 import-boss 工具(入口见 cmd/import-boss)在代码生成与 CI 校验中强制执行,具体由 hack/verify-codegen.sh 等脚本触发。

  2. vendor 循环依赖检查hack/verify-no-vendor-cycles.sh 会先通过 kube::util::list_staging_repos 枚举全部 staging 模块,再用 go mod graph 检查"非主模块/非 staging 模块"是否反向依赖了主模块或 staging 模块,保证 vendor/ 中不存在对 k8s.io/kubernetes 或 staging 的环。

从源码结构看,这套"白名单 + 分层 + 单向依赖"的设计,保证了每个被拆分的 k8s.io/* 仓库即使脱离主仓库单独编译、单独发布,其依赖关系也始终成立、不会产生回环。

从 staging 到正式仓库:publishing-bot 与 rules.yaml

staging 目录中的代码"会周期性发布到各自顶层的 k8s.io 仓库",承担这一工作的是 Kubernetes 的 publishing-bot(发布机器人)。机器人读取的规则正是 staging/publishing/rules.yaml(本快照共 2568 行)。一个典型规则块由以下字段构成:

  • destination:发布目标仓库名,如 apimachineryclient-go
  • branches[].name:目标仓库的分支名,通常包含 master 及每个发布分支(release-1.34release-1.37);
  • branches[].source.branch / source.dirs:从主仓库的哪个分支、哪些目录取代码(取源目录通常即 staging/src/k8s.io/<name>);
  • branches[].dependencies:该分支发布时依赖的其他 staging 仓库及其分支,保证依赖版本一致;
  • library:是否以库的方式发布;
  • required-packages:如 k8s.io/code-generator(sample 类项目需要);
  • smoke-test:发布后的冒烟命令。

以 client-go 的一个分支为例(取自 staging/publishing/rules.yaml):

- destination: client-go
  branches:
  - name: master
    dependencies:
    - repository: apimachinery
      branch: master
    - repository: streaming
      branch: master
    - repository: api
      branch: master
    source:
      branch: master
      dirs:
      - staging/src/k8s.io/client-go
    smoke-test: |
      # assumes GO111MODULE=on
      go build -mod=mod ./...
      go test -mod=mod ./...

可见发布过程是"精确到分支"的:master 对应 master,每个 release-x.y 分支也各有独立的 source/dependencies 映射,从而保证发布出去的独立仓库与主仓库发布分支保持代码同步。

如何新增一个 staging 仓库(完整操作流程)

staging/README.md 后半部分给出了新增仓库的两阶段流程。由于这是面向项目维护者(Kubernetes 社区)的规范,此处完整保留并辅以仓库内可查证的文件依据。

阶段一:在 kubernetes/kubernetes 主仓库中加入 staging 模块

  1. 先获得社区批准:向 SIG Architecture 邮件列表、以及将拥有该仓库的 SIG 邮件列表发送申请,请求批准创建 staging 仓库(属于社区治理流程,需在提交前完成)。

  2. 创建新的 staging 仓库目录(即 staging/src/k8s.io/<新仓库名>/),得到批准后开始搭建。

  3. 登记依赖白名单:更新 staging/publishing/import-restrictions.yaml,把该新仓库允许导入的其他 staging 仓库加入清单——这是 import-boss 校验的数据源。

  4. 补齐社区模板文件:按照 Kubernetes 社区模板项目(kubernetes-template-project)的要求,添加全部必备模板文件。

  5. 声明不收 PR:确保 .github/PULL_REQUEST_TEMPLATE.mdCONTRIBUTING.md 中明确说明"不直接接受该仓库的 PR"——因为代码的权威来源在主仓库 staging 目录,PR 应当提交到主仓库,而非独立仓库。该约定会被 hack/verify-staging-meta-files.sh 强制检查,该脚本要求每个 staging/src/k8s.io/* 目录必须存在以下元数据文件:

    .github/PULL_REQUEST_TEMPLATE.md
    code-of-conduct.md
    LICENSE
    OWNERS
    README.md
    SECURITY_CONTACTS
    

    (例外仅有一个:client-go 用自己的 README 提供专门说明,故豁免 README.md 检查。)

  6. 添加 doc.go:仓库必须提供包级文档文件 doc.go。可以参照 staging/src/k8s.io/client-go/doc.go 的写法——它用大段注释说明"Package clientgo is the official Go client for the Kubernetes API",并分节介绍 kubernetes(类型化 Clientset)、dynamic(动态客户端)、tools/cache(Informer/Lister 控制器底座)、rest.InClusterConfig/clientcmd 两种连接方式等。doc.go 既是规范要求,也是对外文档生成的输入。

  7. 严禁手工编辑 go.mod/go.sum:新模块目录(staging/src/k8s.io/<新仓库名>/)下的 go.modgo.sum 不要手工改动,而是运行:

    ./hack/update-vendor.sh
    

    该脚本会统一完成:为模块注入 replace 指令、执行 go work use ./staging/src/k8s.io/<repo> 把模块加入 workspace、处理版本对齐(相关实现位于 hack/update-vendor.shadd_staging_replace_directives 等函数中)。

阶段二:在 Kubernetes org 创建并启用对外发布仓库

  1. 在 kubernetes/org 仓库提 issue 申请建仓:请求创建对应的公开仓库。该发布仓库必须包含一个初始空提交(initial empty commit),并且需要配置特定的访问规则与分支保护设置。
  2. 配置 CI 与权限:在 test-infra 的 prow 配置中为仓库开启分支保护,并允许 stage-bots 团队访问——stage-bots 是 publishing-bot 用以推送代码的机器人账号。
  3. 接入 publishing-bot:发布仓库创建完成后,更新两处配置使机器人开始发布:
    • staging/publishing/rules.yaml:为该仓库新增 destination 规则块,并确保其 dependencies 列表与生成阶段记录的 Godeps.json 一致;
    • publishing-bot 仓库的 hack/repos.sh:把该 staging 仓库加入待发布仓库列表。
  4. 登记到社区 SIG 组织表:在社区 sigs.yaml 中,把 staging 仓库与发布仓库同时登记为所属 SIG 的子项目。
  5. 更新本 README:把新仓库加入 staging/README.md 的"Repositories currently staged here"清单,使文档与实际模块保持一致。

小结:理解 staging 的几个关键视角

staging/README.md 与仓库实际结构对照后,可以对 staging 机制形成如下判断:

  • 单份权威代码:每个 k8s.io/* 模块只有 staging 内一份源码,对外仓库是它的"发布镜像",避免了同一逻辑在主仓库与外部仓库出现两份不同实现;
  • 开发期零发布延迟:主仓库通过根 go.modreplace 与根 go.workuse,把模块直接指回本地目录,改动即时生效;每个 staging 模块自身的 go.mod 再用 ../xxx 关联兄弟模块;
  • 发布期自动化staging/publishing/rules.yaml 精确描述"哪个仓库、哪个分支、依赖哪些兄弟仓库",publishing-bot 据此把代码同步到 k8s.io/* 顶层仓库,并支持冒烟测试与分支级依赖对齐;
  • 治理靠静态校验:白名单(staging/publishing/import-restrictions.yaml)、元数据文件检查(hack/verify-staging-meta-files.sh)与循环依赖检查(hack/verify-no-vendor-cycles.sh)共同守住模块边界。

对于希望阅读 Kubernetes 源码或二次开发的工程师,staging/ 的价值在于:当你看到形如 k8s.io/client-go/...k8s.io/apimachinery/... 的导入时,可以直接在当前仓库的 staging/src/k8s.io/ 对应目录中跳转阅读与调试,而不必猜测依赖来自哪个外部版本。这也是 Kubernetes 主仓库在 Go module 时代保持"自包含、可复现构建"的核心工程手段。

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