← 回文章列表

2026年10月7日

Kubernetes HPA 為什麼看 Request 而不是 Limit?從擴展公式反推 CPU Request/Limit 怎麼設

KubernetesHPAAutoscalingSRE容量規劃

設定 HPA(Horizontal Pod Autoscaler)時,最常被問的一個問題是:「為什麼 averageUtilization: 75 的 75% 是相對 Request 算,而不是相對 Limit?Limit 才是 Pod 真正能用到的上限啊。」

這個問題背後其實藏著 HPA 的設計哲學:HPA 不是在 Pod 快撐不住時才救火,而是在用量超過「預期常態」的某個比例時,就提早擴展,替新 Pod 的啟動與就緒爭取緩衝時間。理解這點之後,Request、Limit、HPA Target 三個參數該怎麼設,就會變成一個可以推導的問題,而不是憑感覺填數字。

先看 HPA 到底怎麼算

HPA 控制器預設每 15 秒執行一次,從 metrics-server 取得每個 Pod 的 CPU 用量,然後套用這條公式:

text
desiredReplicas = ceil( currentReplicas × currentUtilization / targetUtilization )

currentUtilization = 所有 Pod 實際 CPU 用量的平均 ÷ 每個 Pod 的 CPU Request

舉例:目前 2 個 Pod、Request 為 800m、Target 為 75%,兩個 Pod 平均用了 1000m:

text
currentUtilization = 1000m / 800m = 125%
desiredReplicas    = ceil( 2 × 125 / 75 ) = ceil(3.33) = 4

從公式可以讀出幾個重要的事實:

  • 分母是 Request,Limit 完全不參與計算。 使用率可以超過 100%(上例就是 125%),因為 Pod 被允許用到 Request 以上、Limit 以下的空間。
  • 沒有設定 CPU Request 的 Pod,HPA 算不出使用率。 這時 HPA 會顯示 <unknown>,完全不會擴展。如果 Pod 裡有 sidecar(例如 Istio 的 envoy),sidecar 也要設 Request,因為使用率是整個 Pod 所有容器的加總。
  • 有 10% 的容忍區間。 當 currentUtilization / targetUtilization 落在 0.9~1.1 之間時,HPA 不會動作,避免在目標值附近來回抖動。

為什麼基於 Request 而不是 Limit?

1. 用 Limit 算,擴展時機一定太晚

Limit 是硬性天花板。CPU 用量碰到 Limit 時,Linux 的 CFS 排程器會開始 Throttling——容器在每個 100ms 週期內用完配額就被強制暫停,反映到應用程式上就是延遲突然飆高、timeout 開始出現。記憶體碰到 Limit 則更直接,容器會被 OOMKilled。

而新 Pod 從被建立到真正能接流量,要經過排程、拉 image、應用程式啟動、通過 Readiness Probe,常常要 1~3 分鐘(探針怎麼設計可以參考《Kubernetes 探針怎麼設計?》)。如果等到接近 Limit 才觸發擴展,這段空窗期裡舊 Pod 早就被節流或砍掉了。

2. Request 才是「預期常態用量」

在容量規劃上,兩個參數的角色是這樣分工的:

參數角色意義
Request保證水位應用程式在正常流量下的基準消耗量,Kubernetes 保證一定分得到
Limit突發容量(Burst capacity)應對短時間尖峰的彈性空間,不保證一定分得到

HPA 的邏輯因此很自然:用量超過常態基準的某個比例,就該加副本了;而加副本的這段時間,超出的負載由 Request 到 Limit 之間的彈性空間來吸收。

3. 與 Scheduler 的資源記帳方式一致

Kubernetes Scheduler 決定 Pod 要放到哪個 Node 時,看的是 Request 的加總,而不是 Limit,也不是實際用量。HPA 也用 Request 計算,代表「HPA 決定加幾個 Pod」跟「叢集能不能放得下這些 Pod」用的是同一套帳本——HPA 算出要 4 個 Pod,Scheduler 就準確知道需要 4 × 800m 的可分配空間。

常見疑慮:流量長期大於 Request,會不會無限擴展?

