前的風(fēng)險(xiǎn)核查)
集群升級(jí)前的風(fēng)險(xiǎn)核查節(jié)點(diǎn)排空停滯時(shí)應(yīng)先檢查 PodDisruptionBudget、不可驅(qū)逐工作負(fù)載和副本余量。入口類服務(wù)還要確認(rèn)遷移期間是否保有足夠副本避免把維護(hù)窗口變成單點(diǎn)風(fēng)險(xiǎn)。在 Kubernetes 生產(chǎn)集群大版本升級(jí)例如從 1.25 跨版本升級(jí)至 1.28實(shí)踐中常見(jiàn)隱患除了工具或組件報(bào)錯(cuò)外還包括平時(shí)未顯現(xiàn)、升級(jí)時(shí)觸發(fā)的配置死鎖與廢棄 API 盲區(qū)。本文復(fù)盤(pán)生產(chǎn)集群升級(jí)中的排障細(xì)節(jié)并提供升級(jí)前前的自動(dòng)化校驗(yàn)方案。節(jié)點(diǎn)驅(qū)逐指令下發(fā)后 Worker 節(jié)點(diǎn)卡在 Drain 狀態(tài)。運(yùn)維團(tuán)隊(duì)準(zhǔn)備升級(jí) Worker 節(jié)點(diǎn)的內(nèi)核與 Kubelet 版本。執(zhí)行驅(qū)逐指令后終端持續(xù)輸出以下信息node/node-worker-03 cordoned evicting pod ingress-nginx/ingress-nginx-controller-7d9f4b59c-x29fz error when evicting pod ingress-nginx-controller-7d9f4b59c-x29fz (retry it later): Cannot evict pod as it would violate the pods disruption budget. evicting pod ingress-nginx/ingress-nginx-controller-7d9f4b59c-x29fz運(yùn)維人員通過(guò)診斷命令查看 PodDisruptionBudget (PDB) 與節(jié)點(diǎn)資源分配kubectl get pdb -A kubectl describe pdb ingress-nginx-pdb -n ingress-nginx kubectl get nodes -o wide --show-labels輸出反映出兩項(xiàng)配置組合的制約首先為了保證入口網(wǎng)關(guān)的高可用前人配置了 PDB 資源ingress-nginx-pdb其中設(shè)置了minAvailable: 2。然而當(dāng)前集群因?yàn)橘Y源緊張Ingress controller 恰好只拉起了 2 個(gè) Pod 副本且分布在node-worker-03和node-worker-04上。當(dāng)試圖驅(qū)逐node-worker-03上的 Pod 時(shí)驅(qū)逐控制器Eviction API發(fā)現(xiàn)若殺死該 Pod在新的 Pod 成功調(diào)度并通過(guò) ReadinessProbe 就緒之前集群里可用 Pod 數(shù)量就會(huì)降為 1違背了 minAvailable: 2 的強(qiáng)約束于是驅(qū)逐 API 拒絕執(zhí)行操作。此外新的節(jié)點(diǎn)已經(jīng)被打上了NoSchedule污點(diǎn)導(dǎo)致驅(qū)逐控制器陷入了無(wú)終止條件的死循環(huán)。如果使用--force強(qiáng)刪 Pod可能會(huì)在流量高峰期引發(fā)入口網(wǎng)關(guān)的瞬間丟包。找出廢棄 API 存量資源與 PodDisruptionBudget 盲區(qū)。導(dǎo)致升級(jí)卡頓的另一個(gè)隱患是跨版本升級(jí)時(shí)被移出支持Deprecated Removed的 API 版本。在 K8s 升級(jí)演進(jìn)中官方會(huì)定期移除老舊的 API Group。例如從 1.25 版本開(kāi)始autoscaling/v2beta1、poddisruptionbudget/v1beta1以及k8s.gcr.io鏡像倉(cāng)庫(kù)域名均被移出支持。部分團(tuán)隊(duì)在使用 Helm 或 kubectl 部署應(yīng)用時(shí)歷史上殘留了帶有舊apiVersion頭的 YAML 存根。升級(jí)前雖然 Pod 依然可以正常運(yùn)行但一旦 API Server 升級(jí)完成帶有舊 API 版本的 Helm Release 在試圖執(zhí)行helm upgrade或回滾時(shí)會(huì)因?yàn)?API 無(wú)法識(shí)別導(dǎo)致失敗。因此在升級(jí)之前必須全面掃清集群內(nèi)部所有的 PDB 邏輯死鎖與廢棄 API 存根。用 Go 編寫(xiě)集群升級(jí)前廢棄 API 自動(dòng)化掃描校驗(yàn)工具。為了避免升級(jí)前人工查閱大量 YAML 文件團(tuán)隊(duì)使用 Go 語(yǔ)言配合 K8s Discovery Client 編寫(xiě)了一個(gè)自動(dòng)化預(yù)檢與 PDB 死鎖掃描校驗(yàn)工具package main import ( context fmt os path/filepath time metav1 k8s.io/apimachinery/pkg/apis/meta/v1 k8s.io/client-go/discovery k8s.io/client-go/kubernetes k8s.io/client-go/tools/clientcmd k8s.io/client-go/util/homedir ) func main() { var kubeconfig string if home : homedir.HomeDir(); home ! { kubeconfig filepath.Join(home, .kube, config) } config, err : clientcmd.BuildConfigFromFlags(, kubeconfig) if err ! nil { fmt.Printf(無(wú)法加載 kubeconfig: %v\n, err) os.Exit(1) } clientset, err : kubernetes.NewForConfig(config) if err ! nil { fmt.Printf(創(chuàng)建 ClientSet 失敗: %v\n, err) os.Exit(1) } discoveryClient, err : discovery.NewDiscoveryClientForConfig(config) if err ! nil { fmt.Printf(創(chuàng)建 DiscoveryClient 失敗: %v\n, err) os.Exit(1) } ctx, cancel : context.WithTimeout(context.Background(), 1*time.Minute) defer cancel() fmt.Println( 1. 掃描集群內(nèi)部 PodDisruptionBudget (PDB) 死鎖隱患 ) pdbs, err : clientset.PolicyV1().PodDisruptionBudgets().List(ctx, metav1.ListOptions{}) if err ! nil { fmt.Printf(獲取 PDB 失敗: %v\n, err) } else { for _, pdb : range pdbs.Items { // 判別邏輯若 minAvailable 設(shè)為了 100% 或與期望 Pod 數(shù)相等可能導(dǎo)致 Drain 阻塞 if pdb.Spec.MinAvailable ! nil { fmt.Printf([警告 PDB] 命名空間: %s, 名稱: %s, MinAvailable: %s (請(qǐng)核實(shí)是否有足夠副本用于驅(qū)逐)\n, pdb.Namespace, pdb.Name, pdb.Spec.MinAvailable.String()) } } } fmt.Println(\n 2. 檢查 API Server 暴露的 API Group 組版本 ) apiGroups, err : discoveryClient.ServerGroups() if err ! nil { fmt.Printf(獲取 API 組失敗: %v\n, err) os.Exit(1) } deprecatedGroups : map[string]string{ extensions/v1beta1: 已在 1.22 移出支持請(qǐng)遷移至 networking.k8s.io/v1, autoscaling/v2beta1: 已在 1.25 移出支持請(qǐng)遷移至 autoscaling/v2, policy/v1beta1: 已在 1.25 移出支持請(qǐng)遷移至 policy/v1, } for _, group : range apiGroups.Groups { for _, version : range group.Versions { gv : version.GroupVersion if replacement, isDeprecated : deprecatedGroups[gv]; isDeprecated { fmt.Printf([嚴(yán)重警告 廢棄 API] 發(fā)現(xiàn)存量 API 版本: %s - 解決方案: %s\n, gv, replacement) } } } fmt.Println(\n掃描完成請(qǐng)?zhí)幚硗戤吷鲜鲲L(fēng)險(xiǎn)點(diǎn)后再發(fā)起 K8s 控制平面升級(jí)。) }這段工具的核心邏輯在于調(diào)用 Discovery Client 獲取當(dāng)前 K8s 控制平面注冊(cè)的 API 組與資源映射比對(duì)內(nèi)嵌的棄用規(guī)則庫(kù)輸出集群中的合規(guī)性死角。在 PDB 檢測(cè)模塊中工具會(huì)列舉出所有可能觸發(fā)Cannot evict pod堵塞的預(yù)警配置方便運(yùn)維在驅(qū)逐節(jié)點(diǎn)前提前介入調(diào)優(yōu)。演練 Kubernetes 控制平面升級(jí)時(shí)的檢查與保護(hù)動(dòng)作。在掃清配置死角后團(tuán)隊(duì)將升級(jí)流程標(biāo)準(zhǔn)化為帶有檢查點(diǎn)的 Shell 執(zhí)行腳本#!/usr/bin/env bash set -euo pipefail echo 升級(jí)前置檢查 1: 掃描棄用 API 與 PDB 死鎖 go run /opt/tools/k8s_upgrade_checker.go echo 升級(jí)前置檢查 2: 驗(yàn)證 etcd 數(shù)據(jù)備份狀態(tài) ETCDCTL_API3 etcdctl --endpointshttps://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-snapshot-pre-upgrade.db echo 執(zhí)行控制平面 upgrade plan 評(píng)估 kubeadm upgrade plan echo 依次安全驅(qū)逐節(jié)點(diǎn)流量 (設(shè)置超時(shí)保護(hù)) TARGET_NODEnode-worker-03 kubectl drain ${TARGET_NODE} \ --ignore-daemonsets \ --delete-emptydir-data \ --timeout5m || { echo CRITICAL: Drain node ${TARGET_NODE} timed out! Rolling back cordon... kubectl uncordon ${TARGET_NODE} exit 1 } echo 執(zhí)行 Kubelet 升級(jí)與重啟 apt-get update apt-get install -y kubelet1.28.2-00 systemctl restart kubelet kubectl uncordon ${TARGET_NODE} echo Node ${TARGET_NODE} upgraded and uncordoned successfully.升級(jí) Kubernetes 需要嚴(yán)謹(jǐn)?shù)牟襟E控制。通過(guò)在升級(jí)前完成廢棄 API 的自動(dòng)化掃描、解除 PDB 死鎖限制并在驅(qū)逐節(jié)點(diǎn)時(shí)設(shè)置嚴(yán)格的--timeout回退保護(hù)機(jī)制有助于在大版本升級(jí)中保障業(yè)務(wù)平滑演進(jìn)。