etcd × Kubernetes 版本升级实战:在 kubernetes/kubernetes 中 Bump etcd 的完整流程
本文基于 etcd 官方贡献者指南 bump_etcd_version_k8s.md,完整讲清在 Kubernetes 仓库中升级 etcd 依赖的全部步骤:先升级 Go 客户端 SDK(vendor 层面),再升级 etcd 镜像(发布与引用两个仓库联动)。读完后,你将掌握 K8s 版本与 etcd 版本线的对应规则、pin-dependency.sh / update-vendor.sh 等关键脚本的用法,以及 7 个需要修改的 K8s 仓库文件的精确位置和示例值,可以直接照单执行一次真实的 etcd 版本升级。
1. 背景:Kubernetes 为什么要定期 Bump etcd
etcd 是 Kubernetes 的默认存储后端,负责保存集群中所有 API 对象的持久化状态。随着 etcd 社区持续修复 bug 和安全漏洞,Kubernetes 需要周期性地把依赖的 etcd 版本"bump"(提升)到新的补丁版本,以获取这些修复。
官方指南明确给出了当前的版本对应策略(引自 bump_etcd_version_k8s.md):
目前,K8s release-1.33 及更低的版本使用 etcd v3.5.x 线,K8s release-1.34 及更高的版本使用 etcd v3.6.x 线。
这个策略与 etcd 仓库自身的发布节奏一致:在本仓库中,CHANGELOG-3.5.md 与 CHANGELOG-3.6.md 都在持续滚动发布补丁版本(两条维护线几乎同步修复同一批问题,例如 peer lease HTTP handler 的无界 io.ReadAll 修复、watch 权限越界读取等安全修复同时出现在 3.5 与 3.6 两个 CHANGELOG 中)。因此 K8s 社区可以为不同 release 分支锁定各自对应的 etcd 版本线。
整个升级动作可以概括为两个相互独立、先后执行的阶段:
- Bump etcd client SDK —— 升级 kubernetes/kubernetes 通过 Go module 依赖的 etcd 客户端库;
- Bump etcd image —— 升级 K8s 各测试/部署场景实际运行的 etcd 容器镜像。
指南原文约定:文档示例中"被注释掉的行"表示升级前的旧值,"未注释的行"表示要改成/新增的新值。下文的示例均按此约定保留。
2. 阶段一:Bump etcd client SDK
etcd 客户端以 Go module 形式被 Kubernetes 依赖,升级它本质上是一次标准的 Go 依赖升级 + vendor 重构建。指南中引用的操作脚本全部位于 kubernetes/kubernetes 仓库内(下文所有 hack/...、cmd/...、cluster/... 等路径均指该仓库,不是 etcd 仓库的路径)。
2.1 找出 Kubernetes 用到的全部 etcd 模块
在 kubernetes/kubernetes 根目录执行:
$ grep 'go.etcd.io/etcd/' go.mod | awk '{print $1}'
go.etcd.io/etcd/api/v3
go.etcd.io/etcd/client/pkg/v3
go.etcd.io/etcd/client/v3
go.etcd.io/etcd/client/v2
go.etcd.io/etcd/pkg/v3
go.etcd.io/etcd/raft/v3
go.etcd.io/etcd/server/v3
etcd 采用多模块(multi-module)仓库结构:每个子目录(api/、client/v3/、server/ 等)各自是一个独立的 Go module,版本号独立演进。这一点可以从 etcd 仓库本身的 go.work 中直接印证,它通过 use 指令列出了 ./api、./client/pkg、./client/v3、./pkg、./server、./etcdctl、./etcdutl、./tests 等所有参与本地开发的模块;各模块的 go.mod 声明也与之对应,例如 client/v3/go.mod 中:
module go.etcd.io/etcd/client/v3
而从当前 etcd 源码结构看,模块组成会随版本演进:本仓库主线(api/version/version.go 中 Version = "3.8.0-alpha.0")的 server/go.mod 只直接依赖 api/v3、client/pkg/v3、client/v3、pkg/v3 四个模块,并通过 replace 指令把它们指向相对路径(../api、../client/pkg 等)以便本地开发;而 Kubernetes 在 3.5/3.6 时代 pin 的模块清单(含 client/v2、raft/v3)与主线模块清单存在差异。因此执行 2.1 的 grep 拿到"当前 go.mod 实际引用的模块清单"这一步是必须的,不能照抄历史 PR 的清单。
2.2 逐个 pin 新版本
对 2.1 列出的每个模块,在 kubernetes/kubernetes 仓库根目录执行 pin 脚本(以 client/v3 为例,NEW_VERSION 替换为目标版本,如 v3.5.21):
hack/pin-dependency.sh go.etcd.io/etcd/client/v3 NEW_VERSION
pin-dependency.sh 负责把目标模块在 go.mod 中锁定到指定版本。这一步遵循 Kubernetes 社区 vendor 指南中"Adding or updating a dependency"章节的规范流程。
2.3 重建 vendor 目录
hack/update-vendor.sh
该脚本会重新同步整个 vendor/ 目录,并自动更新 staging 仓库中所有 go.mod 文件以及许可证声明,不需要手工维护。
2.4 校验依赖一致性
新版本可能要求现有已 pin 依赖的更高版本,指南给出两个校验手段:
- 分别在升级分支和
master上运行hack/lint-dependencies.sh,对比两者的输出差异; - 检查
staging/目录下各组件的go.mod是否新增了replace指令——新增replace往往是依赖约束冲突的信号,需要人工确认。
在 etcd 仓库侧可以对照模块间约束来理解这一步的必要性:server/go.mod 中 require 块里的 go.etcd.io/etcd/api/v3 v3.8.0-alpha.0 等条目声明了对兄弟模块的最低版本要求,K8s 升级任何一个 etcd 模块时,其余 etcd 模块的版本必须满足这类约束,这正是 2.1 要"整组"升级、而非单独升一个模块的原因。
3. 阶段二:Bump etcd image
3.1 构建镜像:使用官方发布镜像,无需自建
指南明确指出:Kubernetes 现在直接使用 etcd 官方发布的镜像,不再需要为 K8s 额外构建 etcd 镜像。etcd 官方镜像的构建逻辑可以直接在本仓库的 Dockerfile 中查看:基于 distroless static 基础镜像,仅拷贝 etcd、etcdctl、etcdutl 三个二进制,暴露 2379/2380 端口,默认命令为 /usr/local/bin/etcd。官方发布流程(打 tag、发布镜像与 CHANGELOG)详见 release.md,其中还规定了补丁版本的发布门槛(例如修复了高危 CVE、关键 bug 或累计足够数量的 bug 修复)——K8s 社区 bump 的目标版本通常就是满足这些条件后发布的最新补丁版本。
3.2 发布镜像:在 k8s.io 仓库登记 SHA256 digest
镜像进入 K8s 官方 registry 的路径(参考 k8s.io 仓库的镜像 staging PR 流程):
- 上一步(SDK bump 或镜像构建 job)合并后,post-commit job 会自动构建镜像,新镜像出现在 staging registry(
gcr.io/k8s-staging-etcd/etcd)中; - 找到新构建的镜像,复制其 SHA256 digest;
- 在
kubernetes/k8s.io仓库的registry.k8s.io/images/k8s-staging-etcd/images.yaml中,为期望的版本新增一条记录,写入该 digest:
"sha256:b4a9e4a7e1cf08844c7c4db6a19cab380fbf0aad702b8c01e578e9543671b9f9": ["3.5.17-0"]
# ADD:
"sha256:d58c035df557080a27387d687092e3fc2b64c6d0e3162dc51453a115f847d121": ["3.5.21-0"]
注意 image tag 的格式是 3.5.21-0:-0 是 K8s staging registry 的镜像构建序号,与 etcd 本身的 semver(3.5.21)不同。
3.3 更新 kubernetes/kubernetes 仓库引用新镜像(7 处)
镜像发布后,需要在 kubernetes/kubernetes 仓库中更新所有硬编码的 etcd 版本引用。指南列出的完整清单如下,每处都给出"旧值(注释)→ 新值"的示例:
① build/dependencies.yaml —— 修改 etcd 条目的 version,这是 K8s 依赖管理的单一事实来源,refPaths 声明了版本号需要同步的文件与匹配字段:
# etcd
- name: "etcd"
# version: 3.5.17
version: 3.5.21
refPaths:
- path: cluster/gce/manifests/etcd.manifest
match: etcd_docker_tag|etcd_version
② cluster/gce/manifests/etcd.manifest —— GCE 测试集群的 etcd manifest,同时更新 image tag 和 TARGET_VERSION:
// "image": "{{ pillar.get('etcd_docker_repository', 'registry.k8s.io/etcd') }}:{{ pillar.get('etcd_docker_tag', '3.5.17-0') }}",
"image": "{{ pillar.get('etcd_docker_repository', 'registry.k8s.io/etcd') }}:{{ pillar.get('etcd_docker_tag', '3.5.21-0') }}",
---
{ "name": "TARGET_VERSION",
// "value": "{{ pillar.get('etcd_version', '3.5.17') }}"
"value": "{{ pillar.get('etcd_version', '3.5.21') }}"
},
③ cluster/gce/upgrade-aliases.sh —— GCE 升级测试脚本中的镜像与版本变量:
# export ETCD_IMAGE=3.5.17-0
export ETCD_IMAGE=3.5.21-0
# export ETCD_VERSION=3.5.17
export ETCD_VERSION=3.5.21
④ cmd/kubeadm/app/constants/constants.go —— 这是影响最终用户最直接的改动:DefaultEtcdVersion 决定 kubeadm 拉取哪个 etcd 镜像,SupportedEtcdVersion 则声明各 K8s minor 版本支持的 etcd 镜像 tag。注意 map 的 key 是 K8s minor 版本号(30~33 对应 K8s 1.30~1.33),value 是带 -0 的镜像 tag:
// DefaultEtcdVersion = "3.5.17-0"
DefaultEtcdVersion = "3.5.21-0"
---
SupportedEtcdVersion = map[uint8]string{
// 30: "3.5.17-0",
// 31: "3.5.17-0",
// 32: "3.5.17-0",
// 33: "3.5.17-0",
30: "3.5.21-0",
31: "3.5.21-0",
32: "3.5.21-0",
33: "3.5.21-0",
}
⑤ hack/lib/etcd.sh —— 本地测试默认使用的 etcd 版本:
# ETCD_VERSION=${ETCD_VERSION:-3.5.17}
ETCD_VERSION=${ETCD_VERSION:-3.5.21}
⑥ staging/src/k8s.io/sample-apiserver/artifacts/example/deployment.yaml —— sample-apiserver 示例部署中使用的 etcd 镜像:
- name: etcd
# image: gcr.io/etcd-development/etcd:v3.5.17
image: gcr.io/etcd-development/etcd:v3.5.21
⑦ test/utils/image/manifest.go —— 测试框架的镜像清单,更新 etcd 条目:
// configs[Etcd] = Config{list.GcEtcdRegistry, "etcd", "3.5.17-0"}
configs[Etcd] = Config{list.GcEtcdRegistry, "etcd", "3.5.21-0"}
3.4 两个容易踩的坑
- 版本号与镜像 tag 混用:
3.5.21(etcd semver,用于dependencies.yaml、ETCD_VERSION、TARGET_VERSION)与3.5.21-0(registry 镜像 tag,用于 manifest、kubeadm 常量、image manifest)是两套值,上表 7 处改动中每处该写哪个值都有固定规则,照抄示例即可。 - SDK 与镜像版本要分开跟踪:阶段一改的是编译期依赖(Go module 版本,形如
v3.5.21),阶段二改的是运行期容器镜像(tag 形如3.5.21-0),两者的目标 etcd 版本号应一致,但提交是独立的(指南也分别引用了独立的 SDK bump PR、镜像 staging PR 和镜像更新 PR 作为参考)。
4. 版本兼容性的底层机制(源码视角)
为什么 K8s 可以放心地"只 bump 补丁版本"而不做大规模适配?etcd 源码中有明确的集群版本协商机制可以佐证:
- api/version/version.go 集中定义版本常量:
Version(当前二进制版本)、MinClusterVersion = "3.0.0"(本二进制可兼容的最低集群版本),并以semver库构造V3_5、V3_6等版本对象供各处比较使用; - 成员间通信时,raft 传输层会在每个 POST 请求头中携带版本信息,见 server/etcdserver/api/rafthttp/util.go:
X-Server-Version与X-Min-Cluster-Version;对端响应412 Precondition Failed并返回incompatible version时即判定版本不兼容(同文件的checkPostResponse)。同目录下 pipeline_test.go 中的测试断言了请求头必须携带MinClusterVersion。
从源码结构看,只要 K8s 在同一个 minor 版本线内 bump(3.5.x → 3.5.x,3.6.x → 3.6.x),二进制间兼容性由上述协商机制保证,K8s 侧无需修改 API 调用代码——这也是指南把升级限定为"补丁版本滚动"的原因。
5. 执行清单与参考文件速查
一次完整的 etcd bump 按以下顺序执行:
| 步骤 | 操作 | 所在仓库 |
|---|---|---|
| 1 | grep 'go.etcd.io/etcd/' go.mod 列出全部 etcd 模块 |
kubernetes/kubernetes |
| 2 | 逐模块 hack/pin-dependency.sh <module> NEW_VERSION |
kubernetes/kubernetes |
| 3 | hack/update-vendor.sh 重建 vendor |
kubernetes/kubernetes |
| 4 | 对比 hack/lint-dependencies.sh 输出,检查 staging go.mod 的 replace |
kubernetes/kubernetes |
| 5 | 确认 post-commit job 构建的新镜像,复制 SHA256 digest | k8s-staging registry |
| 6 | 在 images.yaml 登记 digest → 新版本映射 |
kubernetes/k8s.io |
| 7 | 更新 build/dependencies.yaml、etcd.manifest、upgrade-aliases.sh、kubeadm 常量、hack/lib/etcd.sh、sample-apiserver 部署、test/utils/image/manifest.go 共 7 处引用 |
kubernetes/kubernetes |
etcd 仓库侧可用于对照的参考文件:
- Documentation/contributor-guide/bump_etcd_version_k8s.md —— 本文所依据的官方指南原文;
- CHANGELOG/CHANGELOG-3.5.md / CHANGELOG/CHANGELOG-3.6.md —— 选择 bump 目标补丁版本时的修复内容依据;
- api/version/version.go —— 版本常量与 semver 版本对象定义;
- server/go.mod、go.work —— 多模块依赖结构与版本约束;
- Dockerfile —— 官方 etcd 镜像的构建方式;
- Documentation/contributor-guide/release.md —— etcd 发布流程与补丁版本发布门槛。
遵循上述流程后,Kubernetes 侧的 etcd SDK 与镜像将同步滚动到目标补丁版本,且每一步都有明确的脚本或文件锚点,可复现、可审查。
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 StartedRust0623
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