會一直擴展,但這是 HPA 預期中的健康行為——流量本來就超過基礎水位了,系統本來就該增加副本數來攤平負載。要防止的不是「擴展」本身,而是「擴展到把叢集資源榨乾」,正確的作法是:

  1. 設定 maxReplicas 上限,並確認叢集(或 Cluster Autoscaler 的 Node 上限)真的放得下這麼多副本。
  2. 設定合理的 Target Utilization,一般落在 60%~80%。越低代表擴得越早、越保守,但閒置成本越高。
  3. Request 要貼近實際常態用量。 設得極低(例如為了塞更多 Pod 而只給 100m),HPA 就會在常態流量下一直以為使用率爆表,副本數永遠頂在上限。

實戰情境:常態 0.5 core、峰值 2 core 怎麼設

情境假設(單一 Pod 的用量):

  • 常態用量:500m(0.5 core)
  • 流量翻倍:1000m(1 core)
  • 極端峰值:2000m(2 core,常態的四倍)

推薦參數配置

參數建議設定值說明
CPU Request800m排程的保證水位,也是 HPA 計算使用率的分母
CPU Limit2000m硬限制,用來吸收極端峰值,避免新 Pod 啟動空窗期被 Throttling
HPA Target75%平均用量達到 600m(800m × 75%)時開始擴展
minReplicas2至少兩個副本,單一 Pod 掛掉時服務不中斷
maxReplicas10依叢集容量與下游(DB、外部 API)能承受的併發數決定

對應的 YAML:

YAML
# deployment.yaml(節錄)
resources:
  requests:
    cpu: 800m
    memory: 512Mi
  limits:
    cpu: 2000m
    memory: 512Mi   # 記憶體建議 Request = Limit,原因見後文
YAML
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-api
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-api
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 75

套進公式驗算一次

假設 minReplicas: 2,下表是不同流量下 HPA 的反應(「單 Pod 用量」指擴展前每個 Pod 承受的量):

流量情境單 Pod 用量使用率HPA 算出的副本數擴展後單 Pod 用量
常態500m62.5%2(不動作)500m
翻倍1000m125%ceil(2 × 125 / 75) = 4500m(62.5%)
極端峰值2000m250%ceil(2 × 250 / 75) = 7約 571m(71%)

為什麼這樣設定最合理?

  1. 常態用量不會觸發無效擴展。 如果 Request 照常態用量設成 500m、Target 80%,觸發點是 400m,常態的 500m 就已經是 125% 使用率,HPA 會在平常就不斷擴展。把 Request 提高到 800m,觸發點落在 600m,常態 500m 時使用率只有 62.5%,系統會安穩維持在最小副本數。
  2. 流量翻倍時平順擴展。 用量跨過 600m 往 1 core 走時 HPA 就開始擴展,這時離 Limit(2 core)還有一倍的空間,舊 Pod 完全不會被節流,使用者感受不到擴展正在發生。
  3. 極端峰值時,Limit 負責掩護空窗期。 四倍流量瞬間湧入時,HPA 偵測 + 新 Pod 啟動通常要 1~3 分鐘。這段時間舊 Pod 靠 limit: 2000m 直接 Burst 到 2 core 消化請求;等新 Pod 就緒、流量被攤平,每個 Pod 的用量又回到 70% 上下的健康水位。

擴縮的節奏:behavior 設定

HPA 預設的擴縮節奏是「擴得快、縮得慢」:

  • Scale Up:沒有穩定窗口,每 15 秒最多可以翻倍(或多加 4 個 Pod,取較大者)。
  • Scale Down:有 300 秒的穩定窗口——HPA 會取過去 5 分鐘內算出的最大建議值,避免流量一掉下去就馬上縮、下一波尖峰又要重新擴。

大多數情境用預設值就夠了。如果服務的流量是「尖峰很短、但一小時來好幾次」的鋸齒型,可以把縮容放得更保守:

YAML
spec:
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 600   # 觀察 10 分鐘才縮
      policies:
        - type: Pods
          value: 1                      # 每分鐘最多縮 1 個
          periodSeconds: 60

