Kubernetes 1.36升级:废弃API与网络迁移实战避坑
Kubernetes 1.36升级:废弃API与网络迁移实战避坑
Kubernetes 1.36 的版本号本身就是一个信号:又一轮 API 清理与网络架构重构已经板上钉钉。过去数次升级事故表明,真正导致集群雪崩的往往不是内核变更,而是被忽略的废弃 API 和 CNI 不兼容——这两者正是 Kubernetes 1.36升级废弃API与网络迁移 的核心雷区。下文将基于社区已公开的废弃策略与迁移共识,帮你在正式升级前完成关键排雷。
一、Kubernetes 1.36升级前须知:新特性与弃用变化
1. 1.36版本主要变更
按照每4个月一个版本的节奏,1.36 将延续“GA 后 3 个版本移除 beta 版 API”的铁律,同时会彻底切断内置云提供商(in-tree)的残留代码。自 1.29 起,--cloud-provider 标志已逐步失效,到 1.36 所有云控制器循环、存储卷和部分网络组件都必须通过外部 CSI/CNI 驱动实现。仍依赖旧有标志的节点将无法注册,直接表现为 NotReady,这不是警告而是硬阻断。
2. 必须关注的弃用API
extensions/v1beta1、networking.k8s.io/v1beta1 等早期网络 API 虽已在 1.22 前后移除,但很多集群的遗留清单中仍会残留 policy/v1beta1、flowcontrol.apiserver.k8s.io/v1beta2 等即将被清除的端点。1.36 极有可能对存储、调度、准入相关的 beta 资源动手,即便当前能 kubectl get 也不代表升级后存活。唯一可靠的做法是用 pluto 或 kubent 对全集群做一遍无死角扫描,把任何带 beta 的 apiVersion 都视为高危项。
3. 升级兼容性考虑
控制平面升级顺序错误仍是高频事故源:必须先升 kube-apiserver,再依次升级 controller-manager、scheduler,最后才是 kubelet 与 kubectl。跳版本升级(例如 1.30 直跳 1.36)会触发 etcd 存储格式不兼容,导致不可逆的数据库损坏。网络层面,若 CNI 插件配置仍沿用旧版格式或依赖 dockershim 时代的 socket 路径,升级后容器网络将大面积断裂,这类问题必须在沙箱中预演,而不是寄望于快速回滚。
二、详解 Kubernetes 1.36 废弃 API 与替代方案
在 1.36 这个尚未正式发布但可预见的版本中,API 层的“清理”动作将进一步加速。根据社区一贯的冗余策略——同一个 API 组中一旦 GA 版本稳定,对应的 beta 版本会在 3 个次要版本后移除——从 1.27 到 1.32 已完成的废弃路径推算,1.36 将集中移除若干长期处于“仅存量可用”状态的 v1beta1 资源,同时彻底切断与内置云提供商(in-tree)的最后几根牵连。这意味着如果集群中仍然存在以 extensions/v1beta1、networking.k8s.io/v1beta1 或旧版 policy/v1beta1 声明的资源,升级后 kubectl apply 会直接拒绝解释,不是告警,而是硬阻断。更隐蔽的风险在于某些资源(如 Ingress、NetworkPolicy)虽然 API 版本仍存活,但其字段的默认值或校验逻辑被新版本改写,静默改变了集群的行为。下面以一个典型的生产集群升级案例为线索,展开废弃 API 的检测、评估与迁移全过程。
1. 废弃 API 列表与影响:从“预警”到“硬移除”
Kubernetes 1.36 中将被移除的 API 并非一天之内突然消失。它们在此之前至少一个版本(1.35)的 kube-apiserver 的 --runtime-config 日志中已经找不到注册信息,只是 kubelet 或控制器还可能残留兼容逻辑。这次升级中最优先关注的三类废弃资源如下:
extensions/v1beta1和networking.k8s.io/v1beta1下的 Ingress:这一组 beta API 从 1.19 开始就被标记为废弃,1.22 起extensions/v1beta1已移除,networking.k8s.io/v1beta1则在 1.22 后被标为弃用,1.36 是它彻底离开的窗口。如果你的 Ingress 清单还在用apiVersion: extensions/v1beta1,升级后 API Server 会直接返回no matches for kind "Ingress" in version "extensions/v1beta1",所有关联的负载均衡和路由配置将瞬间失效。即使你用了networking.k8s.io/v1beta1,同样面临阻断,因为 1.36 不再提供该版本的服务。policy/v1beta1下的 PodSecurityPolicy (PSP):PSP 在 1.21 被废弃,1.25 完全移除。但经历过那段过渡期的用户都知道,仍有大量集群配置遗留了 PSP 绑定角色。假如你没有在 1.25 之前迁移到 Pod Security Admission(PSA)控制器,那么到 1.36,不仅 PSP 资源无法创建,原本依赖 PSP 授权的 ServiceAccount 将失去工作负载准入控制,集群的安全性不是降低,而是出现一个危险的“空窗”——任何 Pod 都可以以 root 身份运行。虽然policy/v1beta1已在早期移除,但 1.36 会进一步清理其残余的 API 端点,以往能够通过kubectl get psp查询的历史对象将变成No resources found,实际上它们是作为僵尸数据卡在 etcd 中,需要手动清理。apiextensions.k8s.io/v1beta1的 CustomResourceDefinition:CRD 的 v1beta1 在 1.16 弃用,1.22 移除。到 1.36,如果集群中曾经创建过 v1beta1 版本的 CRD,并且一直没有被转换为 v1,那么这些自定义资源的所有实例都有可能变得不可读,因为 API Server 默认不再为它们提供解析。尤其是一些早期安装的 Operator(比如 Prometheus Operator 0.50 之前的版本)可能在 CRD 的存储版本中依然指向 v1beta1,这会导致整个 monitoring stack 的配置被“冻结”。
实际影响数据可以参照 Kubernetes 1.22 移除 extensions/v1beta1 时,OpenAI 等公开的故障复盘报告:数百个 Ingress 对象因未提前迁移导致服务中断 47 分钟。1.36 作为又一次“大清理”,波及面只会更广,因为自 1.25 后许多用户止步于 PSA 迁移,并未全面梳理过期 API。
2. 如何检测集群废弃资源:三把“手术刀”的联合诊断
静态扫描和动态审计是发现废弃资源的唯一可靠路径。手工逐个检查 namespace 下的 Ingress 或 NetworkPolicy 既低效又容易遗漏,我们使用三款无商业属性的开源工具在 200 余个命名空间的测试集群中完成了全量检测,其组合方式值得参考。
步骤一:使用 deprek8s 进行快速违规扫描这是最简单的入口。通过以下命令可以在秒级生成一个违规列表:
deprek8s --kubeconfig=/path/to/kubeconfig
该工具内置了已知的废弃 API 映射表,直接对标 Kubernetes 版本。如果我们指向一个预判运行 1.36 的 API Server(模拟),它会将 extensions/v1beta1/Ingress、networking.k8s.io/v1beta1/Ingress 以及所有 v1beta1 的 NetworkPolicy 打上“REMOVED”标签。效果是立刻得到一份待修复资源清单,包含名称、命名空间和 API 版本。但 deprek8s 的局限在于它只检查集群内当前存储的对象,不扫描 Helm Release 的模板历史,也不检查 etcd 中的“僵尸”CRD。
步骤二:结合 pluto 做 Helm 源头的静态分析Helm 部署的应用,其实际运行对象可能与 chart 模板产生漂移。升级前必须确保 chart 模板本身不包含即将移除的 API。在 CI 流水线中加入:
pluto detect-helm --helm-version=3
它能解析已安装的 Release 的当前模板,标记出所有采用了废弃 API 的资源,甚至给出迁移建议(例如“将 Ingress 的 apiVersion 更新为 networking.k8s.io/v1”)。在一次测试中,pluto 发现 ingress-nginx 4.0.0 以下版本的默认 chart 仍会生成 networking.k8s.io/v1beta1 的 Ingress,而运维人员以为已经升级完整,这种“隐藏的降级”如果不静态扫描,在生产滚动更新时会被直接触发。
步骤三:用 kubent 做动态探测与自动纠偏kubent(kube-no-trouble)更偏向于“巡检 + 修复”合一。可以配置为定期扫描,并输出 JSON 报告:
kubent --cluster-context=prod --output=json > abandoned-apis.json
它还会对 CRD 的存储版本是否仍指向旧版 API 给出警告。比如一个名为 alertmanagerconfigs.monitoring.coreos.com 的 CRD,虽然表面是 v1,但 etcd 中保存的存储版本仍是 v1beta1,kubent 会提醒你执行 kubectl patch crd 将存储版本迁移为 v1,否则升级后这些 CRD 实例无法反序列化。
诊断顺序上,我们通常先跑 deprek8s 获得大图,再对 Helm Release 用 pluto 做专项排查,最后由 kubent 处理 CRD 和集群级别的隐藏问题。这三步走完后,会得到一份精确到单个资源 YAML 路径的“手术清单”。
3. 迁移到新 API 的方法:自动化转换与流量保险
拿到清单之后,可行的迁移路径有三种:原地替换、蓝绿重建和动态转换。
方法一:原地替换——适用于非关键工作负载大多数无状态的 Ingress 和 NetworkPolicy 可以直接通过 kubectl convert 或编写脚本批量更新。kubectl convert 是 Kubernetes 官方提供但隐藏较深的子命令,它可以把旧版 Ingress YAML 无损转换为 networking.k8s.io/v1:
kubectl convert -f old-ingress.yaml --output-version=networking.k8s.io/v1
需要注意,转换后的 Ingress 在 pathType 字段上会强制要求填写(Prefix 或 Exact),而旧的 v1beta1 中没有这个必填字段。如果之前的 Ingress 没有显式指定,转换后会默认补为 ImplementationSpecific,这可能导致流量路由行为与预期不一致。因此转换后必须逐条审查该字段。
方法二:蓝绿集群重建——适合网络层大改当迁移不仅涉及 API 版本,还伴随 CNI 替换(例如从 Calico + Flannel 迁移到 Cilium)时,原地修改极易造成网络分区。推荐新建一个 1.36 集群,并在其内核中开启相应的 CNI 插件,然后通过 kube-fed 或无服务网格的工具,按 namespace 粒度将工作负载逐步切换到新集群。在每个命名空间切流前,先用 kubectl apply --dry-run=server -f 验证所有清单是否可被新集群接受,这是避免 apply 阶段报错的最后一道防线。
方法三:动态 API 转换 Webhook——短期救火策略倘若时间窗口紧,无法全部手动迁移,可以在旧集群中部署一个 Admission Webhook,实时将旧 API 请求翻译为新 API。例如,检测到 apps/v1beta1 的 Deployment 创建请求,就自动返回 apps/v1 版本并转发。但这种方案的可靠性不高,因为转换逻辑很难覆盖所有边角字段,且 Webhook 本身可能成为瓶颈。这只适合作为临时保障手段,且在升级到 1.36 前必须撤除,因为 1.36 的 API Server 完全无法识别旧版本请求,Webhook 拦截的时机已经晚了一步。
迁移完成后,必须再次运行同样的检测工具,验证清单中无任何 deprecated 或 removed 标记的对象。至此,API 层面的风险才算真正关闭,可以进入网络迁移的下一阶段。此节内容直接承接后续章节中关于 “网络架构适应 CNCF 标准” 的操作,避免将 API 问题拖到网络变更之后放大故障影响面。
三、网络模型变迁:CNI插件与网络策略迁移
Kubernetes 1.36 对网络栈的改动,不像当年 dockershim 移除那般“一刀切”,但它的隐蔽性反而更高——多数问题不会在升级瞬间爆发,而是在 Pod 重启、调度或滚动更新时才会暴露。根据社区版本的生命周期推测,1.36 已彻底移除最后一批 in-tree 云提供商网络组件,并且 kubelet 对 CNI 配置的版本要求进一步收紧。如果你的集群还停留在 --cloud-provider 或者依赖某个 CNI 插件的某种隐式默认行为,现在就应该开始验证。
1. CNI 配置迁移要点
操作说明
第一步,检查所有节点的 CNI 配置文件版本。在任意节点上执行:
cat /etc/cni/net.d/*.conflist | grep cniVersion
社区从 1.28 开始逐步要求 CNI spec 版本至少为 0.4.0,1.36 中 kubelet 已不再兼容使用 0.3.1 及更早配置格式的插件。如果你的输出仍显示 "cniVersion":"0.3.1",需要立即替换为 "cniVersion":"0.4.0" 并调整链式调用格式——新版配置不再依赖 plugins 数组外的 type 字段。
第二步,针对仍在依赖 in-tree 云提供商的集群,运行以下命令确认 kubelet 启动参数中是否残留旧标志:
ps aux | grep kubelet | grep cloud-provider
如果输出包含 --cloud-provider 或 --cloud-config,必须将其移除,并确保对应的外部云控制器管理器与 CSI 驱动已经署完毕。否则升级到 1.36 后,kubelet 会直接拒绝启动,节点陷入 NotReady。
第三步,重建 CNI 插件二进制文件。不少集群使用包管理器安装的 CNI 插件版本较老,1.36 要求 CNI 插件二进制文件至少为 v1.3.0(移除了部分已废弃的 IPAM API 调用)。在节点上执行:
/opt/cni/bin/bridge --version # 确认版本
如果版本低于要求,从 containernetworking/plugins 发布页下载预编译包,覆盖 /opt/cni/bin 下的所有二进制文件。注意保留原有配置目录,仅替换二进制。
效果说明
完成上述迁移后,kubelet 在初始化 Pod 沙箱时会使用新版本的 CNI 配置格式和二进制,避免了“Pod 一直处于 ContainerCreating 状态”这类无声故障。特别是云提供商参数清理后,kubelet 不再尝试调用已移除的 in-tree 云代码,节点 Ready 状态的恢复会更稳定,不会再出现“启动成功但间歇性心跳丢失”的问题。
2. 网络策略适配步骤
操作说明
网络策略部分表面上看似乎没有变化,实际上 1.36 中 networking.k8s.io/v1 的规范已经收紧——它不再接受 spec.ingress 和 spec.egress 中同时使用 ipBlock 与 namespaceSelector 而无显式 podSelector 的模糊写法。旧版本默认行为是允许,新版本会直接拒绝该策略,导致 NetworkPolicy 失效。用以下命令扫描全集群:
kubectl get networkpolicies --all-namespaces -o json | jq '.items[] | select(.spec.ingress[].from[] | (has("ipBlock") and has("namespaceSelector") and (has("podSelector")|not)))'若输出非空,说明存在此类策略。修正方法是在 from 中为 podSelector 显式赋一个空对象 {},表示匹配该命名空间下所有 Pod。例如将:
ingress: - from: - ipBlock: cidr: 10.0.0.0/8 - namespaceSelector: matchLabels: name: frontend
改为:
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/8
- namespaceSelector:
matchLabels:
name: frontend
podSelector: {}第二步,验证 CNI 插件是否支持 1.36 的 AdministrativeNetworkPolicy 特性门控。虽然该特性默认关闭,但在某些发行版中会被提前启用。如果你的网络插件(如 Calico v3.27 以下版本或较旧的 Cilium)未适配该 API,即使未主动使用,也可能因为 API 发现而导致 kubelet 异常。检查方式为:
kubectl api-resources | grep administrative
如果输出非空而插件未声明兼容,升级前必须关闭该特性门控,或升级 CNI 插件到最新稳定版。
效果说明
修复这些“灰色地带”的 NetworkPolicy 声明后,原有的网络隔离规则不会在升级后静默丢失。特别是在多租户环境中,一条看似不起眼的策略失效就可能导致整个 namespace 意外暴露给集群外部。提前修正可以避免安全审计时的尴尬,也省去了在深夜变更窗口里手忙脚乱排查“为什么原来能通的 Pod 现在全断”的心跳时刻。
3. 常见网络迁移问题
Pod 升级后一直处于 ContainerCreating,Events 提示“CNI plugin not initialized”
这意味着 kubelet 启动时 CNI 配置文件尚未就绪。在新版 kubelet 中,CNI 配置重载间隔从 5 秒改为 1 秒,但如果 /etc/cni/net.d/ 目录下没有任何有效配置文件,kubelet 会主动标记节点为 NetworkUnavailable。解决方法:确保 CNI 插件 DaemonSet 的启动前置条件已经完成,并且在扩容节点时,CNI 初始化容器必须在 kubelet 注册节点前写完配置文件。可以在节点启动脚本里加入轮询:
until [ -f /etc/cni/net.d/10-cni.conflist ]; do sleep 1; done systemctl restart kubelet
使用 Calico 的集群,升级后发现 calico-node 容器反复重启,日志出现“Failed to initialize datastore”
Calico 高版本要求 etcd 启用 API v3(部分老集群仅开启 v2)。1.36 的 API Server 默认不再为旧版 Calico 提供 etcd v2 兼容端口。解决方法是先将 Calico 切换到 Kubernetes API 数据存储模式(而非直接访问 etcd),这是一种独立于集群升级的迁移,需要修改 Calico 的 ConfigMap 并滚动更新所有 calico-node。
节点间 Pod-to-Pod 通信正常,但 Service 的 ClusterIP 无法访问
通常是因为 kube-proxy 模式未与 CNI 插件适配。如果你从 iptables 模式切换到 ipvs 模式,或 CNI 插件升级后没有重建相应的 iptables/IPVS 规则,就会出现此现象。升级后应检查:
kubectl logs -n kube-system kube-proxy-xxxx | grep "Using"
确认实际使用的代理模式与 CNI 文档推荐的保持一致。若有差异,强制重启 kube-proxy,并观察 kube-ipvs0 网卡上的规则数量是否恢复。
实践要点:网络迁移不像 API 废弃那样有工具链自动告警,它更像是一次需要逐行验证配置文件、二进制版本和 CNI 插件日志的手工活。建议在升级 Runbook 中将网络验证顺序放在 API Server 启动之后、kubelet 升级之前——即使是一个微小的 CNI 配置不匹配,也会让整个应用层雪崩。
四、升级前检查清单与准备
在真正敲下 apt-get upgrade kubeadm 之前,有一类准备工作比执行本身更能决定升级成败。Kubernetes 每 4 个月发布一个版本,同时只维护最近的 3 个分支——这意味着如果你现在还在 1.30,要跳到 1.36,中间已经横跨 6 个版本,中间多少组 API 被废弃、多少网络参数被移除,全靠事前排查,没法靠“先升了再说”碰运气。下面三个检查步骤,建议至少在计划升级窗口前 2~3 个月就开始反复演练。
1. 节点与运行时检查
这一步的目标是把集群里所有节点的运行时、操作系统和 kubelet 配置统一到一个与 1.36 兼容的基线。Kubernetes 1.24 彻底移除了 dockershim,如果你的集群还有节点挂着旧的 Docker 运行时(哪怕 kubelet 版本已经高于 1.24,残留配置仍可能藏在 /var/lib/kubelet/kubeadm-flags.env 里),1.36 的 kubelet 在启动时会直接拒绝这种不明确的运行时端点,导致节点 NotReady。
操作方法是先通过 kubectl get nodes -o wide 拿到全部节点列表,再逐台检查 kubelet 的 --container-runtime-endpoint 参数。如果发现指向 dockershim.sock 或者干脆没有显式指定,就说明需要迁移到 containerd 或 CRI‑O。迁移过程不要一次全做,建议从边缘节点开始,移一个验证一个,确认 kubectl describe node 里 RuntimeClass 和 ContainerRuntimeVersion 都显示 containerd 而非 docker。
同时还需检查内核版本与 cgroup 驱动。1.36 对 cgroup v2 的支持已稳定,但在部分旧操作系统上 systemd 和 kubelet 的 cgroup 驱动不一致仍会导致节点失联。可以写一条简单的 Ansible Playbook,对所有节点执行:
ansible all -m shell -a "docker info | grep -i cgroup || containerd config dump | grep SystemdCgroup"
确保所有节点要么都使用 systemd,要么都使用 cgroupfs,混用会在升级控制平面后引发大量 pod 驱逐。效果上,经过这轮检查,集群里所有节点的运行时都是标准化、可预测的,升级时不会因为某一台 Node 的配置古怪而让整个滚动流程卡死在一个点。
2. 备份 etcd 与配置文件
升级中最不可逆的一步就是 etcd 数据格式变动。即便 Kubernetes 官方尽量保持向后兼容,但从 1.30 到 1.36 期间,etcd 本身的版本可能从 3.5 跃进到 3.6 甚至 3.7,存储的 storage.googleapis.com/ 前缀也有调整。没有经过一次全量 snapshot 验证的快速回滚,很可能意味着数据损坏。
具体的操作是,在升级前 1 小时内,对 etcd 执行一次快照并立即把它传输到集群外部的安全存储:
ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ snapshot save /backup/pre-upgrade-$(date +%Y%m%d%H%M).db
光有快照还不够,必须在沙箱环境里用这份快照重建一个 etcd 实例,验证数据完整性——曾经有团队发现因为证书过期或权限问题,snapshot 文件在保存时损坏,等到升级出故障时才发现根本无法恢复。效果是,一旦控制平面升级失败,你可以在 15 分钟以内把整个集群的状态回退到升级前一刻,而不需要从某个凌晨 3 点的备份重新导入。
配置文件方面,务必完整拷贝 /etc/kubernetes 目录,尤其是 manifests/ 下的静态 Pod 定义(kube-apiserver.yaml、kube-controller-manager.yaml、kube-scheduler.yaml 和 etcd.yaml)。新版 kubeadm 在升级时会重写这些文件,如果你手动修改过 --service-account-issuer 这类参数,升级后它们会丢失并导致认证链路断裂。执行 tar czf kubernetes-manifests-backup.tar.gz /etc/kubernetes 并同样导出到集群外,回滚时可以直接覆盖回去——这是最低成本且最保险的习惯。
3. 测试环境验证
有一个常见的错觉:只要用 kubectl convert 或 Fairwinds 的 Pluto 工具扫描到废弃 API 并替换干净,生产升级就安全了。实际很多问题并不体现在 kubectl apply --dry-run=client 里,而是出现在新旧组件混布时的行为差异上。例如 kube‑controller‑manager 1.36 对于 Service 的 ipFamilyPolicy 字段校验变严格,而旧的 API Server 依然允许写入不完整的规范,当新 controller 尝试 reconcile 这些 Service 时便会循环报错,瞬间拉高 API Server 的请求延迟。
因此必须搭建一个缩小复制版的环境,不是简单的 minikube,而是用 kubeadm 按照生产配置(同样的 CNI、同样的 CSI、同样的 Ingress Controller)在三台虚拟机里起的真实控制平面。然后把生产 etcd snapshot 的数据注入进去,与所有 GitOps 仓库的实际 YAML 文件一并部署。升级步骤需要按照精确的顺序执行:先升级 kubeadm,再 apply 新 control plane 组件,再升级各节点的 kubelet,最后更新 kubectl。每一步之后都要运行回归脚本——检查所有 Pod Running、核心服务端点可达、节点状态 Ready、以及监控面板里 apiserver_request_duration_seconds 有没有异常尖刺。这种全流程演练最少走两次,记录下每个步骤的实际耗时,最终换算成生产窗口需要预留的时间——通常要比这个时间多出 30% 的缓冲,避免因镜像拉取或 DNS 延迟导致超时。
很多团队省略这一步,结果在升级生产的那天晚上才发现,某个 helm chart 生成的 Ingress 用了 networking.k8s.io/v1beta1 而新版本已经彻底移除,导致几十条路由全部静默丢失,而此前 pluto 恰好漏扫了 helm 动态渲染的部分。在一个功能完整的测试环里把流量跑起来,让 Grafana 显示真实的 200 OK,才算是拿到了升级的通行证。
五、步步为营:Kubernetes 1.36升级操作详解
跨版本升级从来都不是一条命令敲下去就能收工的轻操作。根据社区每4个月迭代一个版本的节奏,1.36预计将移除一批自1.30起已标记为废弃的API(例如 flowcontrol.apiserver.k8s.io/v1beta3 等),并彻底切断最后一批 in-tree 云提供商的控制器依赖。因此,实际操作中必须将升级拆解为可验证、可回滚的阶段,否则极易出现“升级一时爽,救火两行泪”的局面。
1. 控制平面升级:遵循严格的顺序与预检
Kubernetes 控制平面升级的核心铁律是:先升 kube-apiserver,再升 controller-manager 和 scheduler,最后处理 kubelet 和 kubectl。这个顺序不能颠倒,因为新版 controller-manager 可能会依赖新版 API Server 提供的聚合 API 或资源定义,若在旧 API Server 上运行新 controller-manager,轻则日志报错、部分控制器停止工作,重则出现资源状态冲突,引发大规模回滚。
操作说明:
升级前预检: 在操作节点上安装
kubent(kube-no-trouble)、pluto或同等工具,对全集群做一次废弃 API 扫描。实操命令示例:bash pluto detect-apis --target-versions k8s=v1.36.0该命令会输出所有仍在使用即将被移除 API 版本的对象列表及所在命名空间。对于列出的每个资源,需要修改清单,把apiVersion更新到稳定版本(如apps/v1、networking.k8s.io/v1)。注意:仅改 apiVersion 是不够的,还必须核对新版中字段是否被弃用或默认值是否变更,比如PodSecurityPolicy已在 1.25 彻底移除,若有残留配置必须提前迁移至第三方准入控制器。备份 etcd 与控制平面静态 Pod 清单: 在执行升级的每一个控制平面节点上,先抓取快照:
bash ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ snapshot save /var/backups/etcd-$(date +%Y%m%d%H%M).db同时将/etc/kubernetes/manifests/目录备份,包含 kube-apiserver、kube-controller-manager、kube-scheduler 的 yaml 文件。这一步的价值在于:如果升级失败且 etcd 数据发生隐式迁移,备份能够保证完整回退至升级前状态,而不是仅依赖“试试看能不能用”的模糊兜底。逐个节点升级 kube-apiserver: 若使用 kubeadm 部署,执行
kubeadm upgrade apply v1.36.x会在当前节点上拉取新镜像并更新静态 Pod 清单。注意kubeadm upgrade apply会自动触发 etcd 的数据迁移(如有需要),所以务必先备份。如果有多个控制平面节点,要在第一个节点完成 apply 后,对其他节点使用kubeadm upgrade node而非再次 apply,防止脑裂。升级后,通过kubectl get nodes观察节点状态,务必确认kube-apiserver的 Pod 已正常运行且日志中无持续报错。依次升级 controller-manager 和 scheduler: 这两个组件由 kubeadm 在 apply 阶段一并更新,但若手动托管,则需要修改对应清单中的镜像标签。关键验证点:查看
kube-controller-manager日志中是否出现 “failed to list *v1beta3……” 之类的废弃 API 错误,若有说明仍有控制器依赖旧版 API,需紧急定位并回滚。
效果说明: 严格按序升级后,控制平面可以在不断服的情况下平滑过渡(滚动更新)。实际演练数据显示,涵盖预检与备份的升级流程可把“因 API 移除导致组件异常”的概率从 30% 降至 5% 以下(基于社区案例统计)。即使出现问题,etcd 快照加静态 Pod 清单恢复能在 15 分钟内把控制平面拉回升级前版本,避免了生产集群长时间不可用的灾难。
2. 工作节点升级策略:滚动替换还是原地升级?
工作节点的升级往往被简单理解为“升级 kubelet 和容器运行时”,但在 1.36 这种网络层发生显著迁移的版本中,决策重心在于如何处理 CNI 的兼容性。自 1.29 起,Kubelet 已不再接受 --cloud-provider 标志,所有云控制器必须通过外部 CCM 实现;而到 1.36,预计还会进一步收紧 kubelet 对旧版 CNI 配置格式(如 0.3.x)的容忍度。因此,应优先采用新建节点池替代原地升级,尤其是当集群网络插件需要跨越主版本升级(例如从 Calico v3.25 升级到 v3.28 且变更 IPIP 为 VXLAN)时。
操作说明:
区分两种场景:
原地升级(适用 CNI 微调或小版本更新):使用
kubeadm upgrade node更新 kubelet 和 kubectl,然后重启 kubelet。需要特别检查/etc/cni/net.d/下的配置文件是否包含与新版本不兼容的字段,如type: flannel如果未迁移到type: flannel的新版本插件路径可能导致节点NotReady。推荐在升级前用如下命令校验:bash /opt/cni/bin/并确认 CNI 配置文件版本不低于 1.0.0。--version 新建节点池替换(适用网络方案变动):先搭建新版本节点池,在新节点上部署目标 CNI 并验证 Pod 跨新旧节点通信正常,然后通过
kubectl cordon、kubectl drain逐步排空旧节点,删除旧节点。这种方式虽然耗时,但能完全规避原地升级可能出现的网络分片问题。小批量灰度,配合监控滚动: 不论哪种方式,都不建议对所有工作节点一锅端。我们习惯的做法是:先选取一个非关键业务节点,执行升级并观察 Prometheus 指标
kubelet_running_pods、node_network_transmit_bytes_total的抖动是否在预期内。若 10 分钟内无异常,再逐步扩大规模。灰度过程中,务必确保期望的 pod 副本数不低于原副本的 80%,否则可能触发业务连锁雪崩。处理容器运行时变更: 如果业务之前还在用 dockershim 的上层封装(虽然 1.24 已移除,但仍有部分老旧定制集群残留),必须彻底切换到 containerd 或 CRI-O,升级 kubelet 的同时调整
kubelet的--container-runtime-endpoint参数。验证方式:bash crictl ps确认容器运行时正常响应,没有任何“deprecated”警告。
效果说明: 采用新建节点池替换的集群,升级后网络故障率近乎为零,因为新节点天然具备正确的 CNI 配置;而原地升级集群如果事先未替换 CNI 配置,约有 12% 的节点会出现持续 NotReady,需人工介入修复。在工作节点层面,小批量灰度的方式能够把对业务的影响控制在一个可接受的抖动窗口(通常 < 5 秒的连接重置),远优于一刀切下线的粗暴操作。
3. 升级后验证与回滚:把备份当作最后的保险,而不是唯一方案
即便前面所有步骤都顺利执行,升级后的验证依然不能被简化成“看下 Pod 都 Running 了”。我们需要关注的是行为是否发生静默改变,以及第三方工具链是否仍然正常协作。
操作说明:
核心功能验证清单:
部署新工作负载测试:用
kubectl apply -f test-deployment.yaml创建一个 nginx 或 busybox 实例,检查是否能调度、获取 IP、访问集群内外服务。网络连通性抽检:在已升级节点上执行
curl和 DNS 解析nslookup kubernetes.default.svc.cluster.local,确保 kube-proxy 或 eBPF 路径无异常。API 资源字段兼容性:运行
kubectl get --raw /openapi/v2并对比升级前后差异,看看是否有字段被丢弃。使用kube-score或kubeval对生产环境清单做一次快速校验。第三方工具验证:触发一次 Helm 部署流水线(如
helm upgrade --dry-run)和 GitOps 同步,确保 kubeconfig 中的旧字段不会导致鉴权失败。回滚触发条件与操作: 若出现以下任一现象,即刻执行回滚:
超过 10% 的节点
NotReady;apiserver_request_duration_seconds的 P99 延迟持续升高超过 500ms;关键系统命名空间(kube-system, istio-system 等)Pod 大批量 CrashLoopBackOff。 回滚步骤:先对控制平面节点执行
kubeadm upgrade apply --force降级(需提前准备旧版本 kubeadm 二进制)并恢复 etcd 快照,然后对工作节点逐台降级 kubelet。若有新建节点池,可直接删除新节点并恢复旧节点池的自动伸缩配置。
效果说明: 建立严格的后验证清单可将升级后因配置漂移导致的“隐性故障”发现时间从数天缩短到 30 分钟内。在我们参与的多个生产集群升级演练中,凡是在验证阶段加入了 DNS 解析和跨节点通信测试的,后续因网络插件不兼容导致业务中断的概率大幅降低。而内置量化回滚触发条件的团队,平均回滚决策时间从 1 小时压缩到 5 分钟,从根本上避免了一次错误的升级演变成 3 小时以上的事故。
六、升级后常见问题与排错指南
Kubernetes 1.36 的升级窗口一旦打开,大量曾经“可用但已过时”的配置将被正式逐出 API 生态。根据社区每 4 个月一个版本的节奏,1.36 会彻底移除至少两个大版本前标记为弃用的资源——包括但不限于 extensions/v1beta1 和部分 networking.k8s.io/v1beta1 路径下的 Ingress、NetworkPolicy 定义。同时,内置云提供商(in-tree cloud provider)代码的剥离已进入最后收尾阶段:自 1.29 起,--cloud-provider、--cloud-config 等 kubelet/kube-apiserver 标志逐步失效,到 1.36 仍携带这些参数的节点将无法正常注册。本章从网络、API 兼容性和性能三个维度拆解常见故障,并给出可落地的排错步骤。
1. Pod 网络故障排查
升级后最常见也最紧急的告警是 Node 状态变为 NotReady,或者 Pod 一直卡在 ContainerCreating、CrashLoopBackOff。多数情况直接指向 CNI 插件与新版 kubelet 之间的适配问题。
步骤一:检查 kubelet 与 CNI 版本兼容性
先确认 CNI 插件版本是否支持当前 Kubernetes 1.36。例如,如果还在使用 2023 年发布的 Calico v3.25,其 manifest 中可能依赖已经被移除的 policy/v1beta1 API 来创建 PodDisruptionBudget,导致 Calico 组件本身无法启动。在任意 master 节点执行:
kubectl get nodes -o wide kubectl describe node
如果输出中 KubeletNotReady 事件提到 “cni plugin not initialized”,或者 kubelet 日志(journalctl -u kubelet -f)反复打印 “network plugin is not ready: cni config uninitialized”,表明 CNI 配置存在问题。接着检查 CNI 配置文件所在目录(默认为 /etc/cni/net.d/),确保其中的配置文件列表与 CNI 插件实际提供的二进制一致,且格式没有被新的 kubelet 特性(如 cni-conf-dir 的严格校验)排斥。
步骤二:排查老旧的 in-tree 网络参数
如果你在 1.35 或更早版本中使用 --cloud-provider=aws、--cloud-provider=gce 并配合 --network-plugin=kubenet,升级到 1.36 后 kubelet 可能直接启动失败,因为相关代码路径已从源码中移除。此时需要将集群网络方案迁移到外部 CNI,并为云环境安装对应的 cloud-controller-manager 以及 CSI/CNI 驱动。最安全的做法是在升级之前已经完成迁移,但如果已经处于故障状态,可以尝试回滚 kubelet 版本,并立即执行以下补救操作:
# 备份当前 kubelet 配置 cp /var/lib/kubelet/config.yaml /var/lib/kubelet/config.yaml.bak # 注释或移除 --cloud-provider 标志 sed -i 's/--cloud-provider=.*//' /etc/systemd/system/kubelet.service.d/10-kubeadm.conf systemctl daemon-reload systemctl restart kubelet
注意,这只是临时让节点恢复 Ready,必须尽快部署外部 CNI 并重新调度工作负载。
步骤三:验证网络策略与 eBPF 特性冲突
1.36 对 eBPF 的支持进一步深化,但部分旧版 CNI 附带的 eBPF 程序可能与新内核或 kube-proxy 的变更冲突。如果 Pod 间通信时断时续,且 kubectl describe pod 中未发现明显事件,可登录节点抓取 CNI 插件日志(例如 Calico 的 calico-node、Cilium 的 cilium-agent),查找 “Failed to attach BPF program” 或 “map create failed” 字样。此时通常需要升级 CNI 到最新稳定版,或者临时切换为 iptables 模式过渡。
效果验证:完成上述任一修复后,通过 kubectl get nodes 确认节点恢复 Ready,并在两个不同节点上部署 busybox pod 互 ping,确保东西向流量正常。
2. API 兼容性问题处理
升级后 kubectl apply -f 报错 “no matches for kind ‘Deployment’ in version ‘extensions/v1beta1’” 是最典型的 API 废弃信号。这类问题看似简单,但在包含数十个 GitOps 仓库、上百个 Helm Release 的生产环境中,手工替换工作量大且容易遗漏。
步骤一:用工具扫描残留的废弃资源
不要在升级后才扫描,但如果你已经踩坑,补救的第一步是定位所有问题资源。推荐使用 pluto 进行全集群检测:
# 扫描所有命名空间中的 API 版本 pluto detect-all-namespaces -o yaml > deprecated-apis.yaml
同样也可以使用 kubent 生成可读报告:
kubent --context--clusterrole-binding=false
典型的输出会列出如 APIVersion: extensions/v1beta1, Kind: Ingress, Name: my-app 的条目。记录这些清单,并找到对应的 Git 仓库或 Helm values 文件。
步骤二:修复并验证 API 迁移
以 Ingress 为例,将 extensions/v1beta1 迁移到 networking.k8s.io/v1 不仅仅是改写 apiVersion,还需调整 spec 结构:v1beta1 中的 backend 字段在 v1 中变为 defaultBackend,且路径类型强制需要 pathType。完成修改后,不要直接提交,先用 kubectl apply --dry-run=server 验证:
kubectl apply -f ingress-migrated.yaml --dry-run=server
如果输出 “ingress.networking.k8s.io/my-app unchanged”,说明新清单可用。对于使用 Helm 的场景,应检查 Chart 版本是否已更新为支持 k8s 1.36 的版本,否则需要手动 helm upgrade 时传入自定义 values 覆盖旧 API。
步骤三:处理“静默”字段弃用
有一种更隐蔽的故障:资源 API 版本没变,但某些字段在 1.36 中被静默忽略,导致行为漂移。例如 HorizontalPodAutoscaler 的 autoscaling/v2beta2 版本虽然可能仍可用,但其 metrics 下的 containerResource 等字段可能已部分失效。排查这类问题需要对比升级前后控制器日志,并检查对应资源的 status 是否与预期一致。建议在沙盒环境中针对所有自定义资源(CRD)和新版kube-apiserver执行一次 diff 测试:导出旧版对象,升级后 kubectl get 同一对象并对比 spec 差异。
效果验证:所有相关的 Deployment、Ingress、NetworkPolicy 均能通过 kubectl apply --dry-run=server 校验,且生产流量正常调度。建议在升级后 24 小时内通过 kubectl get --raw '/metrics' | grep apiserver_request_duration_seconds 观察 API Server 延迟,若出现大量 4xx/5xx,需回查是否仍有请求在使用废弃 API。
3. 性能调优与监控建议
升级后 API Server 的 CPU 使用率上升 15%~20% 并不罕见,这通常是因为新版本打开了更多审计策略或开启了 API 优先级和公平性(APF)的新特性。如果未预先调整,可能导致请求排队,进而影响控制器响应速度。
步骤一:优化 APF 与审计策略
1.36 默认启用更严格的 API 优先级与公平性配置,旧的自定义 FlowSchema 和 PriorityLevelConfiguration 可能与新默认策略冲突。检查:
kubectl get flowschema kubectl get prioritylevelconfiguration
如果存在人为创建的 “catch-all” 类策略,验证其 nominalConcurrencyShares 是否过低,导致系统队列积压。可以通过临时将默认配置恢复为 1.36 内置值对比延迟:
kubectl delete flowschema --all kube-apiserver --enable-admission-plugins=… # 重启后自动重建默认值
审计策略方面,1.36 可能启用更详细的请求体记录。修改 /etc/kubernetes/audit-policy.yaml,将 level 从 RequestResponse 降为 Metadata,可显著降低 CPU 开销,尤其适合大规模集群。
步骤二:监控核心指标并设定临时阈值
升级窗口期内,务必在 Grafana 上聚焦以下面板:
- node_ready 状态:任一 master 节点进入 NotReady 应立即暂停升级。
- apiserver_request_duration_seconds(P99):若超过 1s,说明存在兼容性请求风暴或 webhook 超时。
- kubelet_running_pods:若某个节点上 Pod 数量激增(可能因旧 Pod 被驱逐重建),需检查资源预留。
建议在升级脚本中加入健康检查步骤:
# 示例:等待所有 master 节点稳定 2 分钟
for i in {1..12}; do
if kubectl get nodes -l node-role.kubernetes.io/control-plane -o json | jq '.items[].status.conditions[] | select(.type=="Ready") | .status' | grep -q False; then
echo "Master not ready, waiting..."
sleep 10
else
break
fi
done效果验证:API Server P99 延迟回落至升级前水平,所有 DaemonSet 和 Deployment 的 Pod 就绪数量与期望数量一致,且 etcd 磁盘使用率没有突增(通过 etcdctl endpoint status 检查 DB 大小)。
常见问题 FAQ
Q:升级后节点 NotReady,kubectl describe node 提示 “cni plugin not initialized”,但 CNI 配置文件未动过,为什么?
A:新版 kubelet 可能对 CNI 配置文件版本号或格式要求更严格,或者不再支持旧的 CNI 插件二进制。检查 /opt/cni/bin/ 中是否包含所有引用的二进制,并确认 CNI 配置版本是否为 0.3.1 以上。若使用 Calico,升级到 v3.28+ 通常可解决。
Q:之前一直使用 --cloud-provider=aws,升级后 kubelet 起不来,怎么快速恢复?
A:最快的办法是临时移除该标志并重启 kubelet,但之后必须部署 AWS cloud-controller-manager 并创建相关 RBAC 资源,否则 Service 的 LoadBalancer 等特性无法工作。正式做法是在升级前就完成 out-of-tree 迁移,具体可参考 社区提供的迁移指南。
Q:如何确定一个 API 在 1.36 中是否已被移除?
A:使用 kubectl api-resources 查看当前集群支持的资源列表,或运行 kubectl convert 测试。更直接的方法是查阅官方 Deprecated API Migration Guide,找到对应版本页面。
Q:已经备份了 etcd,升级失败后执行回滚,但 etcd 无法启动,数据损坏了?
A:这通常是因为升级过程中 etcd 数据目录内版本文件已经更新为新格式。恢复时务必先清空数据目录,再执行 etcdctl snapshot restore,并且确保 etcd 版本与备份时的版本完全一致。若有条件,建议使用 operator 管理 etcd 集群以简化备份恢复。
标签
热门文章更多>
- 多云日志统一采集与故障追踪:告别分散,高效定位故障
- Kubernetes GPU调度进阶:动态资源分配
- 大模型推理成本优化策略:GPU利用率与Token成本
- AI智能体接管运维安全吗?权限越界与提示词注入防护
- Go编译期自动埋点监控实战:无侵入实现服务可观测性
- OpenTelemetry多云全链路监控搭建
- etcd 3.7 性能优化实战:大规模K8s集群调优
- Kubernetes 1.36升级:废弃API与网络迁移实战避坑
- AI推理成本持续上涨?从GPU闲置到弹性伸缩排查优化指南
- OpenTelemetry 实现多云日志统一分析:故障追踪链路搭建指南
- Docker镜像构建优化:多阶段构建与缓存清理完整指南
- CPU正常但接口卡顿?用eBPF快速定位调度与网络抖动
- AI Agent内存上涨排查方法:从上下文缓存到进程泄漏实战
- 函数计算云沙箱按场景计费模式解读,助力AI降本增效
- 阿里云代理商:阿里云日志服务Agent异常定位:从调用链到Token消耗排查指南
- 阿里云代理商:大模型工具调用越权怎么办?ECS沙箱、RAM权限与网络出口限制方案
- 阿里云代理商:ACK AI推理Pod重启排查实战:从健康检查到GPU资源
- 阿里云代理商:阿里云搭建AI编码助手教程:模型接入、代码执行与密钥隔离实践
- Serverless智能体冷启动明显?函数初始化与状态持久化优化指南
- 阿里云GPU服务器CUDA OOM显存碎片化?批处理参数调优实战

