Kubernetes VPA 垂直自動擴縮容實作紀錄

在 Kubernetes 裡部署服務時,除了要思考 Pod 數量是否足夠,也要思考每個 Pod 本身配置的 CPU 與記憶體是否合理。若 resources.requests 設得太低,服務可能容易被資源限制影響;若設定得太高,又會讓叢集排程時預留過多資源,造成浪費。

這篇文章會整理 Kubernetes VPA(Vertical Pod Autoscaler)的基本概念,並透過一份測試用 Deployment 與 VPA 配置,觀察 VPA 如何根據 Pod 的使用情況提供資源建議。文末也附上 Demo 影片,方便對照完整操作流程。


什麼是 VPA?

VPA 是 Kubernetes 用來調整 Pod CPU 與記憶體資源配置的元件。和 HPA(Horizontal Pod Autoscaler)透過增加或減少 Pod 數量來處理負載不同,VPA 關注的是單一 Pod 需要多少 resources.requests,也就是每個 Pod 應該預留多少 CPU 與記憶體。

VPA 會觀察目標工作負載的資源使用情況,並計算出建議值。依照 updatePolicy.updateMode 的設定不同,VPA 可以只提供建議,也可以在需要時重新建立 Pod,讓新的 Pod 使用更新後的資源配置。

常見的 VPA 運作角色如下:

  1. vpa-recommender 觀察 Pod 的資源使用量,計算 CPU 與記憶體建議值
  2. vpa-updater 判斷現有 Pod 是否需要更新資源配置,必要時驅逐 Pod
  3. vpa-admission-controller 在 Pod 建立時注入 VPA 建議的 requests 設定

也就是說,VPA 的重點不是增加 Pod 數量,而是讓 Pod 的資源請求值更接近實際需求。


VPA 與 HPA 的差異

HPA 與 VPA 都是自動擴縮容機制,但兩者調整的方向不同。

項目 HPA VPA
調整目標 Pod 數量 Pod 的 CPU 與記憶體 requests
常見用途 流量變大時增加副本數 服務資源設定不準時提供建議或自動修正
對應資源 Deployment 的 replicas Container resources requests
可能影響 建立或移除 Pod 可能重新建立 Pod

實務上要特別注意,VPA 不建議與 HPA 同時針對 CPU 或記憶體使用率調整同一個工作負載。原因是 HPA 會依據資源使用率計算副本數,而 VPA 會改變 requests,兩者若同時作用在相同指標上,可能讓擴縮容判斷互相影響。


前置條件

使用 VPA 前,需要先準備一個可正常操作的 Kubernetes 叢集,並確認 kubectl 已經連到目標叢集。VPA 需要觀察 Pod 的資源使用量,因此叢集內也應該先安裝 metrics-server,讓資源指標可以被正常取得。

可以先用以下指令確認 metrics-server 是否能提供資料:

1
kubectl top nodes
1
kubectl top pods -A

如果可以看到 Node 與 Pod 的 CPU、記憶體使用量,就代表後續觀察 VPA 建議值時比較不容易遇到指標缺失問題。


安裝 VPA

本次實作使用 Kubernetes 官方 autoscaler 專案內提供的安裝腳本。先將專案 clone 到本機:

1
git clone https://github.com/kubernetes/autoscaler.git

接著進入 VPA 目錄,執行安裝腳本:

1
2
cd autoscaler/vertical-pod-autoscaler/
./hack/vpa-up.sh

安裝過程會建立 VPA 需要的 CRD、RBAC、ServiceAccount、Deployment 與 Admission Controller 憑證。完成後可以看到 vpa-recommendervpa-updatervpa-admission-controller 等元件被建立起來。

確認 VPA 元件是否正常執行:

1
kubectl get pods -n kube-system | grep vpa

如果三個 VPA Pod 都是 Running,就代表 VPA 基本元件已經啟動完成。


VPA 基本使用方式

