背景
HomeLab 运行着一个 3 节点的 kubeadm 集群(全部 control-plane),承载了 Harbor、GitLab、WordPress、Longhorn 存储、Calico 网络等约 20 个服务。集群从 2022 年初部署后一直运行在 K8s v1.22.0,containerd v1.4.3,至今已超过 4 年未升级。
考虑到新版本的kubesphere最低支持到1.23,且后续升级需要逐步升级。
K8s 1.23 的关键变更
| 变更 | 影响 |
|---|---|
TTLAfterFinished feature gate 升为 GA |
不允许再通过 --feature-gates 设置 |
IPv6DualStack feature gate 升为 GA |
同上 |
rbac.authorization.k8s.io/v1alpha1 移除 |
我们已用 v1,无影响 |
--experimental-cluster-signing-duration 废弃 |
需改为 --cluster-signing-duration |
kubeadm 升级过程
手动二进制升级流程
由于集群是手动安装(非 apt),升级步骤:
# 1. 下载二进制
curl -L https://dl.k8s.io/release/v1.23.17/bin/linux/amd64/kubeadm -o /root/kubeadm-1.23.17
curl -L https://dl.k8s.io/release/v1.23.17/bin/linux/amd64/kubelet -o /root/kubelet-1.23.17
curl -L https://dl.k8s.io/release/v1.23.17/bin/linux/amd64/kubectl -o /root/kubectl-1.23.17
chmod +x /root/kubeadm-1.23.17 /root/kubelet-1.23.17 /root/kubectl-1.23.17
# 2. 替换 kubeadm
cp /root/kubeadm-1.23.17 /usr/bin/kubeadm
# 3. 执行升级
kubeadm upgrade apply v1.23.17 --ignore-preflight-errors=ImagePull
# 4. 替换 kubelet(需先停止)
systemctl stop kubelet
cp /root/kubelet-1.23.17 /usr/bin/kubelet
cp /root/kubectl-1.23.17 /usr/bin/kubectl
systemctl daemon-reload
systemctl start kubelet
kubeadm 的镜像仓库变更
现象: kubeadm upgrade apply 在 preflight 阶段失败,尝试从 registry.k8s.io 拉取镜像超时。
原因: kubeadm v1.23.17 的发布时间晚于 K8s 镜像仓库从 k8s.gcr.io 迁移到 registry.k8s.io 的时间点,所以 kubeadm 二进制中硬编码了 registry.k8s.io 作为默认仓库。
解决: 预先从阿里云拉取并 tag 为两个地址:
# 从阿里云拉取
crictl pull registry.aliyuncs.com/google_containers/kube-apiserver:v1.23.17
# tag 为 registry.k8s.io(kubeadm 实际拉取的地址)
ctr -n k8s.io images tag \
registry.aliyuncs.com/google_containers/kube-apiserver:v1.23.17 \
registry.k8s.io/kube-apiserver:v1.23.17
# 加 --ignore-preflight-errors=ImagePull 跳过在线拉取检查
kubeadm upgrade apply v1.23.17 --yes --ignore-preflight-errors=ImagePull
问题:kubelet 二进制替换时 “Text file busy”
cp: cannot create regular file '/usr/bin/kubelet': Text file busy
原因: kubelet 进程正在运行,无法覆盖其二进制文件。
解决: 必须先停止 kubelet:
systemctl stop kubelet
cp /root/kubelet-1.23.17 /usr/bin/kubelet
systemctl start kubelet
升级后kube-controller-manager卡CrashLoopBackOff
升级完成后,发现 kube-controller-manager 在所有节点上 CrashLoopBackOff
问题1:已 GA 的 Feature Gate 不可再设置
错误: controller-manager 启动后立即 Fatal 退出(exit code 255)。
根因: manifest 中仍有 --feature-gates=TTLAfterFinished=true。在 K8s 1.23 中,TTLAfterFinished 已升为 GA 并锁定为 true,不允许通过 feature gate 再次显式设置。
# 修复:删除所有 GA feature gates
sed -i '/--feature-gates=TTLAfterFinished=true/d' \
/etc/kubernetes/manifests/kube-controller-manager.yaml \
/etc/kubernetes/manifests/kube-apiserver.yaml \
/etc/kubernetes/manifests/kube-scheduler.yaml
为什么 kubeadm 没有自动清理? 因为 kubeadm 在升级 manifest 时会保留用户自定义的 extraArgs。如果你手动添加了 feature-gates 到 kubeadm-config 的 extraArgs 中,它会原样保留。
问题2:双栈 cluster-cidr 被错误修改
清理 feature gate 后,controller-manager 仍然 Fatal:
error starting controllers: node:masterb has an allocated cidr: fc00::/64
at index:1 that does not exist in cluster cidrs configuration
根因: kubeadm 升级时将 controller-manager 的 --cluster-cidr 从双栈 100.64.0.0/10,fc00::/48 修改为仅 IPv4 的 100.64.0.0/10。但节点已经分配了 IPv6 CIDR,造成不一致。
# 修复:恢复双栈配置
sed -i 's|--cluster-cidr=100.64.0.0/10|--cluster-cidr=100.64.0.0/10,fc00::/48|' \
/etc/kubernetes/manifests/kube-controller-manager.yaml
根因推测: 这是因为之前
--feature-gates=IPv6DualStack=true被删除后,kubeadm 认为不再需要双栈配置,自动剥离了 IPv6 CIDR。
问题3:修改 manifest 后 apiserver 没有自动重启
修改 static pod manifest 后,kubelet 应该自动检测变更并重启 Pod。但实际情况是:apiserver 停止后没有重启。
原因: kubelet 停掉了旧的 apiserver 容器,但新的 Pod sandbox 因为某种原因没有被创建(containerd 层面的 sandbox 残留)。
解决: 手动清理旧的 sandbox 容器:
# 查看 sandbox
crictl pods | grep kube-apiserver-mastera
# 6d2f74d9e6688 44 hours ago NotReady kube-apiserver-mastera
# 强制清理
crictl stopp 6d2f74d9e6688
crictl rmp 6d2f74d9e6688
# kubelet 会在数秒内自动创建新的 sandbox 和容器
升级后遗留问题:Calico CNI 授权失败
升级完成后,新 Pod 无法创建,报错:
Failed to create pod sandbox: error getting ClusterInformation:
connection is unauthorized: Unauthorized
根因
Calico CNI 插件在每个节点上使用一个 kubeconfig 文件(/etc/cni/net.d/calico-kubeconfig)来访问 K8s API。这个 kubeconfig 中的 token 是 Pod-bound Service Account Token,绑定到特定的 calico-node Pod UID。
K8s 升级过程中 calico-node Pod 被重建,UID 改变,导致旧 token 失效。而 CNI 是在 容器 sandbox 创建时被 kubelet 调用的,此时使用的是磁盘上的静态 kubeconfig,不是 Pod 内的自动挂载 token。
解决
重启 calico-node DaemonSet,让新 Pod 写入新的 CNI kubeconfig:
kubectl rollout restart ds calico-node -n kube-system
# 如果旧 Pod 因为 Calico 问题无法正常终止,强制删除
kubectl delete pod calico-node-xxxx -n kube-system --force --grace-period=0
# 验证新 kubeconfig 已更新
ls -la /etc/cni/net.d/calico-kubeconfig
# 确认时间戳是最新的
关键点: 这个问题不会在升级时立即暴露,因为已有的 Pod 不受影响。只有在创建新 Pod 时才会触发 CNI 调用并暴露 token 失效问题。
最终状态
NAME STATUS ROLES VERSION
mastera Ready control-plane,master v1.23.17
masterb Ready control-plane,master v1.23.17
masterc Ready control-plane,master v1.23.17
所有服务正常运行,Calico 网络通信正常,Longhorn 存储无影响。下一步将继续升级到 1.24,届时需要面对 containerd CRI v1alpha2 废弃和 dockershim 移除的挑战。
