Kubernetes 每年发布约三个小版本,每个版本的支持周期大约一年。这意味着你要么跟上节奏定期升级,要么冒险运行一个不再接收安全补丁的集群。现实是,很多团队每次升级都要花数周甚至数月——读 changelog、排查 API 废弃、逐个节点滚动、验证工作负载……升级本身成了工程时间的黑洞。
问题不在于 Kubernetes 太复杂,而在于我们把升级当成了"一次性大事件",而不是一条可重复的流水线。下面是几条把升级时间抢回来的具体策略。
先搞清楚你落后了多少
升级的第一步不是下载新版本,而是量化现状。你需要知道:当前版本距离最新稳定版差几步?中间有哪些 API 已经被移除?
# 查看集群当前版本
kubectl version --short
# 列出所有已废弃但仍在使用的 API 版本(1.22+ 可用)
kubectl get --raw /metrics | grep deprecated_api_version_request_count
# 更直接的方式:用 kubectl explain 检查资源是否仍支持旧 API
kubectl explain deployment.apiVersion
如果你的集群还在跑 1.26,而上游已经到 1.30,中间隔着四个版本——每个版本都有自己的废弃列表。不要跳版本升级,逐个走完,但每一步要尽量自动化。
用 Sonobuoy 把"手动验证"变成一条命令
升级后最耗时间的环节不是升级本身,而是验证"我的工作负载还正常吗"。手动检查几十个服务、看日志、跑集成测试,一轮下来一两天就没了。
Sonobuoy 是 CNCF 的一致性测试工具,但它同样适合做升级后的快速健康检查:
# 安装 sonobuoy
curl -L https://github.com/vmware-tanzu/sonobuoy/releases/download/v0.57.2/sonobuoy_0.57.2_linux_amd64.tar.gz | tar xz
sudo mv sonobuoy /usr/local/bin/
# 对集群跑快速模式(只跑 E2E conformance 的快速子集,约 10-20 分钟)
sonobuoy run --mode quick --wait
# 查看结果
sonobuoy results $(sonobuoy retrieve)
# 清理测试资源
sonobuoy delete
把这条命令嵌入升级流程,你至少省掉了"人肉翻日志确认集群没坏"的半天时间。如果快速模式全过,再跑一轮你的应用级集成测试,信心就很高了。
把升级过程写成可复用的脚本
手动升级最大的问题是不可重复——这次升级的步骤和上次不一样,下次又要重新想。把步骤固化成脚本,升级就从"研究+操作"变成了"执行+观察"。
以下是一个针对 kubeadm 管理的集群升级控制平面的脚本骨架:
#!/usr/bin/env bash
# upgrade-control-plane.sh — 升级 kubeadm 集群控制平面
# 用法: ./upgrade-control-plane.sh <目标版本>,例如 1.30.1-1
set -euo pipefail
TARGET_VERSION="${1:?请指定目标 K8s 版本,如 1.30.1-1}"
KUBEADM_PKG="kubeadm-${TARGET_VERSION}"
KUBELET_PKG="kubelet-${TARGET_VERSION}"
KCTL_PKG="kubectl-${TARGET_VERSION}"
echo "=== 升级目标: v${TARGET_VERSION} ==="
# 1. 升级 kubeadm 本身
echo "[1/4] 安装新版本 kubeadm..."
apt-get update && apt-get install -y "${KUBEADM_PKG}"
# 2. 升级计划预检
echo "[2/4] 预检升级计划..."
kubeadm upgrade plan v${TARGET_VERSION%-*}
# 3. 执行控制平面升级
echo "[3/4] 应用升级..."
kubeadm upgrade apply v${TARGET_VERSION%-*} -y
# 4. 升级 kubelet 和 kubectl
echo "[4/4] 升级 kubelet & kubectl..."
apt-get install -y "${KUBELET_PKG}" "${KCTL_PKG}"
systemctl restart kubelet
echo "=== 控制平面升级完成,等待节点 Ready ==="
kubectl wait --for=condition=Ready node/"$(hostname)" --timeout=120s
echo "=== Done ==="
工作节点升级更简单,可以并行推到多个节点:
#!/usr/bin/env bash
# upgrade-worker.sh — 升级单个工作节点
set -euo pipefail
TARGET_VERSION="${1:?请指定目标版本}"
# 在控制平面执行:标记节点不可调度,驱逐工作负载
NODE_NAME="$(hostname)"
kubectl cordon "${NODE_NAME}"
kubectl drain "${NODE_NAME}" --ignore-daemonsets --delete-emptydir-data --timeout=120s
# 升级软件包
apt-get update && apt-get install -y \
"kubeadm-${TARGET_VERSION}" \
"kubelet-${TARGET_VERSION}" \
"kubectl-${TARGET_VERSION}"
kubeadm upgrade node
systemctl restart kubelet
# 恢复调度
kubectl uncordon "${NODE_NAME}"
把这些脚本放在 Git 仓库里,每次升级只需改版本号参数。升级从"翻文档+手动敲命令"变成 ./upgrade-control-plane.sh 1.30.1-1。
用 Cluster API 让升级变成声明式操作
如果你的集群是用 Cluster API 管理的,升级甚至不需要脚本——改一行 YAML 就行:
# 原来的 MachineDeployment
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
name: my-cluster-md-0
spec:
template:
spec:
bootstrap:
configRef:
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
name: my-cluster-md-0
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AWSMachineTemplate
name: my-cluster-md-0
version: "v1.29.3" # ← 改这里
把 version 从 v1.29.3 改成 v1.30.1,Cluster API 会自动滚动替换节点。旧节点逐个驱逐、销毁,新节点用新版本创建,整个过程和你更新 Deployment 的 rollingUpdate 一样可控。
如果你还在手动管理集群,Cluster API 的迁移成本不小。但如果你已经管理了五个以上的集群,这笔投入会在后续每次升级中收回。
用 managed Kubernetes 把升级推给平台方
EKS、GKE、AKS 这些托管服务把控制平面升级从你的责任清单里移走了。你只需要处理工作负载兼容性。
GKE 的自动升级策略可以直接在集群配置里声明:
# GKE cluster autoscaling + upgrade policy(Terraform 示例)
resource "google_container_cluster" "primary" {
name = "my-cluster"
location = "us-central1"
# 让 GKE 自动升级控制平面,窗口设在周末凌晨
maintenance_policy {
recurring_window {
start = "2024-01-01T02:00:00Z"
end = "2024-01-01T06:00:00Z"
recurrence = "FREQ=WEEKLY;BYDAY=SA"
}
}
# 节点自动升级
node_pool_auto_config {
network_tags {
tags = ["auto-upgrade"]
}
}
}
托管方案省掉了控制平面升级的全部时间,代价是灵活度降低(你不能自定义控制平面组件的参数)和版本可用性滞后(EKS 通常比上游晚 2-3 个月)。对于大多数业务团队,这个交换是划算的。
升级前的 API 废弃扫描
每次升级最隐蔽的坑是 API 废弃。你的 Helm chart 或 YAML 里还在用 v1beta1,新版本已经不认了——升级完直接报错。
在升级前跑一轮扫描:
# 用 pluto 扫描集群和 Git 仓库中的废弃 API
# 安装
curl -L https://github.com/FairwindsOps/pluto/releases/download/v5.18.4/pluto_5.18.4_linux_amd64.tar.gz | tar xz
sudo mv pluto /usr/local/bin/
# 扫描集群中正在运行的废弃 API 资源
pluto detect-helm -o wide
# 扫描本地 Git 仓库中的 YAML 文件
pluto detect-files -d ./k8s-manifests/ -o wide
pluto 会告诉你哪些资源用了即将被移除的 API,以及这些 API 在哪个版本被删除。提前改完,升级当天就不会踩坑。
把升级时间抢回来的清单
| 做法 | 省掉的时间 | 适用场景 |
|---|---|---|
| Sonobuoy 快速验证 | 半天~一天的手动检查 | 所有集群 |
| 升级脚本固化 | 每次省 1-2 天的"重新研究步骤" | kubeadm 集群 |
| Cluster API 声明式升级 | 几乎零操作时间 | 多集群环境 |
| 托管服务自动升级 | 控制平面升级时间归零 | 业务集群(不需要深度定制) |
| pluto 废弃 API 扫描 | 升级后排查 API 报错的数小时 | 所有集群,每次升级前必跑 |
核心思路只有一个:把升级从事件变成流程。能自动化的不要手动,能声明的不要脚本,能交给平台的不要自己扛。每次升级省下来的工程时间,才是真正回到产品开发里的时间。