安裝 VPA 元件後,實際使用時通常會先準備好要被觀察的工作負載,例如 Deployment,接著建立一個 VerticalPodAutoscaler 資源指向該 Deployment。VPA 會根據目標 Pod 的資源使用情況計算建議值,並依照 updatePolicy 決定是否只提供建議,或在 Pod 建立、重建時套用新的 requests。

以下是一份基本的 VPA 設定範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: dockertest-vpa
namespace: dockertest
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: dockertest-deployment
updatePolicy:
updateMode: "Recreate"
resourcePolicy:
containerPolicies:
- containerName: '*'
minAllowed:
cpu: 50m
memory: 50Mi
maxAllowed:
cpu: 1000m
memory: 1000Mi

這份 YAML 的意思是:VPA 會觀察 dockertest namespace 裡的 dockertest-deployment,根據 Pod 的資源使用情況計算 CPU 與記憶體建議值,並把建議值限制在 minAllowedmaxAllowed 範圍內。

建立 VPA 時可以使用:

1
kubectl apply -f vpa.yml

建立完成後,可以透過以下指令查看 VPA 狀態與建議值:

1
kubectl describe vpa dockertest-vpa -n dockertest

Recommendation 區塊中,通常會看到 Lower BoundTargetUncapped TargetUpper Bound。其中 Target 是 VPA 建議套用的 requests,Lower BoundUpper Bound 則是建議值的上下界。


VPA 設定重點

VPA 的核心設定在 VerticalPodAutoscaler 這個資源中。以下整理幾個實作時最常調整的欄位。

官方文件可參考 Kubernetes Vertical Pod Autoscaling

targetRef

targetRef 用來指定 VPA 要觀察與調整的工作負載:

1
2
3
4
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: dockertest-deployment

這段設定代表 VPA 會針對 dockertest-deployment 這個 Deployment 底下的 Pod 計算資源建議值。

updatePolicy

updatePolicy.updateMode 決定 VPA 要如何套用建議值:

1
2
updatePolicy:
updateMode: "Recreate"

常見模式如下:

模式 說明
Off 只產生建議值,不會修改或重新建立 Pod
Initial 只在 Pod 建立時套用建議值,既有 Pod 不會被更新
Recreate 當 VPA 判斷需要更新時,會驅逐 Pod,讓工作負載重新建立 Pod
InPlaceOrRecreate 優先嘗試在不重啟 Pod 的情況下原地更新資源,若無法原地更新則退回 Recreate 行為
InPlace 嘗試只用原地更新套用資源建議,不會退回驅逐重建;需要叢集與 VPA 啟用對應 feature gate
Auto 自 VPA 1.4.0 起已棄用,不建議新配置使用;目前等同 Recreate,需要驅逐重建時建議明確使用 Recreate

新配置建議使用語意明確的 OffInitialRecreate,避免沿用舊範例中的 Auto。如果叢集與 VPA 版本支援原地調整 Pod 資源,也可以再評估 InPlaceOrRecreateInPlace。使用 Recreate 時,當 VPA 判斷資源設定需要更新,可能會重新建立 Pod。測試環境可以用這個模式觀察效果,但正式環境要先評估服務是否能承受 Pod 被重建的影響。

resourcePolicy

resourcePolicy 可以限制 VPA 建議值的範圍,避免建議值過低或過高:

1
2
3
4
5
6
7
8
9
resourcePolicy:
containerPolicies:
- containerName: '*'
minAllowed:
cpu: 50m
memory: 50Mi
maxAllowed:
cpu: 1000m
memory: 1000Mi

以上設定代表所有容器的 CPU 建議值不得低於 50m,也不得高於 1000m;記憶體則限制在 50Mi1000Mi 之間。這種上下限在正式環境很重要,因為它可以避免 VPA 根據短期負載產生過度極端的資源建議。


Demo 範例:測試用配置文件

前面整理完 VPA 的基本使用方式後,接著用 dockertest_vpa_v1.yml 示範一次完整流程。

