Kubernetes Resource Quota 與 HPA 自動擴展

在 Kubernetes 叢集中,資源管理是穩定運行的關鍵。若沒有適當限制,單一異常的 Pod 可能耗盡整個 Node 的 CPU 或記憶體,導致其他正常的服務被「餓死」或驅逐。

本文將深入探討 Kubernetes 的資源管理機制,包括單個 Pod 的 Requests & Limits、Namespace 層級的 ResourceQuota & LimitRange,以及如何利用 HPA 實現自動擴展。

Pod 資源請求與限制 (Requests & Limits)

這是最基礎的資源管理單位。在定義 Pod (或 Deployment) 時,我們可以為每個 Container 設定 resources

# nginx-resources.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-resources
spec:
  containers:
    - name: nginx
      image: nginx
      resources:
        # Requests: K8s 調度依據 (保證有的資源)
        requests:
          memory: '64Mi'
          cpu: '250m'
        # Limits: 容器執行的硬上限 (超過會被限制或殺掉)
        limits:
          memory: '128Mi'
          cpu: '500m'

Requests vs Limits

屬性用途說明
Requests (請求)調度 (Scaling)Kubernetes Scheduler 會根據 Node 剩餘的可用資源 (Capacity - Sum of Requests) 來決定 Pod 該跑在哪台 Node 上。K8s 保證 Pod 至少能獲得這麼多資源。
Limits (上限)執行 (Runtime)Container 執行時能使用的資源上限。這是由 Linux Kernel (Cgroups) 強制執行的。

CPU 與 Memory 的行為差異

  • CPU (可壓縮資源):
    • 單位: 1 代表 1 個 vCPU (AWS vCPU, GCP Core)。100m (millicores) 代表 0.1 vCPU。
    • 超過 Limits 時: 程式不會崩潰,但會被 Throttle (節流/降頻),導致效能變差、延遲增加。
  • Memory (不可壓縮資源):
    • 單位: 推薦使用 Mi (Mebibytes) 或 Gi (Gibibytes)。
      • 二進位制 (Mi, Gi): 以 1024 為換算基底 (2 的次方)。1Gi = 1024Mi。這是電腦作業系統中實際顯示的容量算法。
      • 十進位制 (M, G): 以 1000 為換算基底 (10 的次方)。1G = 1000M
      • 陷阱: 1Gi (約 1073MB) 大於 1G (1000MB)。兩者容易搞混,在 K8s 資源設定中建議統一使用 Mi / Gi 以免發生預期外的誤差。
    • 超過 Limits 時: 若 Container 嘗試使用超過 Limit 的記憶體,該 Process 會被 Kernel 的 OOM Killer (Out Of Memory Killer) 殺掉,導致 Pod 重啟 (狀態顯示 OOMKilled)。

QoS Classes (服務品質等級)

Kubernetes 根據 Requests 和 Limits 的設定,會自動賦予 Pod 一個 QoS Class,決定資源不足時誰先被犧牲:

  1. Guaranteed (最高級):
    • 設定:Requests == Limits (CPU & RAM 都要設且相等)。
    • 行為:最不可能被驅逐。適合核心資料庫或關鍵服務。
  2. Burstable (中級):
    • 設定:Requests < Limits。
    • 行為:允許突發使用資源。當 Node 資源不足時,若並沒有超過 Requests 則安全,若超過 Requests 則可能被驅逐。適合大多數 Web Server。
  3. BestEffort (最低級):
    • 設定:完全沒設 Requests 和 Limits。
    • 行為:資源有剩就能用,但 Node 缺資源時 第一個被殺掉。只適合非關鍵的批次作業或測試。

Namespace 資源管理 (ResourceQuota & LimitRange)

除了限制單一 Pod,管理員通常需要限制整個 Namespace 的資源總量,避免某個團隊佔用過多資源。

ResourceQuota (資源配額)

限制 Namespace 內所有 Pod 的資源加總上限。

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-quota
  namespace: team-a
spec:
  hard:
    pods: '10' # 最多只能有 10 個 Pod
    requests.cpu: '4' # 所有 Pod request 加總不超過 4 vCPU
    requests.memory: '8Gi' # 所有 Pod request 加總不超過 8 GiB
    limits.cpu: '8'
    limits.memory: '16Gi'

LimitRange (預設限制)

如果使用者忘記設定 Pod 的 resources,LimitRange 可以幫忙加上「預設值」,或是限制單一 Pod 可設定的最小/最大範圍。

apiVersion: v1
kind: LimitRange
metadata:
  name: team-a-limits
  namespace: team-a
spec:
  limits:
    - default: # 預設 Limit (若 Pod 沒寫)
        cpu: 500m
        memory: 512Mi
      defaultRequest: # 預設 Request (若 Pod 沒寫)
        cpu: 100m
        memory: 128Mi
      type: Container

如何套用設定

上述設定都指定了 namespace: team-a,因此請先建立該 Namespace,並將設定存為 YAML 檔 (例如 quota-limits.yaml) 後套用:

# 1. 建立 Namespace
kubectl create namespace team-a

