iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0
Kubernetes

從零到一:使用 K8S + GitOps 打造異構技術棧的資料分析平台系列 第 28

[Day 28] 效能調優:JVM 與 Python 容器的資源限制與優化 —— 針對異構語言特性,調整 K8S 資源分配以達到最佳效能。

  • 分享至 

  • xImage
  •  

Day 28: 效能調優:JVM 與 Python 容器的資源限制與優化

針對異構語言特性,調整 K8S 資源分配以達到最佳效能。

1. 資源宣告的重要性

K8S 的 resources.requests 是排程依據(告訴 Scheduler 這個 Pod 大概需要多少資源),resources.limits 是硬上限,超過會被 OOMKilled(記憶體)或被 CPU 節流(CPU)。這兩個值該怎麼設,Java 與 Python 因為記憶體管理模型完全不同,考量點也不一樣。

2. Java:JVM 需要知道容器邊界在哪

JVM 在容器裡跑,預設會嘗試感知自己實際被分配到多少資源(-XX:+UseContainerSupport,Java 10+ 預設開啟),並用 -XX:MaxRAMPercentage 決定 Heap 上限佔可用記憶體的比例。services/user-service/Dockerfile 的啟動參數:

CMD ["java", \
     "-XX:+UseContainerSupport", \
     "-XX:MaxRAMPercentage=75.0", \
     "-jar", "app.jar"]

這串設定實際運作起來是什麼樣子,直接進容器驗證:

https://ithelp.ithome.com.tw/upload/images/20260830/201825492BstGATLBc.png

▲ JVM 資源調優實測:cgroup 記憶體上限、MaxRAMPercentage 換算出的實際 Heap 大小、以及 kubectl top 顯示的真實記憶體佔用

有以下三個關鍵數字對得上:

  • Deployment 設定 limits.memory: 512Mi
  • 容器內 /sys/fs/cgroup/memory.max 顯示 536870912(=512Mi)——這是 JVM 透過 cgroup 介面實際讀到的記憶體邊界
  • MaxHeapSize 算出 402653184(=384Mi),正好是 512Mi 的 75%

這證明 MaxRAMPercentage 不是一個「建議值」,是 JVM 真的會拿容器的 cgroup 限制去乘上這個百分比,算出實際的 Heap 上限。

3. 一個容易忽略的地方:Heap 不等於總記憶體佔用

kubectl top pods 顯示 user-service 閒置時就用了 327Mi,逼近 512Mi 的上限——但 Heap 上限明明只設了 384Mi,為什麼閒置佔用就已經這麼高?因為 JVM 的實際記憶體佔用(RSS)包含:

  • Heap(物件實際存放的地方,受 MaxRAMPercentage 限制)
  • Metaspace(類別中繼資料,預設沒有上限)
  • Thread Stack(每條執行緒獨立配置,執行緒數量多會累加)
  • JIT Code Cache(編譯後的機器碼快取)
  • Direct Memory / NIO Buffer

只調 MaxRAMPercentage 而不管其他部分,容器實際佔用還是可能逼近甚至超過 limits.memory。如果要更精細地控制,-XX:MaxMetaspaceSize-Xss(每執行緒 stack 大小)都可以個別限制。

4. Python:沒有 Heap 概念,記憶體控制點不一樣

Python 的 CPython 直譯器不像 JVM 有獨立的 Heap 上限設定——記憶體怎麼用,完全取決於程式邏輯本身在做什麼。wafer-backend 用 Pandas 處理 Delta Lake 的資料,一次 groupby 或統計運算會把整份資料載進記憶體操作,併發請求一多,記憶體用量會隨併發數線性甚至更快地增加。

這正是 Day 25 實際發生過的事:wafer-backend 在 30 個併發請求同時做 pandas 運算時被 OOMKilled。對照這次的 kubectl top 結果,wafer-backend 在閒置狀態只用 117Mi,跟當時壓力測試下衝破 1Gi 限制的情形反差很大——這正是 Python 記憶體調優最大的陷阱:閒置時的數字完全不能反映尖峰負載下的實際需求

對於這類工作負載,比起單純調高 limits.memory(治標),更根本的做法是控制併發度(用 Gateway 層的 rate limit、或 Uvicorn 的 worker/併發數限制)或改用分批 (chunking) 處理避免一次性把整份資料攤開在記憶體裡。

5. 兩種語言的資源設定心法

Java (JVM) Python (Pandas/FastAPI)
記憶體上限機制 MaxRAMPercentage 可設定 Heap 佔比 無內建機制,取決於應用邏輯
閒置基線 偏高(Metaspace、Thread Stack 常駐) 偏低
尖峰風險 GC 壓力上升、但通常不會突然暴衝 併發運算可能瞬間吃光記憶體
調優重點 拆解 RSS 各部分分別限制 控制併發度、批次處理

6. 小結

limits.memory 這個數字,Java 與 Python 該怎麼定,考量的東西完全不同——Java 有明確的 Heap 換算公式可以驗證,Python 更依賴實際壓測找出尖峰需求。調優不是設一次就結束,需要搭配 Day 22 建立的監控持續觀察。明天回到現場排查的主題:Pod 進入 CrashLoopBackOff 時,實際的除錯步驟。


上一篇
[Day 27] 災難恢復:K8S 叢集備份與數據復原策略 —— 面對最壞的情況,我們如何快速重建整套 BI 平台。
下一篇
[Day 29] 實戰排除:當 Pod 頻繁重啟 (CrashLoopBackOff) 時該如何排查 —— 一步一步走過真實的除錯流程,而不是背口訣。
系列文
從零到一:使用 K8S + GitOps 打造異構技術棧的資料分析平台30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言