這次範例使用 dockertest_vpa_v1.yml 作為測試配置,內容包含三個部分:

  1. 建立 dockertest Namespace
  2. 建立一個使用 polinux/stress image 的 Deployment
  3. 建立對應 Deployment 的 VerticalPodAutoscaler

配置內容如下:

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
# 建立專案使用的命名空間,讓相關資源集中在 dockertest 底下管理。
apiVersion: v1
kind: Namespace
metadata:
name: dockertest
labels:
# 標記此命名空間屬於開發環境,方便查詢或套用環境相關策略。
environment: dev
---
# dockertest-deployment-vpa.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: dockertest-deployment
namespace: dockertest
spec:
replicas: 1 # VPA 驗證,通常保持 1 個副本即可
selector:
matchLabels:
app: dockertest
template:
metadata:
labels:
app: dockertest
spec:
containers:
# 我們改用一個包含 stress 工具的 image 來方便測試
- name: dockertest
image: polinux/stress
command: ["sleep", "infinity"] # 讓容器保持運行,我們稍後進去手動觸發壓力
resources:
requests:
cpu: "100m"
memory: "100Mi"
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: dockertest-vpa
namespace: dockertest
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: dockertest-deployment
updatePolicy:
updateMode: "Recreate"
resourcePolicy:
containerPolicies:
- containerName: '*'
minAllowed:
cpu: 50m
memory: 50Mi
maxAllowed:
cpu: 1000m
memory: 1000Mi

這份配置裡,Deployment 一開始只設定 cpu: "100m"memory: "100Mi"。VPA 會觀察這個 Pod 的實際使用量,再依照 resourcePolicy 設定的上下限提供建議。


Demo 範例:調整 VPA Recommender 半衰期

VPA 的建議值是由 vpa-recommender 根據歷史資源使用量計算出來的。預設情況下,VPA 會保留較長時間的歷史資料,因此在 Demo 或測試環境中,即使已經製造新的 CPU 或記憶體負載,也可能需要等待一段時間,建議值才會明顯變化。

如果希望 VPA 更快忘記舊資料,可以調整 vpa-recommender 的 histogram decay half-life。半衰期越短,舊資料的權重下降越快,VPA 對近期負載變化的反應也會更快。不過這也代表建議值可能更容易受到短時間尖峰影響,所以正式環境不建議設定得太短。

安裝完成後,可以編輯 kube-system namespace 裡的 vpa-recommender Deployment:

1
kubectl edit deployment vpa-recommender -n kube-system

找到 recommender container 的 command 區塊,加入以下兩個參數:

1
2
3
4
5
6
7
8
containers:
- command:
- /recommender
# 加入下面這兩行來加速遺忘進程
- --cpu-histogram-decay-half-life=10m
- --memory-histogram-decay-half-life=10m
image: registry.k8s.io/autoscaling/vpa-recommender:0.14.0
name: recommender

這裡的 --cpu-histogram-decay-half-life=10m--memory-histogram-decay-half-life=10m 代表 CPU 與記憶體歷史資料的半衰期都調整為 10 分鐘。對 demo 來說,這可以讓 VPA 在壓力測試後更快反映新的資源建議;但在正式環境中,建議先保守評估,避免建議值因短期波動而頻繁改變。

修改完成後,Kubernetes 會重新建立 vpa-recommender Pod。可以用以下指令確認 Pod 是否回到 Running

1
kubectl get pods -n kube-system | grep vpa-recommender

Demo 範例:部署與壓力測試

套用測試配置:

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

確認 Deployment、Pod 與 VPA 是否建立成功:

1
kubectl get deploy,pod,vpa -n dockertest

接著可以進入測試 Pod,透過 stress 產生 CPU 或記憶體壓力,讓 VPA 有資料可以觀察:

1
kubectl exec -it -n dockertest deploy/dockertest-deployment -- stress --cpu 1 --timeout 300s

影片中則是直接指定 Pod 名稱與 container 執行壓力測試。由於 Pod 名稱每次建立時都可能不同,請先用 kubectl get pod -n dockertest 查詢實際名稱,再替換以下指令中的 <pod-name>

