前的風(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í)踐中常見隱患除了工具或組件報(bào)錯(cuò)外還包括平時(shí)未顯現(xiàn)、升級(jí)時(shí)觸發(fā)的配置死鎖與廢棄 API 盲區(qū)。本文復(fù)盤生產(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)維人員通過診斷命令查看 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)度并通過 ReadinessProbe 就緒之前集群里可用 Pod 數(shù)量就會(huì)降為 1違背了 minAvailable: 2 的強(qiáng)約束于是驅(qū)逐 API 拒絕執(zhí)行操作。此外新的節(jié)點(diǎn)已經(jīng)被打上了NoSchedule污點(diǎn)導(dǎo)致驅(qū)逐控制器陷入了無終止條件的死循環(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 版本開始autoscaling/v2beta1、poddisruptionbudget/v1beta1以及k8s.gcr.io鏡像倉庫域名均被移出支持。部分團(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 無法識(shí)別導(dǎo)致失敗。因此在升級(jí)之前必須全面掃清集群內(nèi)部所有的 PDB 邏輯死鎖與廢棄 API 存根。用 Go 編寫集群升級(jí)前廢棄 API 自動(dòng)化掃描校驗(yàn)工具。為了避免升級(jí)前人工查閱大量 YAML 文件團(tuán)隊(duì)使用 Go 語言配合 K8s Discovery Client 編寫了一個(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(無法加載 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ī)則庫輸出集群中的合規(guī)性死角。在 PDB 檢測模塊中工具會(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控制。通過在升級(jí)前完成廢棄 API 的自動(dòng)化掃描、解除 PDB 死鎖限制并在驅(qū)逐節(jié)點(diǎn)時(shí)設(shè)置嚴(yán)格的--timeout回退保護(hù)機(jī)制有助于在大版本升級(jí)中保障業(yè)務(wù)平滑演進(jìn)。