另外要注意:HPA 對剛啟動的 Pod 有保護機制——Pod 還沒 Ready、或剛啟動時(預設 5 分鐘內)的 CPU 數據會被保守處理,避免 JIT 編譯、快取預熱這種啟動期的 CPU 尖峰讓 HPA 誤以為負載很高、連鎖擴展。這也是 Readiness Probe 要設計正確的另一個理由。

記憶體為什麼不建議拿來當 HPA 指標?

HPA 也支援 memory 指標,但對 .NET、Java 這類有 GC 的執行環境通常不適合:

  • 記憶體用量不會隨流量下降而回落。 GC 回收後,執行環境常常不會把記憶體還給作業系統,擴上去之後使用率一直維持高檔,HPA 就永遠縮不回來。
  • 記憶體不可壓縮。 CPU 超用只會被節流變慢,記憶體超過 Limit 是直接被 OOMKilled,沒有「Burst 一下撐過去」的空間。

所以一般建議是:CPU 用 HPA 彈性擴展,記憶體則設 Request = Limit,讓每個 Pod 拿到的記憶體是確定的,排程與 OOM 行為都可預期。如果業務上真正的瓶頸是請求量、佇列長度(例如 Kafka consumer lag),比起記憶體,更值得透過 Prometheus Adapter 或 KEDA 改用自訂指標來擴展。

⚠️ 架構師視角的提醒:Overcommit 風險

上面的設定中,CPU Request(0.8)與 Limit(2.0)差距達 2.5 倍,這代表高度依賴 Kubernetes 的 Overcommit(超賣) 機制:Scheduler 只依 Request 把 Pod 塞進 Node,Node 上所有 Pod 的 Limit 加總可以遠大於 Node 的實體 CPU。

  • 優點:應對偶發峰值非常有效,平常不用為峰值預留整顆 CPU,資源利用率高。
  • 風險:如果同一個 Node 上的多個 Pod「同時」迎來峰值,Node 的實體 CPU 不夠分,大家會依 Request 的比例分配 CPU 時間,結果是每個 Pod 都拿不到它以為能用的 2 core,延遲一起上升。

實務上的因應方式:

  1. 監控 Node 層級的 CPU 使用率,不只是 Pod 層級。Node 長期高於 70%~80%,代表 Burst 空間已經被吃掉了。
  2. 同一個服務的副本要分散到不同 Node。 用 topologySpreadConstraints 或 Pod Anti-Affinity,避免「同一波流量」讓擠在同一台 Node 上的副本同時 Burst。
  3. 核心服務考慮縮小 Request/Limit 差距,甚至設成 Guaranteed QoS(Request = Limit),用成本換取可預期性;非核心、可容忍變慢的服務,才放大 Overcommit 的比例。

上線後怎麼確認 HPA 有正常運作

Shell
# 看目前使用率 / 目標值與副本數,TARGETS 若是 <unknown> 通常是沒設 Request 或 metrics-server 異常
kubectl get hpa order-api

# 看 HPA 的事件與每次擴縮的原因
kubectl describe hpa order-api

# 看每個 Pod 的實際 CPU / 記憶體用量
kubectl top pod -l app=order-api

如果有接 Prometheus,建議再觀察 container_cpu_cfs_throttled_periods_total 對 container_cpu_cfs_periods_total 的比例——Throttling 比例持續偏高,代表 Limit 太緊,或 HPA 擴得太慢,即使平均 CPU 使用率看起來很健康也一樣。

總結

  • HPA 的使用率是 實際用量 ÷ Request,Limit 不參與計算;沒設 Request,HPA 就不會動。
  • Request 設成「常態用量 ÷ Target」再留一點餘裕,常態流量才不會觸發無效擴展(本例:500m ÷ 75% ≈ 667m,取 800m)。
  • Limit 的任務是掩護擴展空窗期,讓舊 Pod 在新 Pod 就緒前能 Burst 撐住峰值。
  • maxReplicas 才是防止無限擴展的開關,而不是把 Request 設高或 Target 設高。
  • CPU 適合 HPA,記憶體建議 Request = Limit;Request 與 Limit 差距越大,越要盯緊 Node 層級的資源與副本分散。