Kubernetes HPA 水平自動擴縮容實作紀錄

在 Kubernetes 部署服務時,Pod 的數量通常不會一直維持固定不變。當流量或 CPU 使用率提高時,我們希望服務可以自動增加 Pod 來分攤負載;當負載下降後,也希望 Pod 數量可以自動縮回來,避免浪費叢集資源。

這篇文章會先整理 Kubernetes HPA(Horizontal Pod Autoscaler)的常用設定參數,再透過一份 Spring Boot 測試服務配置,展示如何依照 CPU 使用率自動調整 Deployment 的副本數。


什麼是 HPA?

HPA 是 Kubernetes 用來自動調整 Pod 數量的資源。它會根據指定的指標,例如 CPU 使用率、記憶體使用率或自訂指標,計算目前 Deployment 是否需要擴容或縮容。

以 CPU 使用率為例,HPA 會觀察 Deployment 底下所有 Pod 的平均 CPU 使用情況,並與設定的目標值比較。如果目前平均使用率高於目標值,HPA 就會增加 replicas;如果使用率長時間低於目標值,HPA 則會逐步減少 replicas。

HPA 常見的運作流程如下:

  1. Deployment 建立 Pod,並在容器中設定 resources.requests.cpu
  2. metrics-server 收集 Pod 的 CPU 與記憶體使用量
  3. HPA 讀取 metrics-server 提供的指標
  4. HPA 計算 Deployment 需要的 replicas 數量
  5. Deployment 依照新的 replicas 建立或移除 Pod

也就是說,HPA 並不是直接建立 Pod,而是透過調整目標 Deployment 的 replicas 來完成擴縮容。


前置條件

使用 HPA 前,需要先確認叢集內已經安裝 metrics-server。HPA 依賴 metrics-server 提供 Pod 的資源使用量,如果 metrics-server 沒有正常運作,就會在 HPA 狀態中看到指標無法取得的訊息。

可以先確認 Node 與 Pod 指標是否能正常查詢:

1
kubectl top nodes
1
kubectl top pods -A

如果這兩個指令可以看到 CPU 與記憶體使用量,就代表 metrics-server 已經可以提供基本資源指標。

另外,若 HPA 要使用 CPU utilization 作為擴縮容依據,Deployment 的容器必須設定 resources.requests.cpu。HPA 的 CPU 使用率不是直接用容器實際用掉多少 CPU 來判斷,而是用實際 CPU 使用量相對於 requests.cpu 的比例來計算。


HPA 設定參數總覽

HPA 的設定可以大致分成三個部分:第一是指定要控制哪個工作負載,第二是設定要依照哪些指標擴縮容,第三是控制擴容與縮容的速度。

以下是一份較完整的 HPA 設定示意,裡面同時列出多種 metrics 類型與 behavior 控制方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: app-deployment
minReplicas: 1
maxReplicas: 10

metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50

- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80

- type: Pods
pods:
metric:
name: packets-per-second
target:
type: AverageValue
averageValue: 1k

- type: Object
object:
metric:
name: requests-per-second
describedObject:
apiVersion: networking.k8s.io/v1
kind: Ingress
name: main-route
target:
type: Value
value: 10k

- type: External
external:
metric:
name: queue_messages_ready
selector:
matchLabels:
queue: "worker_tasks"
target:
type: AverageValue
averageValue: 30

behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 100
periodSeconds: 15

scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
- type: Pods
value: 4
periodSeconds: 15
selectPolicy: Max

這份 YAML 不一定要全部照抄到實際環境。實務上通常會先從 CPU 或記憶體這類資源指標開始,等到服務有更明確的業務指標後,再導入 Pods、Object 或 External 指標。


scaleTargetRef

scaleTargetRef 用來指定 HPA 要控制的目標資源。HPA 本身不會直接建立 Pod,而是調整目標資源的 replicas。最常見的目標是 Deployment,也可以是其他支援 scale subresource 的工作負載。

1
2
3
4
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: app-deployment

這段設定代表 HPA 會去調整 app-deployment 這個 Deployment 的 replicas。當 HPA 判斷需要擴容時,Deployment 會建立更多 Pod;當 HPA 判斷可以縮容時,Deployment 會減少 Pod 數量。


minReplicas 與 maxReplicas

minReplicasmaxReplicas 是 HPA 的副本數邊界:

1
2
minReplicas: 1
maxReplicas: 10

minReplicas 代表最少保留幾個 Pod。一般服務通常會至少保留 1 個 Pod,避免被縮到 0 後無法處理請求。