# 2. 套用 ResourceQuota 與 LimitRange
kubectl apply -f quota-limits.yaml

# 3. 查看設定是否生效
kubectl get resourcequota,limitrange -n team-a

Horizontal Pod Autoscaler (HPA)

設定好資源限制後,我們可以利用 HPA 讓服務根據負載自動伸縮。

運作原理

  1. Metrics Server: 必須先安裝,它會定時蒐集 Pod 的 CPU/RAM 使用數據。
  2. HPA Controller: 定期 (預設 15秒) 檢查 Pod 的使用率。
  3. 計算公式: 核心概念是「負載變幾倍,Pod 就變幾倍」。
    • 公式:期望副本數 = ceil[目前副本數 * ( 目前指標 / 目標指標 )]
    • 舉例: 假設你有 2 個 Pod,目前 CPU 平均使用率飆到 80%,但你設定的目標是 50%
    • 計算:2 * (80 / 50) = 3.2 -> 無條件進位成 4 個 Pod。
  4. Scale: 調整 Deployment 的 replicas 數量。

安裝 Metrics Server

HPA 依賴 Metrics Server 提供監控數據,不同環境的安裝方式如下:

1. Minikube (測試環境)

minikube addons enable metrics-server

2. 標準自建叢集 (Production) 通常使用官方提供的 YAML 檔安裝:

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
在某些自建環境 (如 Kubeadm) 若遇到 TLS 憑證問題,可能需要在 Deployment 中加入 --kubelet-insecure-tls 參數 (僅限測試環境,生產環境建議設定正確憑證)。

3. 公有雲託管服務 (EKS, GKE, AKS) 大部分雲端服務 (如 GKE, AKS) 預設已安裝。EKS 則需透過 add-on 安裝。

驗證安裝 安裝完成後,請執行 kubectl top nodes,若能看到 CPU/Memory 數據即代表成功。

HPA 設定範例 (v2 API)

建議使用 autoscaling/v2 API,功能較完整。

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-hpa
spec:
  # 指定要擴縮容的目標 (通常是 Deployment)
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nginx-deploy

  # 副本數的上下限
  minReplicas: 2
  maxReplicas: 10

  # 觸發擴縮容的指標。K8s 會計算每個指標建議的副本數,並取「最大值」
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 50 # 當平均 CPU 使用率超過 50% 時擴容
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 70 # 當平均 Memory 使用率超過 70% 時擴容

  # (進階) 行為設定:用來微調擴縮容的反應速度,防止震盪 (Flapping)
  behavior:
    # 縮容策略 (Scale Down): 預設有 5 分鐘冷卻期,防止流量抖動導致頻繁縮容
    scaleDown:
      stabilizationWindowSeconds: 300 # 穩定視窗:需持續 300 秒 (5分鐘) 建議縮容才會真正執行
      selectPolicy: Max # 若有多個策略,取變動幅度最大者
      policies:
        - type: Percent
          value: 100 # 允許每 15 秒最多縮容 100% 的副本 (即不做額外速率限制)
          periodSeconds: 15
    # 擴容策略 (Scale Up): 通常希望能盡快反應
    scaleUp:
      stabilizationWindowSeconds: 0 # 擴容通常不需要等待,立即反應
      selectPolicy: Max
      policies:
        - type: Percent
          value: 100 # 允許每 15 秒最多擴容 100% (即翻倍)
          periodSeconds: 15
        - type: Pods
          value: 4 # 允許每 15 秒最多增加 4 個 Pod
          periodSeconds: 15

測試 HPA

使用 kubectl get hpa -w 觀察。啟動壓力測試後,應能看到:

  1. TARGETS 數值上升 (e.g., 200%/50%)。
  2. REPLICAS 增加。
  3. 壓力停止後,經過 stabilizationWindowSeconds (預設 5 分鐘),副本數才會慢慢降回來,避免服務忽大忽小 (Flapping)。

進階話題:VPA (Vertical Pod Autoscaler)

除了水平擴展 (加副本數),也有 VPA 可以垂直擴展 (自動調整 Pod 的 CPU/RAM Requests & Limits)。

  • 適用場景: 無法水平擴展的服務 (如單體式資料庫),或是很難預抓資源用量的 App。
  • 限制: 通常不建議 HPA 和 VPA 同時針對 CPU/Memory 控制同一個 Deployment,會造成衝突。通常是 HPA 管副本數,VPA 管資源大小 (但在 "Auto" 模式下 VPA 需要重啟 Pod 才能生效)。

總結與最佳實踐

  1. Requests 務必要設: 原則上 Requests 應設定為「正常運作下的平均值」,Limits 設定為「允許的突發最大值」。
  2. Namespace Quota 當保險: 防止單一團隊資源溢用。
  3. 善用 HPA: 讓服務具備彈性,但要留意 initialDelaySeconds (在 Probes 設定) 與 HPA 的配合,避免 Pod 還沒啟動好就被視為低負載而縮容,或高負載而誤判。
  4. 避免 OOM: 監控 Memory Usage,如果常觸發 OOMKilled,應調高 Memory Limit 或檢查程式是否有 Memory Leak。