Kubernetes 1.35 於 2025 年 12 月釋出,把從 v1.27 alpha 一路走到 v1.33 beta 的 In-Place Pod Resize 正式收進 stable。六年前這個 KEP 剛提出時,社群對「一個運行中的 Pod 能不能改 CPU 跟記憶體」的答案很簡單:不行,殺掉重生就好。這一直是 Kubernetes 縱向擴縮的原罪,任何 requests 或 limits 的變動都得走一次 rolling update,滿手長連線、有本地快取或跑到一半資料處理的 Pod 通通得重來。1.35 把這條路真的走通,但走通不代表所有工作負載都能安全用上,resizePolicy 該怎麼設、記憶體降容那條 OOMKill 界線該怎麼守,才是這次要看的重點。
為什麼熬了六年才 GA
In-Place Pod Resize 的難度不在改 cgroup 這個動作本身。cgroup v2 早就允許動態調整 CPU 跟 memory limit,直接寫 sysfs 檔案就行。真正卡住社群的是狀態一致性:Pod 的 spec.containers[*].resources 是使用者輸入的期望值,status.containerStatuses[*].resources 才是 kubelet 真的推到容器上的實際值,兩者之間可能因為節點資源不足而卡在 Deferred 或 InProgress。這個 desired vs actual 的模型設計,加上 scheduler、kubelet、CRI runtime 三方協調誰有權裁決,才是拖到 v1.35 才敢貼 GA 標籤的原因。
1.35 引入獨立的 resize subresource:PATCH /api/v1/namespaces/{ns}/pods/{name}/resize。這條路徑跟一般 Pod 更新分開,權限也單獨切出來——RBAC 上可以只給某個 controller resize 而不給 update,等於 VPA 或自訂 controller 有辦法在受限權限下調資源,而不會順手能改 Pod 其他欄位。這個切分在稽核跟最小權限實作上很有意義。
resizePolicy 是這次真正該留意的欄位
CPU 跟記憶體的 resize 行為由 per-container 的 resizePolicy 控制,兩個選項:NotRequired 直接調 cgroup、RestartContainer 觸發容器重啟。預設兩個資源都是 NotRequired,但這對 JVM、Go 帶 GOMEMLIMIT、Python 帶 PYTHONMALLOC arena、或任何在啟動時就 pre-allocate 記憶體池的執行環境是陷阱。
具體場景:一個 Spring Boot 服務用 -XX:+UseContainerSupport -XX:MaxRAMPercentage=75 起在 memory limit 2 GiB 上,JVM 認到的 heap ceiling 就固定在 1.5 GiB。透過 in-place resize 把 limit 拉到 4 GiB,cgroup 立刻放寬,但 JVM 內部的 heap ceiling 不會重新協商,等於白調——-Xmx 已經在 process 記憶體佈局裡固定住了。這種情境的正解是為 memory 設 RestartContainer:
1 | resizePolicy: |
CPU 給 NotRequired 有意義,JIT 熱身或流量尖峰可以動態加 CPU 而不打斷連線;記憶體給 RestartContainer 是承認 JVM 這類 runtime 需要 process 重啟才能真正吃到新的 heap。反過來,Envoy、Nginx、Redis、PostgreSQL 這類 C/C++ 寫的 daemon 對 memory limit 變動可以直接反應,兩個資源都給 NotRequired 沒問題。
一個容易踩雷的限制:resizePolicy 在 Pod 建立後不可變更。這代表決策要在 spec 寫下去那一刻就想清楚,事後改 Deployment template 也只是重建 Pod 才會生效——多環境 rollout 時尤其要盯緊。
VPA InPlaceOrRecreate 才是這次生態圈變動的重點
Vertical Pod Autoscaler 在 1.35 對齊之後補上一個新模式:InPlaceOrRecreate,優先用 in-place resize 調整,做不到再退回舊的驅逐重建路徑。過去 VPA 只有 Auto 跟 Recreate 能實際套用推薦值,而這兩者都是先 evict 再等 controller 重建 Pod,對有狀態服務、長流程作業、或 Deployment 剛好只有一個 replica 的情境幾乎不能用。改成 InPlaceOrRecreate 之後 VPA 才真正對 stateful workload 有實用價值。
VPA 何時會退回 evict?三種情境:Pod 沒設 resizePolicy: NotRequired、記憶體 limit 要往下調且當前使用量已經高於目標值、或節點資源不足以在原機執行放大。第二種是設計上刻意的 fallback:與其在原機直接推小 memory limit 然後被 OOM Killer 收走,不如換一臺容量充裕的節點重新排程。實務上建議把 InPlaceOrRecreate 搭配 PDB 一起設,確保退回 evict 那條路徑不會一次拉倒整個 Deployment。
記憶體降容 OOMKill 那條線
這是 In-Place Pod Resize 目前最大的一個實務地雷。把 memory limit 從 4 GiB 降到 2 GiB,如果容器當下實際用了 3 GiB,cgroup memory.max 直接寫下 2 GiB 之後,kernel OOM killer 會立刻挑一個目標——通常就是這個容器裡最大的 process。1.34 版本這個行為被明確標成「best-effort,可能 OOMKill」,1.35 GA 沒有為這種降容加任何保護欄。
kubelet 端能做的檢查很有限:它會拒絕明顯不合理的降容請求,例如新 limit 低於 request,但不會去查 container_memory_working_set_bytes 是不是還在爆的邊緣。這個責任被推到呼叫方——不管是人工 kubectl、GitOps controller、還是 VPA。實務上安全的做法有兩種選擇:把 memory 的 resizePolicy 設成 RestartContainer,讓每次降容都走一次 clean restart,代價是連線斷;或者接受 memory limit 只往上調不往下調,把降容留給下一次 Pod 重建。後者對絕大多數應用是更好的預設,尤其考慮到 Java、Node.js 這類 GC runtime 本來就會在有空間時佔滿 heap,強制降容幾乎必翻車。
想跑起來,節點要先過三關
三個前置條件常常被忽略:kernel 要能跑 cgroup v2、CRI runtime 是 containerd 2.0+ 或 CRI-O 對應版本、kubectl 至少 v1.32。cgroup v1 的環境雖然還能吃到部分功能,但 memory 一律只能用 RestartContainer 模式,等於等同於 rolling update,實用意義有限。Debian 11、CentOS 7 這種還沒下架的舊節點基本上會被擋在門口,遷移到 cgroup v2 通常等於換 kernel 加改 grub cmdline,然後重開機。
containerd 1.7 對 in-place resize 的支援是實驗性的,1.35 開始官方建議 2.0+。RKE2、K3s、kubeadm 底下自帶的 containerd 版本得先確認,特別是自架環境用 apt/yum 裝的通常慢一兩個 minor version。至於 Docker Engine 走 dockershim 那條路早在 1.24 就被砍掉,這裡不重覆了。
現階段該怎麼用
值得先用起來的三個場景:測試環境自動右調 requests 降低資源浪費、批次 job 起動時放大 CPU 加速 JIT 熱身、以及有明確流量週期的服務用 cron 驅動的 controller 做 scheduled resize。VPA 的 InPlaceOrRecreate 目前還在 beta,生產環境建議先觀察一到兩個 minor version 再全面切上去,畢竟這是六年來 Kubernetes 資源調度模型最大的一次改動,autoscaler 這邊的推薦演算法也還在跟新行為對齊。
跑在自家 VPS 或代管節點上的 Kubernetes 叢集要吃到這個新功能,除了升到 1.35 之外,最關鍵的是節點作業系統的 kernel 跟 CRI runtime 版本要跟得上。臺灣是方電訊機房的 NCSE Network VPS 全線支援自訂 kernel 跟 cgroup v2 開機參數調整,Intel Gold CPU 與 NVMe SSD 的搭配也適合當 Kubernetes 節點跑穩定工作負載。想把叢集升到 1.35 並實測 In-Place Pod Resize 對現有服務的影響,可以到 ncse.tw 查看相關方案。