maxReplicas 代表最多可以擴展到幾個 Pod。這個值不是越大越好,因為 Pod 最終仍然需要被排程到 Node 上。如果叢集資源不足,HPA 即使把 replicas 調高,新 Pod 也可能因為 CPU 或記憶體不足而停在 Pending


metrics 指標設定

metrics 是 HPA 判斷是否擴縮容的依據。HPA 可以同時設定多個指標,Kubernetes 會分別計算每個指標需要的副本數,最後採用其中最高的建議值,避免某個指標已經過載卻被其他較低的指標蓋掉。

Resource 指標

Resource 是最常見的指標類型,用來依照 Pod 的 CPU 或記憶體使用率擴縮容。

1
2
3
4
5
6
7
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50

name: cpu 代表使用 CPU 作為指標,averageUtilization: 50 代表希望所有 Pod 的平均 CPU 使用率維持在 request 的 50%。如果容器設定 resources.requests.cpu: "200m",那麼 50% 約等於平均使用 100m CPU。

記憶體也可以使用相同方式設定:

1
2
3
4
5
6
7
metrics:
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80

使用 CPU 或記憶體的 Utilization 時,容器必須設定對應的 resources.requests。例如 CPU utilization 需要 resources.requests.cpu,Memory utilization 需要 resources.requests.memory

Pods 指標

Pods 指標會針對每個 Pod 收集某個自訂指標,並以所有 Pod 的平均值作為擴縮容依據。例如每個 Pod 每秒處理的封包數:

1
2
3
4
5
6
7
8
metrics:
- type: Pods
pods:
metric:
name: packets-per-second
target:
type: AverageValue
averageValue: 1k

AverageValue 代表 HPA 會觀察每個 Pod 的平均指標值。如果平均值高於 1k,HPA 就會評估是否需要增加 Pod 數量。這類指標通常需要搭配 Prometheus Adapter 或其他 custom metrics adapter,讓 Kubernetes 可以透過 Custom Metrics API 取得資料。

Object 指標

Object 指標會針對某個 Kubernetes 物件收集指標,例如 Ingress 每秒總請求數:

1
2
3
4
5
6
7
8
9
10
11
12
metrics:
- type: Object
object:
metric:
name: requests-per-second
describedObject:
apiVersion: networking.k8s.io/v1
kind: Ingress
name: main-route
target:
type: Value
value: 10k

describedObject 用來指定這個指標描述的是哪個物件。target.type: Value 代表 HPA 會用該物件的總指標值進行判斷。以上面例子來說,當 main-route 這個 Ingress 的每秒請求數高於 10k 時,HPA 就會評估是否要擴容。

External 指標

External 指標用來讀取 Kubernetes 叢集外部系統的數值,例如訊息佇列中等待處理的任務數:

1
2
3
4
5
6
7
8
9
10
11
metrics:
- type: External
external:
metric:
name: queue_messages_ready
selector:
matchLabels:
queue: "worker_tasks"
target:
type: AverageValue
averageValue: 30

這類指標很適合用在背景任務 worker。例如佇列中待處理訊息越多,就增加更多 worker Pod;待處理訊息下降後,再逐步縮容。

External 指標通常需要透過 External Metrics API 提供資料來源。常見做法是由 Prometheus Adapter、KEDA 或雲端服務提供對應的外部指標。


target 設定

每個 metric 都會有 target,用來描述 HPA 希望指標維持在什麼水準。常見的 target.type 有三種:

類型 說明 常見搭配
Utilization 以 request 為基準計算百分比 CPU、Memory 的 Resource 指標
AverageValue 每個 Pod 平均分攤後的目標值 PodsExternal 指標
Value 物件或指標本身的總量目標值 Object 指標

Utilization 最容易入門,但它依賴 container 的 resources.requestsAverageValueValue 則常用於自訂指標或外部指標,適合描述更貼近業務的負載,例如每秒請求數、佇列長度、待處理任務數等。


behavior 擴縮容行為控制

behavior 用來控制 HPA 擴容與縮容的速度。這個區塊不決定「要不要擴縮容」,而是控制「一旦需要擴縮容時,可以多快調整」。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
- type: Pods
value: 4
periodSeconds: 15
selectPolicy: Max

scaleUp 是擴容策略,通常會設定得比較積極,讓服務在流量上升時可以快速補上 Pod。scaleDown 是縮容策略,通常會設定得保守一點,避免流量短暫下降就立刻刪掉 Pod。

stabilizationWindowSeconds 是穩定視窗。用在縮容時,常見目的是參考過去一段時間的建議副本數,避免 Pod 數量來回震盪。例如 scaleDown.stabilizationWindowSeconds: 300 代表縮容會觀察過去 300 秒內的建議值,讓縮容比較不容易因短暫低流量而太快發生。