1
kubectl exec -it <pod-name> -n dockertest -c dockertest -- stress --cpu 2 --timeout 300

等待一段時間後,查看 VPA 狀態:

1
kubectl describe vpa dockertest-vpa -n dockertest

在輸出內容中,可以觀察 Recommendation 區塊。這裡通常會出現 Lower BoundTargetUncapped TargetUpper Bound 等欄位,代表 VPA 根據目前觀察到的資源使用情況所計算出的建議值。


Demo 範例:觀察重點

測試 VPA 時,可以特別觀察以下幾件事:

  1. Recommendation 是否出現 CPU 與 Memory 建議值
  2. 建議值是否落在 minAllowedmaxAllowed 範圍內
  3. 使用 Recreate 模式時,Pod 是否曾被重新建立
  4. 新 Pod 的 resources.requests 是否被更新成 VPA 建議值

可以使用以下指令查看 Pod 目前的資源設定:

1
kubectl get pod -n dockertest -o yaml

若只想快速觀察 VPA 建議值,也可以使用:

1
kubectl describe vpa dockertest-vpa -n dockertest

這次 Demo 影片示範了從部署測試資源、製造負載到觀察 VPA 建議值的完整流程。


Demo 範例:完整重置 VPA 歷史紀錄

在反覆測試 VPA 時,有時候會發現即使重新建立 Pod,VPA 的建議值仍然受到前一次測試結果影響。這是因為 VPA 會透過 VerticalPodAutoscalerCheckpoint 保留歷史統計資料,讓 vpa-recommender 可以延續先前的資源使用紀錄來計算建議值。

如果想重新跑一次乾淨的 demo,可以刪除測試 Deployment、VPA 物件與 namespace 內的 checkpoint,再重新套用配置檔:

1
2
3
4
kubectl delete deployment dockertest-deployment -n dockertest
kubectl delete vpa dockertest-vpa -n dockertest
kubectl delete verticalpodautoscalercheckpoints.autoscaling.k8s.io -n dockertest --all
kubectl apply -f ./assets/dockertest_vpa_v1.yml

這組指令會先移除目前的測試 Pod 與 VPA,再清掉 dockertest namespace 裡所有 VPA checkpoint。重新 apply 後,VPA 會從新的觀察資料重新累積歷史紀錄,適合用在 demo 前重置狀態,或是在調整半衰期參數後重新驗證建議值變化。

可以用以下指令確認 checkpoint 是否已經被清除:

1
kubectl get verticalpodautoscalercheckpoints.autoscaling.k8s.io -n dockertest

若輸出顯示沒有資源,代表目前 namespace 內沒有殘留的 VPA 歷史紀錄。


Demo 範例:清除測試資源

測試完成後,可以先刪除本次建立的 Namespace,連同 Deployment 與 VPA 一起清掉:

1
kubectl delete namespace dockertest

如果也要移除 VPA 元件,可以回到 autoscaler/vertical-pod-autoscaler/ 目錄,執行官方提供的清除腳本:

1
./hack/vpa-down.sh

清除過程會刪除 VPA 相關的 CRD、RBAC、ServiceAccount、Deployment、Service 與 Admission Controller 設定。若看到部分 CRD 顯示找不到資源,通常代表前面的刪除步驟已經先移除完成。


小結

VPA 適合用來處理「Pod 資源設定不準」的問題。當服務剛上線、負載型態還不明確,或是希望定期檢查 requests 是否過高或過低時,VPA 可以提供很有參考價值的建議。

不過,若要讓 VPA 自動更新正式環境的 Pod,仍然需要評估服務可用性、Pod 重建影響,以及是否會和 HPA 使用相同資源指標互相干擾。比較穩健的做法是先使用 Off 模式觀察建議值,確認 VPA 的推薦結果符合預期後,再逐步評估是否改用 InitialRecreate,或在環境支援時評估 InPlaceOrRecreateInPlace 模式。