HPA to not scale correctly
HPA 無法正確 Auto Scaling
這篇文章將介紹我們在客戶端的 GKE 上使用 HPA 時遭遇到的 Bug。
環境描述
Kubernetes version: 1.27.8-gke.1067004
事件發生
客戶的 GKE 上遭遇一個十分詭異的問題,部署 HPA 後,無論 resource usage 是否到達 threshold,會馬上 scale up 到 max replica count,並且永遠不會 scale down,從 HPA 的 event 上看來,HPA controller 確實認為 resource usage 超過 threshold:
New size: 3; reason: memory resource utilization (percentage of request) above target
以下是 HPA 設定:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server
spec:
scaleTargetRef:
kind: Deployment
name: api-server
apiVersion: apps/v1
minReplicas: 1
maxReplicas: 3
metrics:
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
在 GKE 上的 workload 查看 HPA 的狀態,當前的 memory usage 是 33%,threshold 則是 80%:
很明顯的,current value 明明沒有到達 target value,但是 HPA 仍然將 replica count 升到 3:
調查經過
一開始認為是 metrics server 的問題,但又覺得十分不合理,假設 metrics server 回傳值是錯誤的,那 HPA 畫面上顯示的值是怎麼來的。透過以下API,可以獲得當前 metrics value:
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/namespaces/<namespace>/pods/<pod-name>"
再來調查相關 issue 及 kubernetes 在 1.27 版本的 change log,但很遺憾的,所有情境都與現在 不相符:
- HPA doesn’t scale down to minReplicas even though metric is under target
- HPA scale down not happening properly
於是我棄用了 type: Resource
,改用 external metrics
功能,透過 HPA 存取 Prometheus 提供 metrics API 進行 autoscaling ,但是問題 仍然發生:
我還發現,如果調高 threshold 到一個很大的值,例如 memory 我調整為 400%,則 HPA 會下降 replica count 為 2,我推測 HPA 顯示的值與實際計算的內容不相符。
問題解決
後來我在相同 Kubernetes 版本的 GKE 上部署一個全新的服務進行測試,所有 Demo codes 都來自 Kubernetes HorizontalPodAutoscaler Walkthrough,結果十分正常,證實這個 k8s 版本的 HPA 是可運作的,於是我開始調查 Demo codes 與 客戶端服務的差異。
最後發現,客戶服務中的 Deployment 中的 selector.matchlabels
有重複的情況,而重複的 selector.matchlabels
可能會導致 HPA 產生 bug:
將其改為不同的 selector.matchlabels
後,HPA 的行為就完全符合預期了:
- 壓測前 (CPU: 1% / 50%, Memory: 26% / 80%, replica: 1)
- 壓測後 (CPU: 102% / 50%, Memory: 41% / 80%, replica: 3 (still scaling…))
結論
後續有找到相關的 issue: Overlapping Deployment matchLabels causes HPA to not scale correctly, 我相信我遇到的 bug 正如底下的留言有關,是由於 HPA 的行為所導致。
我沒有官方文件中看到相關的文章,也沒有證實 HPA 工作行為是否跟這則留言相符,不過確實在改變 selector.matchlabels
後,問題再也沒有發生。