policies 用來限制每段時間最多可以調整多少 Pod。type: Percent 代表依照目前副本數的百分比調整,type: Pods 則代表直接指定 Pod 數量。

以上面的 scaleUp 為例,HPA 每 15 秒最多可以擴容目前副本數的 100%,或最多新增 4 個 Pod。因為 selectPolicy: Max,HPA 會選擇兩個 policy 計算後較大的結果,讓擴容速度更快。

如果想讓擴容更保守,可以把 selectPolicy 改成 Min,或降低 PercentPods 的數值。如果想停用某個方向的調整,也可以使用 selectPolicy: Disabled


Demo 範例架構

前面介紹完 HPA 的主要設定參數後,接著用 dockertest_hpa_v1.yml 展示一個基於 CPU 使用率的 HPA demo。

這次的配置檔 dockertest_hpa_v1.yml 會建立以下 Kubernetes 資源:

資源 說明
Namespace 建立 dockertest 命名空間,集中管理測試資源
Secret 儲存拉取私有 Container Registry 映像所需的認證資訊
Deployment 部署 Spring Boot 測試服務
Service 建立 ClusterIP,讓叢集內部可以存取服務
HorizontalPodAutoscaler 依照 CPU 使用率自動調整 Deployment replicas

其中 Secret 會依環境存放私有 registry 認證,實務上不建議直接把可用憑證寫入文章或公開版控。本文只說明用途,不展開 Secret 內容。


Deployment 資源設定

HPA 要依照 CPU utilization 計算擴縮容,Deployment 內的容器需要設定 resources.requests.cpu。在這份範例中,容器的 CPU request 與 limit 都設定為 150m

1
2
3
4
5
6
7
resources:
requests:
memory: "512Mi"
cpu: "150m"
limits:
memory: "800Mi"
cpu: "150m"

150m 代表 0.15 顆 CPU。後面 HPA 的 averageUtilization 設定為 50,意思是希望所有 Pod 的平均 CPU 使用率維持在 CPU request 的 50%。以目前 requests.cpu: "150m" 來看,目標平均使用量約為 75m

Deployment 同時設定了 readinessProbe,讓 Kubernetes 透過 /hello 判斷 Pod 是否已經可以接收流量:

1
2
3
4
5
6
7
8
readinessProbe:
httpGet:
path: /hello
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3

readinessProbe 與 HPA 沒有直接關係,但它可以避免尚未就緒的 Pod 被 Service 導流。當 HPA 擴容建立新 Pod 時,這個設定可以讓流量在 Pod 準備好之後才進入服務。


Service 設定

配置檔中建立了一個 ClusterIP Service,將叢集內部流量轉發到 Spring Boot 容器的 8080 port:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: dockertest-clusterip
namespace: dockertest
spec:
type: ClusterIP
selector:
app: dockertest
ports:
- name: http
port: 8080
targetPort: 8080
protocol: TCP

這個 Service 的 selector 會選到 labels 為 app: dockertest 的 Pod。當 HPA 增加 Deployment replicas 後,新建立的 Pod 也會帶有相同 label,因此 Service 會自動把這些 Pod 納入流量轉發目標。


HPA 設定

HPA 的核心設定如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: dockertest-hpa
namespace: dockertest
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: dockertest-deployment
minReplicas: 1
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50

scaleTargetRef 指定 HPA 要控制的目標資源。這裡指向 dockertest-deployment,因此 HPA 會調整這個 Deployment 的 replicas。

minReplicas: 1 代表至少保留 1 個 Pod,避免服務被縮到 0;maxReplicas: 5 代表最多擴展到 5 個 Pod。這個上限需要搭配叢集節點資源一起評估,否則 HPA 雖然想擴容,但新的 Pod 可能會因為資源不足而停在 Pending

averageUtilization: 50 代表 HPA 希望 Pod 平均 CPU 使用率維持在 request 的 50%。當目前使用率高於目標值時,HPA 會提高 replicas;當使用率下降後,HPA 會再依照縮容策略逐步降低 replicas。

這份配置也額外設定了 HPA 的擴縮容行為:

1
2
3
4
5
6
7
8
9
10
11
12
13
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Pods
value: 2
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 1
periodSeconds: 15

scaleUp 設定代表需要擴容時,每 15 秒最多增加 2 個 Pod。scaleDown 則代表縮容時,每 15 秒最多減少 1 個 Pod,並加上 60 秒的穩定視窗,避免負載剛下降就太快縮容。


套用配置檔

確認配置檔準備好後,可以使用 kubectl apply 建立資源:

