升級 Kubernetes 前,怎麼提前知道哪裡會爆
前言:升級當天才發現,就太晚了
升級 Kubernetes 真正花時間的,從來不是那三行 kubeadm 指令。
是升級前那份「到底什麼東西會爆」的清單。
而會爆的東西分成三種,它們壞掉的方式完全不一樣:
| 會爆的東西 | 長什麼樣 | 什麼時候發現 |
|---|---|---|
| ① 元件版本不相容 | Cilium 不支援新版 K8s、Helm chart 裝不上去 | 升級當下就失敗 |
| ② 你的 YAML 用了被移除的 API | apiVersion 在新版已經不存在,資源直接建不起來 |
升級後才發現,而且是安靜地壞** |
| ③ Feature gate 換階段了 | 你依賴的功能預設值變了,或開關直接被移除 | 行為變了但沒有錯誤訊息,最難查 |
第一種是大家都會想到的,但它其實最不危險——因為它會當場失敗,你馬上知道。
真正陰險的是第二、三種。你的 Deployment 昨天還好好的,升級完之後某天有人重新 apply 一次,才發現 apiVersion 已經不被接受了;或是某個功能的預設值悄悄從關變成開,三天後某個 Pod 重啟才爆出來。
這篇文章要教的就是怎麼在升級前,把這三種都提前抓出來:
- 元件版本 → 認出它屬於哪一種「相容性模型」,就知道去哪一頁查
- Deprecated API → 用工具掃,而且要在 CI 就擋掉
- Feature gate → 看懂 Alpha / Beta / GA 的生命週期,知道哪些預設值會變
[!NOTE] 這篇文章假設你已經跑過 Kubernetes
你知道叢集分成 control plane 和 node、大概知道 Pod 和 Service 在做什麼、用過helm install。但你沒有親手升級過一次 minor version。文章裡出現的專有名詞我都會就地解釋。
[!TIP] 這篇也涵蓋 host OS 層
實務上升級 Kubernetes 常常伴隨著升級 Ubuntu(kernel、FRR 這些)。這兩件事會互相牽制,所以放在同一篇講。
一、先搞懂 Kubernetes 自己的規矩
在擔心外掛之前,先處理 Kubernetes 本體。因為外掛的相容性都是建立在你的 K8s 版本之上——你得先知道自己能升到哪,才有辦法回頭問外掛「你支不支援」。
Kubernetes 官方訂了一份版本歪斜政策(Version Skew Policy),規定叢集裡各個元件的版本之間可以差多少。這份政策不是建議,是官方測試涵蓋的範圍;超出範圍的組合,出事了官方不負責。
1-1. 一次只能升一個 minor version
這是最重要、也最常被低估的一條規則。
Kubernetes 的版號是 1.36.2 這種格式,中間那個 36 叫 minor version(次版本),最後的 2 叫 patch version(修補版本)。kubeadm(官方的叢集安裝/升級工具)一次只允許你升一個 minor version,不能跨版本跳躍。
也就是說,如果你現在在 1.31,想升到 1.36,你不是升一次,是升五次:
flowchart LR
A["1.31"] --> B["1.32"] --> C["1.33"] --> D["1.34"] --> E["1.35"] --> F["1.36"]
而每一跳,都是一次完整的升級流程:升 control plane、升 node、驗證、觀察。這就是為什麼升級 Kubernetes 是「專案」而不是「工單」。
[!WARNING] patch 版本可以跳,minor 版本不行
從 1.31.2 直接升到 1.31.9 沒問題(同一個 minor 內的 patch)。但 1.31 → 1.33 一定要中間停一次 1.32,沒有捷徑。
1-2. 元件之間可以差多少
升級不是一瞬間完成的。你升完 control plane 之後,node 上的 kubelet 可能還是舊版,這個「暫時不一致」的狀態必須合法。以下是官方允許的落後範圍(以 kube-apiserver 為基準):
| 元件 | 可以比 kube-apiserver 新嗎? | 最多可以落後幾個 minor? |
|---|---|---|
kubelet |
❌ 絕對不行 | 3 個 |
kube-proxy |
❌ 絕對不行 | 3 個 |
kubectl |
✅ 可以新 1 個 | 1 個 |
kube-apiserver(HA 多實例之間) |
— | 彼此不得差超過 1 個 |
舉個實例:當你的 kube-apiserver 是 1.36 時,
kubelet可以是 1.36、1.35、1.34、1.33 —— 但不能是 1.37kubectl可以是 1.37、1.36、1.35 —— 它是唯一被允許「比叢集新」的- 如果你跑 HA(多台 control plane),升級過程中最舊和最新的 apiserver 不能差超過一個 minor
kubectl 之所以特別,是因為它只是你電腦上的本地 CLI 工具,透過 REST API 跟叢集溝通,本身不是跑在叢集裡的元件——所以它的規則寬鬆得多。
[!TIP] 這條規則替你買到了時間
kubelet 可以落後三個 minor,意味著你不需要在升完 control plane 的當天就把所有 node 升完。你可以先升 control plane,觀察幾天,再分批滾動升級 node。這是升級策略上很重要的一個緩衝。
1-3. 升級順序:為什麼是這個順序
官方規定的元件升級順序是固定的:
flowchart TD
A["1 · kube-apiserver"] --> B["2 · controller-manager
scheduler
cloud-controller-manager"]
B --> C["3 · kubelet"]
C --> D["4 · kube-proxy"]
為什麼 kube-apiserver 一定要打頭陣?
因為它是整個叢集的前門,也是唯一直接跟 etcd 溝通的元件(etcd 是叢集存放所有狀態的資料庫)。其他所有元件——controller-manager、scheduler、甚至每個 node 上的 kubelet——都不會直接碰 etcd,一律得透過 apiserver。
既然大家都要跟它講話,而且規則是「誰都不能比它新」,那它就必須第一個升。先升它,其他元件才有往上升的空間;反過來先升 controller-manager,你立刻就違反了「不能比 apiserver 新」這條規則。
第二階段的三個元件(controller-manager、scheduler、cloud-controller-manager)之間沒有順序要求,可以任意順序甚至同時升。
[!NOTE] 這一頁值得你每次升級前重看一遍
版本歪斜政策的官方頁面在 https://kubernetes.io/releases/version-skew-policy/。這些數字會隨版本演進調整(例如 kubelet 的落後上限在 1.25 之前只有 2 個 minor,之後才放寬到 3 個),所以不要背,要查。
二、五種相容性模型
搞定 Kubernetes 本體之後,才輪到外掛。而這裡就是前言講的那件煩人的事:每個專案宣告相容性的方式都不一樣。
我把常見的十幾個元件歸納成五種模型。認出一個元件屬於哪一型,你就知道該去哪裡找答案。
flowchart TD
Q{"這個元件怎麼宣告相容性?"}
Q --> M1["① 版號對齊型
版號跟著 K8s 走"]
Q --> M2["② 機器可讀型
寫在設定檔,工具會擋"]
Q --> M3["③ 公告區間型
每次發版寫死一段範圍"]
Q --> M4["④ 政策繼承型
不列表,繼承 K8s 政策"]
Q --> M5["⑤ 不在體系內
查了也沒用,要查別的"]
[!IMPORTANT] 離線環境請看每一節的「只能看網頁時」
很多公司的正式環境不能連外網,helm show chart、gh這類指令直接不能用。所以下面每一種模型我都會給兩條路:能連網時最快的指令,以及只能開瀏覽器看 GitHub/官網時,該打開哪個檔案、看哪一行。後者才是封閉環境的主力。
模型 ①:版號對齊型 —— CRI-O
最幸運的一種。這類專案直接讓自己的 minor 版號跟 Kubernetes 對齊,你不用查表,看版號就知道。
CRI-O 是容器執行時期(container runtime,實際負責在 node 上跑起容器的程式,kubelet 透過 CRI 介面指揮它)。它的規則非常乾脆:
1 | Kubernetes 1.36.x → CRI-O 1.36.x |
CRI-O 官方 README 寫明它跟隨 Kubernetes 的發布週期與版本歪斜政策,每個 release-1.x 分支對應 Kubernetes 的 release-1.x。
只能看網頁時怎麼確認:
- 打開 https://github.com/cri-o/cri-o,README 往下找 Compatibility matrix: CRI-O ⬄ Kubernetes 這一段。
- 更直接的做法是看分支清單:https://github.com/cri-o/cri-o/branches ,如果你要升到 K8s 1.36,就確認
release-1.36這個分支存在。存在就代表官方有出對應版本。 - 要拿具體 patch 版號,去 https://github.com/cri-o/cri-o/releases 找
v1.36.x最新的那一個。
[!TIP] 版號對齊型的隱藏好處
這型元件不需要「升級前特別查」,因為答案是自明的。你只要確保它跟 K8s 同步往前走就好。containerd 則不屬於這一型——它有自己的版號節奏(1.7.x、2.x),要當成模型 ③ 來查。
特例補充:CoreDNS 由 kubeadm 自動決定
CoreDNS 是叢集內部的 DNS 服務(讓 Pod 可以用 service-name.namespace 這種名字互相找到對方)。它看起來像版號對齊型,實際上更特別:你通常不用自己決定版本,kubeadm 會幫你決定。
kubeadm 內建一個叫 corefile-migration 的函式庫,升級時它會:
Deprecated()— 檢查你的 Corefile(CoreDNS 的設定檔)有沒有用到即將棄用的 plugin,有就警告,但還是讓你繼續Unsupported()— 檢查有沒有用到無法自動遷移的 plugin,有就中止Migrate()— 真正把舊格式改寫成新版相容的格式- 詢問你是否套用
這個機制存在的原因很實際:CoreDNS 自己演進時會棄用、改名、移除 plugin(例如 proxy 後來被 forward 取代)。如果叢集跑了兩三年,Corefile 裡可能還留著舊設定,kubeadm 若直接用新版覆蓋上去,你自訂的 upstream DNS 或 stub domain 會直接失效。
所以 kubeadm 能升到哪個 CoreDNS 版本,取決於它內建的 corefile-migration 版本。
只能看網頁時怎麼確認:
- 對照表:https://github.com/coredns/deployment/blob/master/kubernetes/CoreDNS-k8s_version.md
- 想知道某個 K8s 版本內建哪個 corefile-migration,打開 kubernetes/kubernetes 的
go.mod,搜尋corefile-migration - 想知道 kubeadm 預設會裝哪個 CoreDNS,看
cmd/kubeadm/app/constants/constants.go裡的CoreDNSVersion - 或直接看該版本的 CHANGELOG,通常會有
Upgraded github.com/coredns/corefile-migration to vX.X.X這種條目
模型 ②:機器可讀型 —— Grafana Helm chart
這型最可靠,因為它不是文件上的建議,是工具會實際擋你的硬規則。
Helm chart 有一個官方標準欄位 kubeVersion,寫在 Chart.yaml 裡。如果你的叢集版本不符合,helm install / helm upgrade 會直接報錯拒絕安裝——不是廠商口頭說說而已。
Grafana 社群維護的 chart 目前就寫死:
1 | # Chart.yaml |
注意 kubeVersion 和 appVersion 是兩件事:chart 版本、Grafana 應用版本、K8s 門檻,三個號碼互相獨立。 你要查的是 kubeVersion。
只能看網頁時怎麼確認:
- 找到 chart 的 GitHub repo,例如 https://github.com/grafana-community/helm-charts/blob/main/charts/grafana/Chart.yaml
- 打開
Chart.yaml,找kubeVersion:那一行 - 如果沒有這個欄位,代表 chart 沒設限制——那就退回去看 CHANGELOG 或 release notes
能連網時的捷徑:
1 | # 直接問 chart 的 kubeVersion |
[!TIP] 升級前先乾跑一次,不用動到叢集
這招對離線環境也有用(只要你本機有 chart 檔案)。helm template可以指定假的 K8s 版本來渲染,測出 chart 裡有沒有用到已被移除的 API:
1 helm template grafana-community/grafana --kube-version 1.36.0如果 chart 裡還在用 1.36 已經移除的 API(例如舊版
PodSecurityPolicy、舊版IngressapiVersion),這行會直接報錯告訴你哪裡壞掉——比升級當天才發現 CrashLoopBackOff 好太多了。
Grafana 的另一條路:Grafana Operator。 如果你不是用 Helm chart 裝單一 Grafana,而是用 Operator(以 CRD 宣告式管理多個 instance),判斷邏輯不一樣——Operator 本身要一直呼叫 K8s API,依賴度高很多。這時候真正決定相容性的是它用的 controller-runtime 與 client-go 版本,要去它的 go.mod 查 sigs.k8s.io/controller-runtime,再對照 controller-runtime 自己的相容表。
模型 ③:公告區間型 —— Istio、cert-manager、ECK
最常見的一型,也最需要你動手查。這類專案每次發版都會明確寫出「本版支援 K8s 1.x 到 1.y」,範圍固定,過期不候。
特徵是支援區間會滾動前進:新版往前支援新的 K8s,同時砍掉最舊的。所以你不能只查一次就記著,每次升級都要重查。
以 Istio 為例(服務網格,負責流量管理與 mTLS):
| Istio 版本 | 支援的 K8s 範圍 |
|---|---|
| 1.30 | 1.31 – 1.36 |
| 1.29 | 1.31 – 1.35 |
cert-manager 更細緻,它把相容性分成兩個等級:
- supported:官方會修這個組合的 bug
- tested:實際有跑 e2e 測試
兩者不一定相同。例如某個版本可能宣告 supported 到 1.36,但 tested 只到 1.35——中間那段是「理論上可以,但沒人實測過」。正式環境請以 tested 為準。
只能看網頁時怎麼確認:
| 專案 | 該打開哪一頁 |
|---|---|
| Istio | https://istio.io/latest/docs/releases/supported-releases/,或各版本的 Announcing 公告文 |
| cert-manager | https://cert-manager.io/docs/releases/,看 Supported / Tested 兩欄 |
| ECK | https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s 的支援版本段落 |
Istio 特別注意:升級模式會影響你能跳多遠
Istio 有兩種升級方式,而且它們的跨版本能力不同,這會直接影響你的升級計畫:
| 比較項目 | Revision-based(金絲雀升級) | In-place(原位覆蓋) |
|---|---|---|
| 運作機制 | 新舊 control plane 同時並存,用 revision 標籤區分(如 1-30-0) |
直接把舊的覆蓋掉 |
| 跨版本能力 | 可跨兩個 minor 直升(如 1.28 → 1.30) | 必須逐級升,不能跳 |
| 服務影響 | 零停機,可逐個 namespace 驗證 | 覆蓋瞬間可能短暫中斷 |
| 回滾 | 極簡單,標籤改回去就好 | 困難,要重新部署舊版 manifest |
為什麼 Revision-based 可以跨版本?因為新版 istiod 是獨立部署的,不影響舊的。你可以先挑一個 namespace 貼上 istio.io/rev=1-30-0 標籤,把它的流量交給新 control plane 驗證,確認沒問題再逐步遷移其他工作負載。In-place 則是一瞬間全換,若中間隔了兩個 minor,舊的 sidecar proxy 很容易出錯,所以官方限制必須逐級。
實務建議:正式環境用 Revision-based,開發/測試環境可以用 In-place(省資源、流程簡單、壞了也沒關係)。但如果叢集資源吃緊(Revision-based 需要同時跑兩套 control plane)、或公司政策禁止同叢集出現多個 control plane,那就只能選 In-place。
[!TIP] 升級 Istio 前先跑這個
1 istioctl x precheck它會檢查 CRD 相容性、現有流量設定跟新版本會不會衝突,把問題在升級前就抓出來。
模型 ④:政策繼承型 —— OPA Gatekeeper
這型最容易讓人以為「文件寫得很爛,怎麼查不到相容表」——其實不是沒寫,是它刻意不列表。
OPA Gatekeeper(用 policy 攔截不合規資源的 admission controller)官方的說法是:
Gatekeeper is assumed to be compatible with the current Kubernetes Supported Versions per Kubernetes Supported Versions policy.
翻成白話:「Kubernetes 官方目前支援哪些版本,我就支援哪些。」
所以判斷邏輯變成兩步:
- 你的 Gatekeeper 版本還在支援期內嗎?(Gatekeeper 維護 n 和 n-1 兩個 minor,大約三個月一版)
- 你的 K8s 版本還在官方支援期內嗎?(目前是 1.34 / 1.35 / 1.36)
兩個都是「是」,官方就認定這個組合相容。
這帶來一個很實用的結論:當 Kubernetes 發布新的 minor 版本時,只要你手上的 Gatekeeper 還沒過期,它就自動被認定相容於新版 K8s,你不需要特地等 Gatekeeper 發新版才能升 K8s。
只能看網頁時怎麼確認:
- 支援政策:https://github.com/open-policy-agent/gatekeeper/blob/master/docs/Release_Management.md
- 目前有哪些版本還在支援期:https://github.com/open-policy-agent/gatekeeper/releases(看最新的兩個 minor)
- K8s 這邊的支援期:https://kubernetes.io/releases/
[!WARNING] 「政策繼承」不等於「永遠沒事」
官方文件同時寫明:如果你用的是不受支援的組合,風險自負(“you are using it at your own risk”)。政策繼承型的元件,真正的風險在於你的版本悄悄過期了卻沒發現——因為它不會有一張表跳出來提醒你。
模型 ⑤:跨層依賴型 —— Kernel、host FRR、Fluentd
前面四型都還在 Kubernetes 的世界裡。這一型不是——它們的版本問題不在 K8s 相容表上,因為 K8s 根本不知道它們存在。
而這正是很多升級專案翻車的地方:你把叢集層全部查得乾乾淨淨,結果掛在一個從來沒進過清單的東西上。
5-1. Linux Kernel:沒有「K8s 對應 kernel 版本」這張表
你不會找到一張寫著「K8s 1.36 需要 kernel 6.x」的表,因為沒有這種東西。實際的判斷方式是:
flowchart TD
A["你要的 kernel 版本"] --> B["CRI 的最低需求
(CRI-O / containerd)"]
A --> C["CNI 的最低需求
(Cilium eBPF 要 ≥ 5.10)"]
A --> D["你要用的 K8s 功能
(見下表)"]
B & C & D --> E["取三者之中
最高的那個"]
Kubernetes 官方文件是按功能列 kernel 需求的,不是按版本。幾個實務上會踩到的:
| 功能 | 最低 kernel |
|---|---|
| cgroup v2 | 建議 5.8+(低於 5.2 不建議) |
| kube-proxy nftables mode(K8s 1.36) | 5.13+ |
| Pod user namespaces | 6.5+ |
| Recursive read-only mounts | 5.12+ |
| PSI(Pressure Stall Information) | 4.20+,且需 CONFIG_PSI=y |
Ubuntu 22.04 → 24.04 的實際情況:
| GA kernel | HWE kernel | |
|---|---|---|
| Ubuntu 22.04 LTS | 5.15 | 6.8 |
| Ubuntu 24.04 LTS | 6.8 | 6.17 |
HWE(Hardware Enablement) 是 Ubuntu 讓舊 LTS 也能用新 kernel 的機制——你不用等下一個 LTS,就能把 22.04 的 kernel 從 5.15 拉到 6.8。Server 安裝預設走 GA kernel,HWE 要自己選;Desktop 預設就是 HWE。
這對你的升級計畫有個很實際的影響:如果卡住你的只是 kernel 版本(例如要用 Cilium 的 eBPF dataplane,需要 ≥ 5.10),你可能不需要整個 OS 升到 24.04,先上 22.04 的 HWE kernel 就夠了。
[!WARNING] 光看版號可能會誤判
Kubernetes 官方特別提醒:RHEL、Ubuntu、SUSE 這些發行版經常把新功能 backport 到舊 kernel。所以 Ubuntu 的 5.15 不等於原生 upstream 的 5.15,它可能已經含有某些 6.x 才有的功能。反過來也一樣——不要因為版號看起來夠新就假設功能一定在。查當前版本:
uname -r;查 Ubuntu 這邊的支援週期:https://ubuntu.com/kernel/lifecycle
5-2. host 上的 FRR:不在任何 K8s 清單裡
FRR(FRRouting) 是跑 BGP 等路由協定的軟體。如果你是直接裝在 host OS 上、用 systemd 管理(而不是讓 MetalLB 或 Calico 把它包在 Pod 裡),那它跟 Kubernetes 是兩個完全獨立的世界:
- K8s 的任何相容表都不會提到 FRR
- FRR 官方也不會說「本版支援 K8s 1.36」——這個問題對它沒有意義
- 但它一掛,你的叢集對外路由就斷了
所以 FRR 的版本管理要當成作業系統套件來看,而不是叢集元件。而它跟這次升級的關聯點在於:你升 Ubuntu 的時候,apt 來源和套件版本會跟著動。
查目前版本(這些指令在離線環境也能跑,因為都是查本機):
1 | # 查 FRR 執行中的版本 |
最後一條特別重要:如果你當初是加了 deb.frrouting.org 這個官方來源來裝比較新的 FRR,升級 Ubuntu 時這個來源可能會失效或被停用(因為 codename 從 jammy 變成 noble),結果 apt 悄悄把你的 FRR 換成 Ubuntu 內建的舊版。這種降版很容易被忽略。
只能看網頁時: FRR 的版本與變更看 https://github.com/FRRouting/frr/releases,官方文件在 https://docs.frrouting.org/。要確認某個 Ubuntu 版本內建哪個 FRR,可以查 Ubuntu 的套件頁面。
[!NOTE] 如果你的 FRR 是被包在 Pod 裡的,那是另一回事
有些環境的 FRR 不是裝在 host,而是 MetalLB 或 Calico 把 FRR 當 sidecar 塞進自己的 Pod。那種情況你查不到、也不該查「FRR 支不支援 K8s」,你要查的是「MetalLB 這個版本包了哪個 FRR」——去 https://metallb.universe.tf/release-notes/ 搜FRR關鍵字。MetalLB 目前已改用獨立的 frr-k8s 專案,舊的內嵌 FRR mode 已標記為 deprecated。兩種情況的共同點是:FRR 的版本都不是你直接決定的,差別只在決定它的是 apt 還是 MetalLB。
5-3. Fluentd:綁的是 container runtime,不是 K8s
Fluentd 是日誌收集工具。它的核心程式碼完全不在乎你跑的是 K8s 1.31 還是 1.36——對它來說 K8s 只是另一種文字來源,所以 Fluentd 官方不會訂「支援 K8s 1.36」這種規格。
真正決定它能不能正常運作的是兩個東西:
fluent-plugin-kubernetes_metadata_filter—— 這個 plugin 負責去問 API Server 拿 Pod 名稱、namespace、labels。K8s 升級時如果 API 版本或 RBAC 架構變了,plugin 太舊就會呼叫失敗,導致日誌抓不到或沒有標籤。- container runtime 的日誌格式 —— 這是最容易被忽略的:
- 舊時代(Docker Engine):日誌是 JSON 格式
- 現在(containerd / CRI-O):日誌是 CRI 格式,像
2026-08-22T10:00:00.123456789Z stdout F log content
所以你要查的不是 Fluentd 官網,而是 fluent/fluentd-kubernetes-daemonset 這個 repo——官方把「Fluentd + metadata plugin + 對應 runtime 的 parser」打包好的 image 都在那裡,答案藏在 image tag 裡:
1 | # 你的 node 用 containerd |
tag 的命名邏輯拆開來看:
1 | v1.18 -debian -elasticsearch7 -containerd -1.0 |
只能看網頁時: 打開 https://github.com/fluent/fluentd-kubernetes-daemonset,看 README 列出的可用 tag,挑跟你 runtime 相符的那一個。
三、實戰版本對照表(K8s 1.31 → 1.36)
前面講完方法,這一節給你查證過的實際版號。
[!IMPORTANT] 查核日期:2026-08-22
這張表一定會過期。 CRI-O、Cilium、Istio 這些專案每幾個月就發新版,支援區間會往前滾動。所以請把這張表當成「範例」而不是「答案」——重點是最後一欄的來源連結,那才是永遠有效的東西。過期了就照第二節的方法自己重查一次。
3-1. Kubernetes 本體
先確認你的目標版本還在支援期內。截至查核日,官方維護最近三個 minor:
| K8s 版本 | 最新 patch | 支援狀態 | EOL |
|---|---|---|---|
| 1.36 | 1.36.2 | ✅ 支援中(最新) | 2027-06-28 |
| 1.35 | 1.35.6 | ✅ 支援中 | 2027-02-28 |
| 1.34 | 1.34.9 | ✅ 支援中 | 2026-10-27 |
| 1.33 及更早 | — | ❌ 已 EOL | — |
來源:https://kubernetes.io/releases/
[!WARNING] 1.31~1.33 已經沒有官方支援了
如果你現在還在 1.31,你不只是「落後」,是已經沒有安全性修補。升級路徑仍然要一個一個跳(1.31→1.32→1.33→1.34→1.35→1.36,共五跳),中間那幾版只是「路過」,不要停留。
3-2. K8s 叢集層元件
| 元件 | 模型 | 對應 K8s 1.31 → 1.36 的版本 | 怎麼判斷 | 來源連結(佐證) |
|---|---|---|---|---|
| CRI-O | ① | 版號直接對齊: K8s 1.36 → v1.36.3K8s 1.35 → v1.35.6K8s 1.34 → v1.34.11 |
minor 跟 K8s 一致,取該分支最新 patch | releases · branches |
| CoreDNS | ①特例 | K8s 1.31 → v1.11.3K8s 1.32 → v1.11.3K8s 1.33 → v1.12.0K8s 1.36 kubeadm 預設 → v1.14.6 |
kubeadm 依內建的 corefile-migration 自動決定 | CoreDNS-k8s_version.md · kubeadm constants.go |
| Cilium | ③ | v1.20.1 → e2e 測過 K8s 1.33–1.36 |
每個 minor 約支援 3–4 個相鄰 K8s minor | compatibility 頁 · releases |
| Istio | ③ | 1.30.x → K8s 1.31–1.361.29.x → K8s 1.31–1.35 |
每版公告寫死區間;control plane 可比 data plane 新一版 | supported-releases · releases |
| cert-manager | ③ | v1.21.x → supported/tested K8s 1.33–1.36v1.20.x → supported K8s 1.32–1.35 |
分 supported 與 tested 兩級,正式環境看 tested | releases 頁 |
| ECK(Elastic) | ③ | 3.4+(最新 v3.5.0)→ K8s 1.31–1.36 |
官方列出 K8s 與各雲端託管服務的支援範圍 | ECK 文件 · releases |
| OPA Gatekeeper | ④ | 最新 v3.23.0;只要版本在支援期內,即視為相容所有受支援的 K8s 版本(1.34–1.36) |
不列表,繼承 K8s 支援政策;維護 n / n-1 | Release_Management.md · releases |
| Prometheus Operator | ② | v0.84.0+(最新 v0.93.1)→ 需 K8s ≥ 1.25 |
透過 client-go 溝通,相容性參照 client-go 對照表 | compatibility 文件 · releases |
| Grafana(Helm chart) | ② | chart 12.11.1(app 13.2.0)→ kubeVersion: ^1.25.0-0 |
看 Chart.yaml 的 kubeVersion,Helm 會硬擋 |
Chart.yaml |
| Fluentd | ⑤ | 不綁 K8s 版本,綁 container runtime containerd → v1.18-debian-containerd-1.0 |
看 image tag 裡的 runtime 欄位 | fluentd-kubernetes-daemonset |
[!NOTE] 為什麼有些欄位寫的是「規則」而不是「版號」
模型 ④ 和 ⑤ 的元件,本來就不存在「對應某個 K8s 版本」的版號。Gatekeeper 是政策繼承,Fluentd 綁的是 runtime。硬要在表格裡填一個版號反而是誤導——這也正是前言講的:不同元件回答問題的方式不一樣。
3-3. Host OS 層(Ubuntu 22.04 → 24.04)
這一層不在任何 K8s 相容表上,但同樣會讓升級翻車:
| 項目 | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS | 怎麼查 | 來源連結(佐證) |
|---|---|---|---|---|
| GA kernel | 5.15 | 6.8 | uname -r |
Ubuntu kernel lifecycle |
| HWE kernel | 6.8 | 6.17 | uname -r;server 預設走 GA,HWE 需自選 |
Ubuntu kernel lifecycle |
| K8s 對 kernel 的需求 | 依功能而定,非單一版號 cgroup v2 建議 ≥ 5.8;nftables mode(1.36)需 ≥ 5.13;user namespaces 需 ≥ 6.5 |
同左 | 對照你要啟用的功能逐項確認 | kernel-version-requirements |
| FRR(host 安裝) | 依 apt 來源而定 | 依 apt 來源而定 | vtysh -c "show version"dpkg -s frr | grep Versioncat /etc/apt/sources.list.d/*.list |
FRR releases · FRR 文件 |
[!TIP] 升 Ubuntu 前,先確認 kernel 是不是真的需要動
如果卡住你的只是某個功能的 kernel 門檻(例如 Cilium eBPF dataplane 要 ≥ 5.10),22.04 裝上 HWE kernel 就能拿到 6.8,跟 24.04 的 GA kernel 同版。先確認清楚,可以省下一次完整的 OS 升級。
3-4. 完全離線環境的實務提醒
如果你的公司網路看得到 GitHub 網頁、但下載會被擋(很常見),這張表的用法會有點不同:
- ✅ 能做的:所有「來源連結」欄的頁面都能用瀏覽器打開確認版號;查本機現況的指令(
uname -r、vtysh -c "show version"、dpkg -s frr、kubectl version)都能跑 - ❌ 不能做的:
helm show chart、helm repo update、gh這類要連外網的指令;直接apt install新版套件 - 💡 實務做法:先在能連網的環境把版號查齊、把 image 與 chart 準備好,再照公司規定的管道搬進內網。版本確認和實際下載是兩件事——確認可以先做完,這樣搬進來的東西才不會白搬。
[!WARNING] 離線環境最容易踩的坑:image 沒有一起搬
你確認了 CRI-O 要v1.36.3、CoreDNS 要v1.14.6,但如果內網的 registry 裡沒有這些 image,kubeadm upgrade會在拉 image 那一步卡死。升級前先用kubeadm config images list把需要的 image 清單印出來,確認內網 registry 都有了再開始。
1 kubeadm config images list --kubernetes-version v1.36.2
四、Deprecated API:升級後才安靜壞掉的東西
版本相容性的問題會當場失敗,你馬上知道。但 deprecated API 不是——它會在你完全沒察覺的情況下,把某個資源變成再也 apply 不上去的狀態。
4-1. 為什麼這件事這麼容易被漏掉
Kubernetes 的 API 有版本,寫在每個 YAML 最上面的 apiVersion:
1 | apiVersion: flowcontrol.apiserver.k8s.io/v1beta3 # ← 就是這一行 |
這些 API 版本會經歷 deprecated(棄用)→ removed(移除) 兩個階段:
flowchart LR
A["可正常使用"] --> B["Deprecated
還能用,但會回警告"]
B --> C["Removed
完全不能用"]
style B fill:#fff3cd,stroke:#856404,color:#000
style C fill:#f8d7da,stroke:#721c24,color:#000
危險就在中間那一段:deprecated 的 API 還是能正常運作,只是 API Server 會回一個警告 header。而警告很容易被忽略——尤其是透過 CI/CD 或 GitOps 自動 apply 的時候,根本沒人在看終端機輸出。
於是典型的翻車劇本是這樣的:
- 你的 Deployment 用了某個 beta API,跑了兩年都沒事
- 升級 K8s,那個 API 被移除了
- 既有的資源還在跑(已經存在的物件不會憑空消失)
- 三個月後有人改了一行設定重新 apply → 直接被拒絕
- 這時候已經沒人記得當初為什麼要用那個 apiVersion
[!WARNING] 升級不會刪掉你既有的資源,這正是它陰險的地方
API 被移除之後,已經存在的物件仍然可以透過新版 API 讀到(Kubernetes 保證物件能在版本之間無損轉換)。所以升級當下你看不出任何異狀,問題會延後到下一次有人動那個資源時才爆發。
4-2. 官方的棄用時程規則
好消息是這件事有明確的規則,不是隨時說砍就砍。Kubernetes 官方的 Deprecation Policy 規定:
| API 穩定度 | 棄用後還要撐多久才能移除 |
|---|---|
GA(如 apps/v1) |
不能在同一個 major 版本內移除(實務上等於不會被砍) |
Beta(如 v1beta3) |
棄用後至少 9 個月或 3 個 minor 版本(取較長者) |
Alpha(如 v1alpha1) |
任何一版都可能直接移除,不需預告 |
這張表告訴你一件很實用的事:如果你的 YAML 全都用 GA 版本的 API,你基本上不會被這個問題咬到。 真正的風險集中在 beta 和 alpha。
Beta 的 9 個月/3 個 minor 這個設計也有原因——它必須涵蓋官方支援的版本歪斜範圍,讓你有足夠時間在支援期內完成遷移。
[!TIP] 一個很好用的判斷捷徑
打開你的 YAML,看apiVersion有沒有出現beta或alpha字樣。
- 沒有 → 大致安全
- 有
beta→ 記下來,去查移除時程- 有
alpha→ 高風險,隨時可能消失
4-3. 三個查法(離線環境也能用)
查法一:官方的 Deprecated API Migration Guide
這是最權威的來源,一頁列完所有被移除的 API 以及該換成什麼:
https://kubernetes.io/docs/reference/using-api/deprecation-guide/
它是按「哪一版移除」分段的,例如 v1.32 這一段就寫明:
| 資源 | 被移除的版本 | 要改用 | 額外注意 |
|---|---|---|---|
| FlowSchema、PriorityLevelConfiguration | flowcontrol.apiserver.k8s.io/v1beta3 |
flowcontrol.apiserver.k8s.io/v1(v1.29 起可用) |
nominalConcurrencyShares 只在未指定時才預設為 30;明確填 0 不再被改成 30 |
注意最後一欄——遷移不見得只是把 apiVersion 換掉,欄位語意也可能變。這種細節只有官方遷移指南會寫。
這頁在離線環境完全可用,用瀏覽器打開就好。升級到哪一版,就把那一版的段落整段讀完。
查法二:問你自己的叢集(最準,但需要能連叢集)
從 K8s 1.19 開始,API Server 會為每個用到 deprecated API 的請求記錄 metric。如果你有 Prometheus,這是最貼近實際使用情況的查法:
1 | apiserver_requested_deprecated_apis |
這個 metric 帶有 removed_release 這個 label,直接告訴你「這個 API 會在哪一版被移除」。搭配 apiserver_request_total 可以看出到底是誰在呼叫:
1 | apiserver_requested_deprecated_apis{removed_release="1.36"} |
除了 metric,API Server 也會:
- 在回應加上
Warningheader - 在 audit log 裡標記
"k8s.io/deprecated":"true"
[!TIP] 這是唯一能抓到「誰在偷偷用」的方法
掃 YAML 檔只能找到你寫在 repo 裡的東西。但有些 controller、operator、或某個同事手動 apply 的資源,不會出現在你的 Git 裡。這個 metric 是唯一能抓到它們的方式。
查法三:用工具掃(推薦,可以進 CI)
有兩個成熟的開源工具,功能有重疊但定位不同:
| 工具 | 掃什麼 | 特色 |
|---|---|---|
| Pluto | 靜態 YAML 檔、Helm chart、Helm release | 區分 DEPRECATED(還能用)和 REMOVED(已不能用),適合放進 CI |
| kubent(kube-no-trouble) | 活的叢集、Helm v3、本機 manifest | 預設直接掃線上叢集,適合對現有環境做健檢 |
Pluto —— 掃你的 repo:
1 | # 掃一個目錄底下所有 manifest |
kubent —— 掃你的叢集:
1 | # 直接掃目前 kubeconfig 指向的叢集 |
[!IMPORTANT] Pluto 為什麼不直接問 API Server?
因為問了會被騙。你用舊的apiVersion建立資源,API Server 會自動幫你轉換成目前的儲存版本,所以你再讀回來時看到的是新版——舊的寫法就這樣被藏起來了。這就是為什麼掃原始 YAML 檔才是可靠的做法。這也是為什麼查法二(metric)和查法三(掃檔案)要一起用:前者抓執行時期真正的呼叫,後者抓 repo 裡的寫法。
4-4. 怎麼「提前卡控」:把檢查放進 CI
前面都是「發現問題」,這一節是不讓問題進來。
最有效的做法是在 CI pipeline 裡加一道 Pluto 檢查,讓任何用到已移除 API 的 PR 直接無法合併:
1 | # GitLab CI 範例 |
這裡有個很關鍵的設計決定:
[!TIP]
--target-versions要填「下一個版本」,不是「現在的版本」
如果你現在跑 1.34、下一步要升 1.35,就填v1.35.0。這樣新寫的 YAML 在還沒升級之前就會被擋下來,而不是等升級當天才發現一堆要改。更進取的做法是填再下一個版本,讓團隊提前兩個 minor 開始適應。
搭配 Helm 的乾跑(第二節提過的),可以形成雙重保險:
1 | # 用目標版本渲染 chart,如果用到已移除的 API 會直接報錯 |
離線環境的提醒:Pluto 和 kubent 都是單一 binary,沒有執行時期的外部依賴,所以只要能把 binary 想辦法搬進內網,就能離線使用——它們的判斷邏輯是內建的,不需要連網查資料庫。
五、Feature Gate:預設值悄悄改變的那些行為
第三種會爆的東西最難查,因為它不會有錯誤訊息。功能還在、API 還在,只是行為變了。
5-1. 什麼是 Feature Gate
Feature gate(功能開關) 是 Kubernetes 用來控制「某個功能要不要啟用」的開關,寫在各元件的啟動參數裡:
1 | # kubelet 或 kube-apiserver 的啟動參數 |
它存在的目的是讓新功能能夠逐步推出:先讓願意冒險的人開來試,穩定之後再讓所有人預設拿到。
5-2. 三個階段,以及每個階段的預設值
這是整節的重點——每個階段的預設值不一樣,而階段是會變的:
flowchart LR
A["Alpha
預設 false
要自己開"] --> B["Beta
預設 true
自動就開了"]
B --> C["GA / Stable
永遠 true
不能關"]
C --> D["開關被移除
再填就報錯"]
style A fill:#f8d7da,stroke:#721c24,color:#000
style B fill:#fff3cd,stroke:#856404,color:#000
style C fill:#d4edda,stroke:#155724,color:#000
style D fill:#e2e3e5,stroke:#383d41,color:#000
| 階段 | 預設值 | 穩定度 | 對你的意義 |
|---|---|---|---|
| Alpha | false(預設關閉) |
可能隨時變更或移除,不保證相容 | 你必須手動開才會生效;隨時可能消失 |
| Beta | true(預設啟用) |
API 與行為穩定,實作細節可能改 | ⚠️ 升級後可能自動獲得你沒預期的行為 |
| GA / Stable | 永遠啟用,無法關閉 | 完全支援 | 開關失去意義,準備被移除 |
Beta 那一列是最需要注意的:一個功能從 Alpha 升到 Beta 的那一刻,它的預設值從 false 變成 true。也就是說——
你什麼都沒改,只是升了一個 minor 版本,某個原本關著的功能就自己打開了。
這就是「行為變了但沒有錯誤訊息」的來源。
5-3. GA 之後開關會被移除——這是最容易踩的雷
很多人以為功能 GA 了就萬事大吉,但還有最後一步:開關本身會從程式碼裡被拿掉,通常在 GA 之後的一到兩個版本。
危險的情境是這樣的:
- 幾年前某個功能還是 Alpha,你在 kubelet 參數裡寫死
--feature-gates=SomeFeature=true - 它後來 GA 了,你的參數還留著,反正也沒差
- 某次升級,這個開關被移除了
- kubelet 因為「無法識別的 feature gate」直接啟動失敗
這個雷的可怕之處在於:壞的不是某個功能,是整個元件起不來。而且原因是你多年前加的一行參數。
[!WARNING] 升級前務必清點你手動設定的 feature gate
找出所有元件啟動參數裡的--feature-gates,逐一確認每個名字現在還存不存在。已經 GA 的就該刪掉——留著沒有任何好處,只是等著在未來某次升級炸掉。
1
2
3
4
5 # 檢查 static pod manifest 裡有沒有殘留的 feature gate
grep -r "feature-gates" /etc/kubernetes/manifests/
# 檢查 kubelet 設定
grep -r "featureGates" /var/lib/kubelet/config.yaml這幾條指令都是查本機,離線環境完全可用。
5-4. 怎麼查(離線可用)
官方的 feature gate 清單頁面把所有開關的狀態、起訖版本都列出來了:
https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/
表格的讀法是這樣:
1 | 特性名稱 預設值 階段 起始版本 結束版本 |
同一個功能會有多列,代表它在不同版本的不同階段。最後一欄(結束版本)是關鍵:有值代表這個階段到那一版為止,之後會進入下一階段(預設值可能就此改變)。
已經被移除的開關則另有一頁:
https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates-removed/
升級前的檢查動作:
- 用上面的
grep找出你所有手動設定的 feature gate - 每一個都去官方清單查現在是什麼階段
- 已 GA 的 → 從參數裡刪掉
- 查不到的 → 去「已移除」那頁確認,如果在上面,一定要刪
- 順便瀏覽一下目標版本有哪些功能從 Alpha 升到 Beta(這些會自動啟用)
[!NOTE] 另一個常被忽略的來源:CHANGELOG
每個版本的 CHANGELOG 都有 Urgent Upgrade Notes 段落,會明確寫出「這一版有哪些行為變更需要你注意」。這是除了 feature gate 清單之外,最值得花十分鐘讀完的東西:
https://github.com/kubernetes/kubernetes/tree/master/CHANGELOG
六、把三件事串成一份升級前檢查清單
前面拆開講了三種會爆的東西,這一節把它們合成一個實際執行的順序。
6-1. 為什麼是這個順序
flowchart TD
A["① 確認目標 K8s 版本
還在官方支援期內"] --> B["② 查 kernel 需求
決定 host OS 要不要先動"]
B --> C["③ 升 host OS / kernel
(如果需要)"]
C --> D["④ 掃 deprecated API
Pluto + kubent + metric"]
D --> E["⑤ 清點 feature gate
刪掉已 GA 的參數"]
E --> F["⑥ 升 K8s:一次一跳
1.31→1.32→…→1.36"]
F --> G["⑦ 最後才升外掛層
Cilium / Istio / cert-manager…"]
style C fill:#fff3cd,stroke:#856404,color:#000
style F fill:#d4edda,stroke:#155724,color:#000
幾個順序上的理由:
為什麼 host OS 要排在 K8s 前面? 因為kernel 是最底層的地基。如果你 K8s 升到一半才發現 kernel 版本不夠(例如要開 Cilium 的 eBPF dataplane),前面做的事可能要回頭重來。先把地基墊好,上面才好蓋。
為什麼 deprecated API 和 feature gate 要在升級前掃? 這是整篇的核心——這兩件事升級後才發現就來不及了。掃描本身不需要真的升級,成本很低,但能避免的是最難查的那種問題。
為什麼外掛層放最後? 因為外掛通常一次支援一段 K8s 版本區間(例如 Istio 1.30 支援 1.31–1.36),你不需要每跳一個 K8s minor 就跟著升一次外掛。等 K8s 到定位了再一次處理,省下大量重複工。
[!WARNING] 但外掛的「支援下限」要先確認
「外掛放最後」的前提是:你目前的外掛版本要能撐到目標 K8s 版本。如果你的 Cilium 舊到只支援 1.33 以下,那它就會變成升級路上的一道牆,必須先處理。所以第 ① 步查版本時,要順便確認每個外掛的支援上限有沒有涵蓋你的目標版本。
6-2. 檢查清單
升級前逐項打勾:
版本相容性(第二、三節)
- [ ] 目標 K8s 版本還在官方支援期內(https://kubernetes.io/releases/)
- [ ] 每個外掛的支援區間都涵蓋目標版本,且已記下該用哪個版號
- [ ] 每個版號都找到了官方來源佐證,不是從部落格或 Stack Overflow 抄的
- [ ] Helm chart 的
kubeVersion都確認過 - [ ] 離線環境:需要的 image 都已備妥(
kubeadm config images list)
Host OS 層(第三節 3-3)
- [ ]
uname -r確認目前 kernel,對照目標 K8s 所需功能的最低需求 - [ ] 確認是否只需上 HWE kernel,而不必整個 OS 升版
- [ ]
dpkg -s frr記錄目前 FRR 版本,確認 apt 來源在 OS 升級後不會失效
Deprecated API(第四節)
- [ ] 讀完官方 Migration Guide 中目標版本的段落
- [ ]
pluto detect-files --target-versions k8s=<目標版本>掃過所有 repo - [ ]
kubent --target-version <目標版本>掃過線上叢集 - [ ] 查過
apiserver_requested_deprecated_apismetric,抓出不在 Git 裡的呼叫者 - [ ] CI 已加入 Pluto 檢查,防止新的 PR 再引入舊 API
Feature Gate(第五節)
- [ ]
grep -r "feature-gates" /etc/kubernetes/manifests/清點所有手動設定 - [ ] 每個 gate 都對照官方清單確認階段,已 GA 的都已刪除
- [ ] 讀過目標版本 CHANGELOG 的 Urgent Upgrade Notes
- [ ] 確認有哪些功能在目標版本從 Alpha 升到 Beta(會自動啟用)
結語:真正的產出不是那張表
這篇給了你一張查證過的版本對照表,但那張表明年就過期了。
真正能一直用下去的是三件事:
-
認出相容性模型 —— 遇到沒看過的元件,先問「它屬於哪一型」,就知道該打開哪一頁。版號對齊型看分支、機器可讀型看
Chart.yaml、公告區間型看 release notes、政策繼承型看支援期、跨層依賴型則要往上或往下找真正管它的那一層。 -
分辨三種失效模式 —— 版本不相容會當場炸,deprecated API 會延後炸,feature gate 會安靜地改變行為。三者要用不同的方法抓,缺一不可。
-
把檢查往前推 —— 最好的升級是「升級當天沒有任何驚喜」。而做到這件事的方法,是把 Pluto 放進 CI、把 feature gate 清點變成例行公事,讓問題在還很便宜的時候就被擋下來,而不是等到維護窗口的凌晨三點。
升級 Kubernetes 從來不是三行指令的事,但它也不該是一場賭博。差別只在於,你有沒有在升級前把該查的都查完。
延伸閱讀
- 一個 kubectl apply 的旅程:Kubernetes 七個元件在做什麼 —— 本文第一節提到「apiserver 是唯一直接碰 etcd 的元件、所以升級順序以它為首」,那篇會完整走過七個元件各自的職責與協作方式
- Kubernetes 常用測試指令 —— 升級後驗證叢集狀態時會用到的臨時容器除錯技巧
- 在 k3s 上手把手打造 Prometheus + Grafana 監控系統 —— 第四節提到的
apiserver_requested_deprecated_apismetric 需要 Prometheus 才能查詢