優(yōu)化 Kubernetes 橫向擴(kuò)縮容 HPA

圖片來(lái)源: instagram.com/febin_raj
Pod水平自動(dòng)擴(kuò)縮(Horizontal Pod Autoscaler, 簡(jiǎn)稱HPA)可以基于 CPU/MEM 利用率自動(dòng)擴(kuò)縮Deployment、StatefulSet 中的 Pod 數(shù)量,同時(shí)也可以基于其他應(yīng)程序提供的自定義度量指標(biāo)來(lái)執(zhí)行自動(dòng)擴(kuò)縮。默認(rèn)HPA可以滿足一些簡(jiǎn)單場(chǎng)景,對(duì)于生產(chǎn)環(huán)境并不一定適合,本文主要分析HPA的不足與優(yōu)化方式。
HPA Resource類型不足
默認(rèn)HPA提供了Resource類型,通過(guò)CPU/MEM使用率指標(biāo)(由metrics-server提供原始指標(biāo))來(lái)擴(kuò)縮應(yīng)用。
使用率計(jì)算方式
在Resource類型中,使用率計(jì)算是通過(guò)request而不是limit,源碼如下:
// 獲取Pod resource request
func calculatePodRequests(pods []*v1.Pod, resource v1.ResourceName) (map[string]int64, error) {
requests := make(map[string]int64, len(pods))
for _, pod := range pods {
podSum := int64(0)
for _, container := range pod.Spec.Containers {
if containerRequest, ok := container.Resources.Requests[resource]; ok {
podSum += containerRequest.MilliValue()
} else {
return nil, fmt.Errorf("missing request for %s", resource)
}
}
requests[pod.Name] = podSum
}
return requests, nil
}
// 計(jì)算使用率
func GetResourceUtilizationRatio(metrics PodMetricsInfo, requests map[string]int64, targetUtilization int32) (utilizationRatio float64, currentUtilization int32, rawAverageValue int64, err error) {
metricsTotal := int64(0)
requestsTotal := int64(0)
numEntries := 0
for podName, metric := range metrics {
request, hasRequest := requests[podName]
if !hasRequest {
// we check for missing requests elsewhere, so assuming missing requests == extraneous metrics
continue
}
metricsTotal += metric.Value
requestsTotal += request
numEntries++
}
currentUtilization = int32((metricsTotal * 100) / requestsTotal)
return float64(currentUtilization) / float64(targetUtilization), currentUtilization, metricsTotal / int64(numEntries), nil
}
通常在Paas平臺(tái)中會(huì)對(duì)資源進(jìn)行超配,limit即用戶請(qǐng)求資源,request即實(shí)際分配資源,如果按照request來(lái)計(jì)算使用率(會(huì)超過(guò)100%)是不符合預(yù)期的。相關(guān)issue見(jiàn)72811,目前還存在爭(zhēng)論??梢孕薷脑创a,或者使用自定義指標(biāo)來(lái)代替。
多容器Pod使用率問(wèn)題
默認(rèn)提供的Resource類型的HPA,通過(guò)上述方式計(jì)算資源使用率,核心方式如下:
metricsTotal = sum(pod.container.metricValue)
requestsTotal = sum(pod.container.Request)
currentUtilization = int32((metricsTotal * 100) / requestsTotal)
計(jì)算出所有container的資源使用量再比總的申請(qǐng)量,對(duì)于單容器Pod這沒(méi)影響。但對(duì)于多容器Pod,比如Pod包含多個(gè)容器con1、con2(request都為1cpu),con1使用率10%,con2使用率100%,HPA目標(biāo)使用率60%,按照目前方式得到使用率為55%不會(huì)進(jìn)行擴(kuò)容,但實(shí)際con2已經(jīng)達(dá)到資源瓶頸,勢(shì)必會(huì)影響服務(wù)質(zhì)量。當(dāng)前系統(tǒng)中,多容器Pod通常都是1個(gè)主容器與多個(gè)sidecar,依賴主容器的指標(biāo)更合適點(diǎn)。
好在1.20版本中已經(jīng)支持了ContainerResource可以配置基于某個(gè)容器的資源使用率來(lái)進(jìn)行擴(kuò)縮,如果是之前的版本建議使用自定義指標(biāo)替換。
性能問(wèn)題
單線程架構(gòu)
默認(rèn)的hpa-controller是單個(gè)Goroutine執(zhí)行的,隨著集群規(guī)模的增多,勢(shì)必會(huì)成為性能瓶頸,目前默認(rèn)hpa資源同步周期會(huì)15s,假設(shè)每個(gè)metric請(qǐng)求延時(shí)為100ms,當(dāng)前架構(gòu)只能支持150個(gè)HPA資源(保證在15s內(nèi)同步一次)
func (a *HorizontalController) Run(stopCh <-chan struct{}) {
// ...
// start a single worker (we may wish to start more in the future)
go wait.Until(a.worker, time.Second, stopCh)
<-stopCh
}
可以通過(guò)調(diào)整worker數(shù)量來(lái)橫向擴(kuò)展,已提交PR。
調(diào)用鏈路
在hpa controller中一次hpa資源同步,需要調(diào)用多次apiserver接口,主要鏈路如下
通過(guò) scaleForResourceMappings得到scale資源調(diào)用 computeReplicasForMetrics獲取metrics value調(diào)用 Scales().Update更新計(jì)算出的副本數(shù)
尤其在獲取metrics value時(shí),需要先調(diào)用apiserver,apiserver調(diào)用metrics-server/custom-metrics-server,當(dāng)集群內(nèi)存在大量hpa時(shí)可能會(huì)對(duì)apiserver性能產(chǎn)生一定影響。
其他
對(duì)于自定義指標(biāo)用戶需要實(shí)現(xiàn)custom.metrics.k8s.io或external.metrics.k8s.io,目前已經(jīng)有部分開(kāi)源實(shí)現(xiàn)見(jiàn)custom-metrics-api。
另外,hpa核心的擴(kuò)縮算法根據(jù)當(dāng)前指標(biāo)和期望指標(biāo)來(lái)計(jì)算擴(kuò)縮比例,并不適合所有場(chǎng)景,只使用線性增長(zhǎng)的指標(biāo)。
期望副本數(shù) = ceil[當(dāng)前副本數(shù) * (當(dāng)前指標(biāo) / 期望指標(biāo))]
watermarkpodautoscaler提供了更靈活的擴(kuò)縮算法,比如平均值、水位線等,可以作為參考。
總結(jié)
Kubernetes提供原生的HPA只能滿足一部分場(chǎng)景,如果要上生產(chǎn)環(huán)境,必須對(duì)其做一些優(yōu)化,本文總結(jié)了當(dāng)前HPA存在的不足,例如在性能、使用率計(jì)算方面,并提供了解決思路。
?本文鏈接: https://qingwave.github.io/k8s-hpa-enchance/
?
