前言:升級當天才發現,就太晚了

升級 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 這種格式,中間那個 36minor version(次版本),最後的 2patch 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-apiserver1.36 時,

  • kubelet 可以是 1.36、1.35、1.34、1.33 —— 但不能是 1.37
  • kubectl 可以是 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 chartgh 這類指令直接不能用。所以下面每一種模型我都會給兩條路:能連網時最快的指令,以及只能開瀏覽器看 GitHub/官網時,該打開哪個檔案、看哪一行。後者才是封閉環境的主力。

模型 ①:版號對齊型 —— CRI-O

最幸運的一種。這類專案直接讓自己的 minor 版號跟 Kubernetes 對齊,你不用查表,看版號就知道

CRI-O 是容器執行時期(container runtime,實際負責在 node 上跑起容器的程式,kubelet 透過 CRI 介面指揮它)。它的規則非常乾脆:

1
2
3
Kubernetes 1.36.x  →  CRI-O 1.36.x
Kubernetes 1.35.x → CRI-O 1.35.x
Kubernetes 1.34.x → CRI-O 1.34.x

CRI-O 官方 README 寫明它跟隨 Kubernetes 的發布週期與版本歪斜政策,每個 release-1.x 分支對應 Kubernetes 的 release-1.x

只能看網頁時怎麼確認:

  1. 打開 https://github.com/cri-o/cri-o,README 往下找 Compatibility matrix: CRI-O ⬄ Kubernetes 這一段。
  2. 更直接的做法是看分支清單https://github.com/cri-o/cri-o/branches ,如果你要升到 K8s 1.36,就確認 release-1.36 這個分支存在。存在就代表官方有出對應版本。
  3. 要拿具體 patch 版號,去 https://github.com/cri-o/cri-o/releasesv1.36.x 最新的那一個。

[!TIP] 版號對齊型的隱藏好處
這型元件不需要「升級前特別查」,因為答案是自明的。你只要確保它跟 K8s 同步往前走就好。containerd 則不屬於這一型——它有自己的版號節奏(1.7.x、2.x),要當成模型 ③ 來查。

特例補充:CoreDNS 由 kubeadm 自動決定

CoreDNS 是叢集內部的 DNS 服務(讓 Pod 可以用 service-name.namespace 這種名字互相找到對方)。它看起來像版號對齊型,實際上更特別:你通常不用自己決定版本,kubeadm 會幫你決定。

kubeadm 內建一個叫 corefile-migration 的函式庫,升級時它會:

  1. Deprecated() — 檢查你的 Corefile(CoreDNS 的設定檔)有沒有用到即將棄用的 plugin,有就警告,但還是讓你繼續
  2. Unsupported() — 檢查有沒有用到無法自動遷移的 plugin,有就中止
  3. Migrate() — 真正把舊格式改寫成新版相容的格式
  4. 詢問你是否套用

這個機制存在的原因很實際: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
2
3
4
5
6
# Chart.yaml
apiVersion: v2
name: grafana
version: 12.11.1 # chart 自己的版本
appVersion: "13.2.0" # 裡面裝的 Grafana 版本
kubeVersion: "^1.25.0-0" # ← 這一行才是相容性門檻

注意 kubeVersionappVersion 是兩件事:chart 版本、Grafana 應用版本、K8s 門檻,三個號碼互相獨立。 你要查的是 kubeVersion

只能看網頁時怎麼確認:

  1. 找到 chart 的 GitHub repo,例如 https://github.com/grafana-community/helm-charts/blob/main/charts/grafana/Chart.yaml
  2. 打開 Chart.yaml,找 kubeVersion: 那一行
  3. 如果沒有這個欄位,代表 chart 沒設限制——那就退回去看 CHANGELOG 或 release notes

能連網時的捷徑:

1
2
# 直接問 chart 的 kubeVersion
helm show chart grafana-community/grafana | grep kubeVersion

[!TIP] 升級前先乾跑一次,不用動到叢集
這招對離線環境也有用(只要你本機有 chart 檔案)。helm template 可以指定假的 K8s 版本來渲染,測出 chart 裡有沒有用到已被移除的 API:

1
helm template grafana-community/grafana --kube-version 1.36.0

如果 chart 裡還在用 1.36 已經移除的 API(例如舊版 PodSecurityPolicy、舊版 Ingress apiVersion),這行會直接報錯告訴你哪裡壞掉——比升級當天才發現 CrashLoopBackOff 好太多了

Grafana 的另一條路:Grafana Operator。 如果你不是用 Helm chart 裝單一 Grafana,而是用 Operator(以 CRD 宣告式管理多個 instance),判斷邏輯不一樣——Operator 本身要一直呼叫 K8s API,依賴度高很多。這時候真正決定相容性的是它用的 controller-runtimeclient-go 版本,要去它的 go.modsigs.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 官方目前支援哪些版本,我就支援哪些。」

所以判斷邏輯變成兩步:

  1. 你的 Gatekeeper 版本還在支援期內嗎?(Gatekeeper 維護 n 和 n-1 兩個 minor,大約三個月一版)
  2. 你的 K8s 版本還在官方支援期內嗎?(目前是 1.34 / 1.35 / 1.36)