1
kubectl apply -f ./assets/dockertest_hpa_v1.yml

建立完成後,檢查 Deployment、Pod、Service 與 HPA 狀態:

1
kubectl get deployment,pod,service,hpa -n dockertest

也可以單獨觀察 HPA:

1
kubectl get hpa -n dockertest

如果 metrics-server 可以正常提供資料,TARGETS 欄位會出現類似 10%/50% 的數值。前面的數字是目前平均 CPU 使用率,後面的數字是 HPA 設定的目標值。

若要查看更完整的 HPA 判斷過程,可以使用:

1
kubectl describe hpa dockertest-hpa -n dockertest

Events 區塊中可以看到 HPA 是否成功取得指標,以及是否曾經觸發擴容或縮容。


觀察 HPA 擴容

要讓 HPA 觸發擴容,需要讓目標 Pod 的 CPU 使用率超過設定值。若測試服務的 /hello endpoint 會產生足夠 CPU 負載,可以在叢集內建立一個臨時 Pod 持續打流量:

1
kubectl run hpa-load -n dockertest --rm -it --image=busybox:1.36 --restart=Never -- /bin/sh

進入 Pod 後執行:

1
while true; do wget -q -O- http://dockertest-clusterip:8080/hello > /dev/null; done

接著開另一個終端機觀察 HPA 與 Pod 數量:

1
kubectl get hpa,pod -n dockertest -w

當 CPU 使用率持續高於目標值時,HPA 會逐步提高 Deployment replicas,Pod 數量會從 1 增加到 2、3,最多不會超過 maxReplicas: 5

如果 /hello 只是很輕量的回應,CPU 使用率可能不足以觸發 HPA。這時候可以改用應用程式中較耗 CPU 的測試端點,或在服務內準備專門用來壓測的 endpoint,讓 demo 更容易觀察到擴容效果。


觀察 HPA 縮容

停止壓力測試後,CPU 使用率會逐漸下降。HPA 不會立刻把 Pod 全部縮回去,而是會依照 scaleDown 的設定逐步縮容。

在目前配置中,scaleDown.stabilizationWindowSeconds 設定為 60,代表 HPA 會等待一段穩定時間後再進行縮容判斷。縮容政策則設定為每 15 秒最多減少 1 個 Pod:

1
2
3
4
5
6
scaleDown:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 1
periodSeconds: 15

這樣的設定可以避免服務在流量短暫下降時過快縮容,導致下一波流量進來又需要重新擴容。


常見問題

HPA 顯示 unknown

如果 kubectl get hpa 看到 TARGETS 顯示 <unknown>/50%,通常代表 HPA 無法取得 CPU 指標。可以依序檢查:

  • metrics-server 是否已安裝並正常運作
  • kubectl top pods -n dockertest 是否能取得 Pod 指標
  • Deployment 是否有設定 resources.requests.cpu
  • Pod 是否已經進入 Running 並通過 readinessProbe

HPA 沒有擴容

HPA 沒有擴容不一定代表設定錯誤。可以先確認目前 CPU 使用率是否真的高於目標值:

1
kubectl get hpa dockertest-hpa -n dockertest

如果目前使用率低於 50%,HPA 就不會增加 replicas。若測試流量很輕,可能需要提高負載,或改用會消耗 CPU 的測試行為。

Pod 數量沒有超過 5 個

這是因為 HPA 設定了 maxReplicas: 5。即使 CPU 使用率持續偏高,HPA 也不會把 Deployment 擴展到超過 5 個 Pod。

如果要提高上限,可以調整 HPA 的 maxReplicas,但同時也要確認叢集節點資源是否足夠承載更多 Pod。


清理測試資源

測試完成後,可以用同一份配置檔刪除資源:

1
kubectl delete -f ./assets/dockertest_hpa_v1.yml

如果只想確認命名空間內剩餘資源,可以使用:

1
kubectl get all -n dockertest

小結

HPA 是 Kubernetes 中非常實用的自動擴縮容機制。它可以依照實際負載調整 Pod 數量,讓服務在高流量時具備更好的承載能力,也能在低流量時釋放不必要的資源。

在本次範例中,HPA 透過 metrics-server 取得 CPU 使用率,並依照 averageUtilization: 50 判斷是否要調整 dockertest-deployment 的 replicas。實作時要特別注意兩件事:第一,叢集必須能正常提供 metrics;第二,Deployment 必須設定 resources.requests.cpu,HPA 才能正確計算 CPU utilization。

只要掌握這兩個重點,再搭配適當的 minReplicasmaxReplicasbehavior 設定,就可以讓服務具備基本且可控的自動擴縮容能力。