深入解析 Kubernetes 的 staging 目录:单仓库开发与 k8s.io 多仓库发布的桥接架构
staging/ 是 Kubernetes 主仓库中一块特殊的中转区:它以"目录内的 Go 模块"形式,容纳了 30 多个将被拆分到独立 k8s.io/* 顶层仓库(如 client-go、api、apimachinery)的代码,并让主仓库在编译期直接引用这些本地模块,而不是引用发布到远端的外部依赖。阅读本文后,你将理解 staging/ 目录的结构与运作原理、Go workspace + replace 的引用机制、staging 到独立仓库的自动发布链路,以及如何按社区规范向 staging 新增一个仓库。
关联文档为仓库根下的 staging/README.md,文中所有源码证据均来自当前仓库快照。
staging 是什么:外部仓库的"代码中转站"
根据 staging/README.md 的定义,staging/ 目录是已被拆分为独立仓库的软件包的中转区(staging area)。这些包会周期性发布(publish)到 Kubernetes org 下各自的顶层仓库,例如 k8s.io/api、k8s.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 类型与工具链 | api、apimachinery、code-generator |
| API Server 体系 | apiserver、apiextensions-apiserver、kube-aggregator、sample-apiserver |
| 客户端与 CLI | client-go、cli-runtime、kubectl、sample-cli-plugin |
| 控制面组件 | kube-controller-manager、controller-manager、component-base、component-helpers |
| 节点与运行组件 | kubelet、kube-proxy、kube-scheduler、cri-api、cri-client、cri-streaming |
| 云与存储 | cloud-provider、csi-translation-lib、mount-utils |
| 资源与扩展 | dynamic-resource-allocation、endpointslice、metrics、cluster-bootstrap |
| 安全 | externaljwt、kms、pod-security-admission |
| 示例项目 | sample-controller |
每个名称均对应真实目录,例如 staging/src/k8s.io/client-go、staging/src/k8s.io/apimachinery 等。此外,本快照的 go.work 与 staging/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.mod、go.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.sh加hack/update-vendor.sh,禁止直接go mod tidy。
依赖方向的治理:为什么 api 不能 import kubelet
staging 的目录本质上是多个独立发布的"伪外部模块"。为了防止它们之间形成混乱或反向依赖(例如底层 apimachinery 去依赖高层 kubelet),仓库用两层机制做静态约束:
-
import-boss 白名单:配置集中在 staging/publishing/import-restrictions.yaml。每个
baseImportPath都有allowedImports白名单。例如底层模块的边界非常清晰:k8s.io/api只允许导入k8s.io/api、k8s.io/apimachinery、k8s.io/klog等少数包;./staging/src/k8s.io/apimachinery只允许k8s.io/kube-openapi、k8s.io/streaming、k8s.io/utils/*等;cli-runtime等模块还通过ignoredSubTrees排除例外子树,并注明# TODO等待收紧的临时放宽项(例如pkg/apis中标注"temporary and should go away"的条目)。
该白名单由 import-boss 工具(入口见 cmd/import-boss)在代码生成与 CI 校验中强制执行,具体由 hack/verify-codegen.sh 等脚本触发。
-
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:发布目标仓库名,如apimachinery、client-go;branches[].name:目标仓库的分支名,通常包含master及每个发布分支(release-1.34~release-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 模块
-
先获得社区批准:向 SIG Architecture 邮件列表、以及将拥有该仓库的 SIG 邮件列表发送申请,请求批准创建 staging 仓库(属于社区治理流程,需在提交前完成)。
-
创建新的 staging 仓库目录(即
staging/src/k8s.io/<新仓库名>/),得到批准后开始搭建。 -
登记依赖白名单:更新 staging/publishing/import-restrictions.yaml,把该新仓库允许导入的其他 staging 仓库加入清单——这是 import-boss 校验的数据源。
-
补齐社区模板文件:按照 Kubernetes 社区模板项目(kubernetes-template-project)的要求,添加全部必备模板文件。
-
声明不收 PR:确保
.github/PULL_REQUEST_TEMPLATE.md与CONTRIBUTING.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检查。) -
添加
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 既是规范要求,也是对外文档生成的输入。 -
严禁手工编辑 go.mod/go.sum:新模块目录(
staging/src/k8s.io/<新仓库名>/)下的go.mod、go.sum不要手工改动,而是运行:./hack/update-vendor.sh该脚本会统一完成:为模块注入
replace指令、执行go work use ./staging/src/k8s.io/<repo>把模块加入 workspace、处理版本对齐(相关实现位于 hack/update-vendor.sh 的add_staging_replace_directives等函数中)。
阶段二:在 Kubernetes org 创建并启用对外发布仓库
- 在 kubernetes/org 仓库提 issue 申请建仓:请求创建对应的公开仓库。该发布仓库必须包含一个初始空提交(initial empty commit),并且需要配置特定的访问规则与分支保护设置。
- 配置 CI 与权限:在 test-infra 的 prow 配置中为仓库开启分支保护,并允许
stage-bots团队访问——stage-bots 是 publishing-bot 用以推送代码的机器人账号。 - 接入 publishing-bot:发布仓库创建完成后,更新两处配置使机器人开始发布:
- staging/publishing/rules.yaml:为该仓库新增
destination规则块,并确保其dependencies列表与生成阶段记录的Godeps.json一致; - publishing-bot 仓库的
hack/repos.sh:把该 staging 仓库加入待发布仓库列表。
- staging/publishing/rules.yaml:为该仓库新增
- 登记到社区 SIG 组织表:在社区
sigs.yaml中,把 staging 仓库与发布仓库同时登记为所属 SIG 的子项目。 - 更新本 README:把新仓库加入 staging/README.md 的"Repositories currently staged here"清单,使文档与实际模块保持一致。
小结:理解 staging 的几个关键视角
把 staging/README.md 与仓库实际结构对照后,可以对 staging 机制形成如下判断:
- 单份权威代码:每个
k8s.io/*模块只有 staging 内一份源码,对外仓库是它的"发布镜像",避免了同一逻辑在主仓库与外部仓库出现两份不同实现; - 开发期零发布延迟:主仓库通过根 go.mod 的
replace与根 go.work 的use,把模块直接指回本地目录,改动即时生效;每个 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 时代保持"自包含、可复现构建"的核心工程手段。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00