兩個都是「是」,官方就認定這個組合相容。

這帶來一個很實用的結論:當 Kubernetes 發布新的 minor 版本時,只要你手上的 Gatekeeper 還沒過期,它就自動被認定相容於新版 K8s,你不需要特地等 Gatekeeper 發新版才能升 K8s。

只能看網頁時怎麼確認:

  1. 支援政策:https://github.com/open-policy-agent/gatekeeper/blob/master/docs/Release_Management.md
  2. 目前有哪些版本還在支援期:https://github.com/open-policy-agent/gatekeeper/releases(看最新的兩個 minor)
  3. 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
2
3
4
5
6
7
8
# 查 FRR 執行中的版本
vtysh -c "show version"

# 查 apt 裝的套件版本
dpkg -s frr | grep Version

# 確認 apt 來源是指向 FRR 官方 repo 還是 Ubuntu 內建的
cat /etc/apt/sources.list.d/*.list | grep -i 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」這種規格。

真正決定它能不能正常運作的是兩個東西:

  1. fluent-plugin-kubernetes_metadata_filter —— 這個 plugin 負責去問 API Server 拿 Pod 名稱、namespace、labels。K8s 升級時如果 API 版本或 RBAC 架構變了,plugin 太舊就會呼叫失敗,導致日誌抓不到或沒有標籤。
  2. 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
2
3
4
5
# 你的 node 用 containerd
image: fluent/fluentd-kubernetes-daemonset:v1.18-debian-containerd-1.0

# containerd + 輸出到 Elasticsearch
image: fluent/fluentd-kubernetes-daemonset:v1.18-debian-elasticsearch7-containerd-1.0

tag 的命名邏輯拆開來看:

1
2
3
v1.18        -debian    -elasticsearch7  -containerd     -1.0
│ │ │ │ │
Fluentd 版本 底層 OS 輸出目標 容器執行時期 image 修訂號

只能看網頁時: 打開 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.3
K8s 1.35 → v1.35.6
K8s 1.34 → v1.34.11
minor 跟 K8s 一致,取該分支最新 patch releases · branches
CoreDNS ①特例 K8s 1.31 → v1.11.3
K8s 1.32 → v1.11.3
K8s 1.33 → v1.12.0
K8s 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.36
1.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.36
v1.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.yamlkubeVersion,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 Version
cat /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 -rvtysh -c "show version"dpkg -s frrkubectl version)都能跑
  • 不能做的helm show charthelm repo updategh 這類要連外網的指令;直接 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
2
apiVersion: flowcontrol.apiserver.k8s.io/v1beta3   # ← 就是這一行
kind: FlowSchema

這些 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 的時候,根本沒人在看終端機輸出

於是典型的翻車劇本是這樣的:

  1. 你的 Deployment 用了某個 beta API,跑了兩年都沒事
  2. 升級 K8s,那個 API 被移除了
  3. 既有的資源還在跑(已經存在的物件不會憑空消失)
  4. 三個月後有人改了一行設定重新 apply → 直接被拒絕
  5. 這時候已經沒人記得當初為什麼要用那個 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 有沒有出現 betaalpha 字樣。

  • 沒有 → 大致安全
  • 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
2
3
apiserver_requested_deprecated_apis{removed_release="1.36"}
* on(group,version,resource,subresource)
group_right() apiserver_request_total

除了 metric,API Server 也會:

  • 在回應加上 Warning header
  • 在 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
2
3
4
5
# 掃一個目錄底下所有 manifest
pluto detect-files -d ./k8s-manifests

# 指定目標 K8s 版本(重點!預設不一定是你要升的版本)
pluto detect-files -d ./k8s-manifests --target-versions k8s=v1.36.0

kubent —— 掃你的叢集:

1
2
3
4
5
6
7
8
# 直接掃目前 kubeconfig 指向的叢集
kubent

# 指定目標版本,看升上去之後會壞什麼
kubent --target-version 1.36

# 只掃本機檔案(CI 環境沒有叢集連線時用)
kubent -f ./k8s-manifests

[!IMPORTANT] Pluto 為什麼不直接問 API Server?
因為問了會被騙。你用舊的 apiVersion 建立資源,API Server 會自動幫你轉換成目前的儲存版本,所以你再讀回來時看到的是新版——舊的寫法就這樣被藏起來了。這就是為什麼原始 YAML 檔才是可靠的做法

這也是為什麼查法二(metric)和查法三(掃檔案)要一起用:前者抓執行時期真正的呼叫,後者抓 repo 裡的寫法

4-4. 怎麼「提前卡控」:把檢查放進 CI

前面都是「發現問題」,這一節是不讓問題進來

最有效的做法是在 CI pipeline 裡加一道 Pluto 檢查,讓任何用到已移除 API 的 PR 直接無法合併

1
2
3
4
5
6
7
# GitLab CI 範例
check-deprecated-api:
stage: test
script:
# --target-versions 設成你「下一個」要升的版本,而不是目前的版本
- pluto detect-files -d ./manifests --target-versions k8s=v1.36.0
# Pluto 偵測到 REMOVED 的 API 會回傳非 0 exit code,pipeline 自動失敗

這裡有個很關鍵的設計決定:

[!TIP] --target-versions 要填「下一個版本」,不是「現在的版本」
如果你現在跑 1.34、下一步要升 1.35,就填 v1.35.0。這樣新寫的 YAML 在還沒升級之前就會被擋下來,而不是等升級當天才發現一堆要改。

更進取的做法是填再下一個版本,讓團隊提前兩個 minor 開始適應。

搭配 Helm 的乾跑(第二節提過的),可以形成雙重保險:

1
2
# 用目標版本渲染 chart,如果用到已移除的 API 會直接報錯
helm template my-release ./chart --kube-version 1.36.0

離線環境的提醒:Pluto 和 kubent 都是單一 binary,沒有執行時期的外部依賴,所以只要能把 binary 想辦法搬進內網,就能離線使用——它們的判斷邏輯是內建的,不需要連網查資料庫。


五、Feature Gate:預設值悄悄改變的那些行為

第三種會爆的東西最難查,因為它不會有錯誤訊息功能還在、API 還在,只是行為變了

5-1. 什麼是 Feature Gate

Feature gate(功能開關) 是 Kubernetes 用來控制「某個功能要不要啟用」的開關,寫在各元件的啟動參數裡:

1
2
# kubelet 或 kube-apiserver 的啟動參數
--feature-gates=SomeFeatureName=true,AnotherFeature=false

它存在的目的是讓新功能能夠逐步推出:先讓願意冒險的人開來試,穩定之後再讓所有人預設拿到。

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 之後的一到兩個版本。

危險的情境是這樣的:

  1. 幾年前某個功能還是 Alpha,你在 kubelet 參數裡寫死 --feature-gates=SomeFeature=true
  2. 它後來 GA 了,你的參數還留著,反正也沒差
  3. 某次升級,這個開關被移除了
  4. 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
2
3
特性名稱                              預設值   階段    起始版本  結束版本
AllowParsingUserUIDFromCertAuth false Alpha 1.33 1.33
AllowParsingUserUIDFromCertAuth true Beta 1.34 –

同一個功能會有多列,代表它在不同版本的不同階段。最後一欄(結束版本)是關鍵:有值代表這個階段到那一版為止,之後會進入下一階段(預設值可能就此改變)。

已經被移除的開關則另有一頁:

https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates-removed/

升級前的檢查動作:

  1. 用上面的 grep 找出你所有手動設定的 feature gate
  2. 每一個都去官方清單查現在是什麼階段
  3. 已 GA 的 → 從參數裡刪掉
  4. 查不到的 → 去「已移除」那頁確認,如果在上面,一定要刪
  5. 順便瀏覽一下目標版本有哪些功能從 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_apis metric,抓出不在 Git 裡的呼叫者
  • [ ] CI 已加入 Pluto 檢查,防止新的 PR 再引入舊 API

Feature Gate(第五節)

  • [ ] grep -r "feature-gates" /etc/kubernetes/manifests/ 清點所有手動設定
  • [ ] 每個 gate 都對照官方清單確認階段,已 GA 的都已刪除
  • [ ] 讀過目標版本 CHANGELOG 的 Urgent Upgrade Notes
  • [ ] 確認有哪些功能在目標版本從 Alpha 升到 Beta(會自動啟用)

結語:真正的產出不是那張表

這篇給了你一張查證過的版本對照表,但那張表明年就過期了

真正能一直用下去的是三件事:

  1. 認出相容性模型 —— 遇到沒看過的元件,先問「它屬於哪一型」,就知道該打開哪一頁。版號對齊型看分支、機器可讀型看 Chart.yaml、公告區間型看 release notes、政策繼承型看支援期、跨層依賴型則要往上或往下找真正管它的那一層。

  2. 分辨三種失效模式 —— 版本不相容會當場炸,deprecated API 會延後炸,feature gate 會安靜地改變行為。三者要用不同的方法抓,缺一不可。

  3. 把檢查往前推 —— 最好的升級是「升級當天沒有任何驚喜」。而做到這件事的方法,是把 Pluto 放進 CI、把 feature gate 清點變成例行公事,讓問題在還很便宜的時候就被擋下來,而不是等到維護窗口的凌晨三點。

升級 Kubernetes 從來不是三行指令的事,但它也不該是一場賭博。差別只在於,你有沒有在升級前把該查的都查完


延伸閱讀

官方參考來源

主題 連結
版本歪斜政策 https://kubernetes.io/releases/version-skew-policy/
目前支援的版本 https://kubernetes.io/releases/
Deprecated API 遷移指南 https://kubernetes.io/docs/reference/using-api/deprecation-guide/
API 棄用政策 https://kubernetes.io/docs/reference/using-api/deprecation-policy/
Feature Gates 清單 https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/
已移除的 Feature Gates https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates-removed/
Kernel 版本需求 https://kubernetes.io/docs/reference/node/kernel-version-requirements/
kubeadm 升級教學 https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/