hpa原理详解
-
1. hpa介紹
-
1.1 hpa是什么
-
1.2 hpa如何用起來
-
-
2. hpa 源碼分析
-
2.1 啟動參數介紹
-
2.2 啟動流程
-
2.3 核心計算邏輯
-
2.4 計算期望副本數量
-
2.4.1 GetRawMetric-具體的metric值
-
2.4.2 calcPlainMetricReplicas-計算期望副本值
-
-
-
3. 舉例說明計算過程
-
3.1 hpa擴容計算邏輯
-
3.2 場景1
-
3.3 場景2
-
-
4. 總結
本章重點: 從源碼角度分析hpa的計算邏輯
1. hpa介紹
1.1 hpa是什么
hpa指的是 Pod 水平自動擴縮,全名是Horizontal Pod Autoscaler簡稱HPA。它可以基于 CPU 利用率或其他指標自動擴縮 ReplicationController、Deployment 和 ReplicaSet 中的 Pod 數量。
用處: 用戶可以通過設置hpa,實現deploy pod數量的自動擴縮容。比如流量大的時候,pod數量多一些。流量小的時候,Pod數量降下來,避免資源浪費。
1.2 hpa如何用起來
(1)需要一個deploy/svc等,可以參考社區
(2)需要對應的hpa
舉例:
(1) 創建1個deploy。這里只有1個副本
apiVersion: apps/v1 kind: Deployment metadata:labels:app: zx-hpa-testname: zx-hpa spec:strategy:type: RollingUpdaterollingUpdate:maxSurge: 1replicas: 2selector:matchLabels:app: zx-hpa-testtemplate:metadata:labels:app: zx-hpa-testname: zx-hpa-testspec:terminationGracePeriodSeconds: 5containers:- name: busyboximage: busybox:latestimagePullPolicy: IfNotPresentcommand:- sleep- "3600"(2)創建對應的hpa。
apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler metadata:name: nginx-hpa-zx-1annotations:metric-containerName: zx-hpa spec:scaleTargetRef:apiVersion: apps/v1 ? // 這里必須指定需要監控那個對象kind: Deploymentname: zx-hpaminReplicas: 1 ? ? ? ? // deploy最小的Pod數量maxReplicas: 3 ? ? ? ? // deploy最大的Pod數量metrics:- type: Podspods:metricName: pod_cpu_1mtargetAverageValue: 60hpa是從同命名空間下,找對應的deploy。所以yaml中指定deploy的時候不要指定namespaces。這也就要求,hpa 和deploy必須在同一命名空間。
這里我使用的 pod_cpu_1m這個指標。這是一個自定義指標。接下來就是分析
創建好之后,觀察hpa,當deploy的cpu利用率變化時,deploy的副本會隨之改變。
2. hpa 源碼分析
2.1 啟動參數介紹
hpa controller隨controller manager的初始化而啟動,hpa controller將以下flag添加到controller manager的flag中,通過controller manager的CLI端暴露給用戶:
// AddFlags adds flags related to HPAController for controller manager to the specified FlagSet. func (o *HPAControllerOptions) AddFlags(fs *pflag.FlagSet) {if o == nil {return} ?fs.DurationVar(&o.HorizontalPodAutoscalerSyncPeriod.Duration, "horizontal-pod-autoscaler-sync-period", o.HorizontalPodAutoscalerSyncPeriod.Duration, "The period for syncing the number of pods in horizontal pod autoscaler.")fs.DurationVar(&o.HorizontalPodAutoscalerUpscaleForbiddenWindow.Duration, "horizontal-pod-autoscaler-upscale-delay", o.HorizontalPodAutoscalerUpscaleForbiddenWindow.Duration, "The period since last upscale, before another upscale can be performed in horizontal pod autoscaler.")fs.MarkDeprecated("horizontal-pod-autoscaler-upscale-delay", "This flag is currently no-op and will be deleted.")fs.DurationVar(&o.HorizontalPodAutoscalerDownscaleStabilizationWindow.Duration, "horizontal-pod-autoscaler-downscale-stabilization", o.HorizontalPodAutoscalerDownscaleStabilizationWindow.Duration, "The period for which autoscaler will look backwards and not scale down below any recommendation it made during that period.")fs.DurationVar(&o.HorizontalPodAutoscalerDownscaleForbiddenWindow.Duration, "horizontal-pod-autoscaler-downscale-delay", o.HorizontalPodAutoscalerDownscaleForbiddenWindow.Duration, "The period since last downscale, before another downscale can be performed in horizontal pod autoscaler.")fs.MarkDeprecated("horizontal-pod-autoscaler-downscale-delay", "This flag is currently no-op and will be deleted.")fs.Float64Var(&o.HorizontalPodAutoscalerTolerance, "horizontal-pod-autoscaler-tolerance", o.HorizontalPodAutoscalerTolerance, "The minimum change (from 1.0) in the desired-to-actual metrics ratio for the horizontal pod autoscaler to consider scaling.")fs.BoolVar(&o.HorizontalPodAutoscalerUseRESTClients, "horizontal-pod-autoscaler-use-rest-clients", o.HorizontalPodAutoscalerUseRESTClients, "If set to true, causes the horizontal pod autoscaler controller to use REST clients through the kube-aggregator, instead of using the legacy metrics client through the API server proxy. This is required for custom metrics support in the horizontal pod autoscaler.")fs.DurationVar(&o.HorizontalPodAutoscalerCPUInitializationPeriod.Duration, "horizontal-pod-autoscaler-cpu-initialization-period", o.HorizontalPodAutoscalerCPUInitializationPeriod.Duration, "The period after pod start when CPU samples might be skipped.")fs.MarkDeprecated("horizontal-pod-autoscaler-use-rest-clients", "Heapster is no longer supported as a source for Horizontal Pod Autoscaler metrics.")fs.DurationVar(&o.HorizontalPodAutoscalerInitialReadinessDelay.Duration, "horizontal-pod-autoscaler-initial-readiness-delay", o.HorizontalPodAutoscalerInitialReadinessDelay.Duration, "The period after pod start during which readiness changes will be treated as initial readiness.") }| horizontal-pod-autoscaler-sync-period | 15s | controller同步HPA信息的同步周期 |
| horizontal-pod-autoscaler-downscale-stabilization | 5m | 縮容穩定窗口,縮容間隔時間(v1.12支持) |
| horizontal-pod-autoscaler-tolerance | 0.1 | 最小縮放容忍度:計算出的期望值和實際值的比率<最小容忍比率,則不進行擴縮容 |
| horizontal-pod-autoscaler-cpu-initialization-period | 5m | pod剛啟動時,一定時間內的CPU使用率數據不參與計算。 |
| horizontal-pod-autoscaler-initial-readiness-delay | 30s | 擴容等待pod ready的時間(無法得知pod何時就緒) |
kcm中需要設置這個,才能啟動自定義的rest-clients。 –horizontal-pod-autoscaler-use-rest-clients=true
2.2 啟動流程
代碼流程:
startHPAControllerWithMetricsClient -> startHPAControllerWithMetricsClient -> Run -> worker -> processNextWorkItem -> reconcileKey->reconcileAutoscaler
func (a *HorizontalController) reconcileKey(key string) (deleted bool, err error) {namespace, name, err := cache.SplitMetaNamespaceKey(key)if err != nil {return true, err} ?hpa, err := a.hpaLister.HorizontalPodAutoscalers(namespace).Get(name)if errors.IsNotFound(err) {klog.Infof("Horizontal Pod Autoscaler %s has been deleted in %s", name, namespace)delete(a.recommendations, key)return true, nil} ?return false, a.reconcileAutoscaler(hpa, key) }2.3 核心計算邏輯
metric的定義類型分為3種,resource、pods和external,這里只分析pods類型的metric。
reconcileAutoscaler函數就是hpa的核心函數。該函數主要邏輯如下:
-
1.做一些類型轉換,用于接下來的Hpa計算
-
2.計算hpa 的期望副本數量。
-
3.根據計算的結果判斷是否需要改變副本數,需要改變的話,調用接口修改,然后做錯誤處理。
這里主要關心第二個步驟:hpa如何計算期望副本數量
2.4 計算期望副本數量
概念:
最小值:minReplicas。 這個是用戶在hpa里面的yaml設置的。這個是可選的,如果不設置,默認是1。
最大值:MaxReplicas。 這個是用戶在hpa里面的yaml設置的。這個必填的,如果不設置,會報錯, 如下。
當前值:currentReplicas。這個是hpa獲得的當前deploy的副本數量。
期望值:desiredReplicas。 這個是hpa希望deploy的副本數量。
error: error validating "nginx-deployment-hpa-test.yaml": error validating data: ValidationError(HorizontalPodAutoscaler.spec): missing required field "maxReplicas" in io.k8s.api.autoscaling.v2beta1.HorizontalPodAutoscalerSpec; if you choose to ignore these errors, turn validation off with --validate=false計算邏輯分為兩部分,第一種情況是不需要算,就可以直接得出期望值。 第二種情況需要調用函數計算。
情況1:不需要計算
(1)當前值等于0。 期望值=0. 不擴容,
(2)當前值 > 最大值。 沒必要計算期望值。 期望值=最大值,需要擴縮容。
(3)當前值 < 最小值。 沒必要計算期望值。 期望值=最小值,需要擴縮容。
情況2: 最小值 <= 當前值 <= 最大值。 需要調用函數計算 期望值。
這里的調用鏈為 computeReplicasForMetrics -> computeReplicasForMetric -> GetMetricReplicas
這里computeReplicasForMetrics有一個需要注意的點就是。這里可以處理了多個metric的情況。例如:這里一個hpa有多個指標。
- type: Resourceresource:name: cpu# Utilization類型的目標值,Resource類型的指標只支持Utilization和AverageValue類型的目標值target:type: UtilizationaverageUtilization: 50# Pods類型的指標- type: Podspods:metric:name: packets-per-second# AverageValue類型的目標值,Pods指標類型下只支持AverageValue類型的目標值target:type: AverageValueaverageValue: 1k這里hpa的邏輯是,誰最大取誰。例如, 通過cpu.Utilization hpa算出來應該需要 4個pod。 但是packets-per-second算出來需要5個。這個時候就已5個為準。見下面代碼:
// computeReplicasForMetrics computes the desired number of replicas for the metric specifications listed in the HPA, // returning the maximum of the computed replica counts, a description of the associated metric, and the statuses of // all metrics computed. func (a *HorizontalController) computeReplicasForMetrics(hpa *autoscalingv2.HorizontalPodAutoscaler, scale *autoscalingv1.Scale,metricSpecs []autoscalingv2.MetricSpec) (replicas int32, metric string, statuses []autoscalingv2.MetricStatus, timestamp time.Time, err error) { ?for i, metricSpec := range metricSpecs {replicaCountProposal, metricNameProposal, timestampProposal, condition, err := a.computeReplicasForMetric(hpa, metricSpec, specReplicas, statusReplicas, selector, &statuses[i]) ?if err != nil {if invalidMetricsCount <= 0 {invalidMetricCondition = conditioninvalidMetricError = err}invalidMetricsCount++}if err == nil && (replicas == 0 || replicaCountProposal > replicas) {timestamp = timestampProposalreplicas = replicaCountProposalmetric = metricNameProposal}} ?// If all metrics are invalid return error and set condition on hpa based on first invalid metric.if invalidMetricsCount >= len(metricSpecs) {setCondition(hpa, invalidMetricCondition.Type, invalidMetricCondition.Status, invalidMetricCondition.Reason, invalidMetricCondition.Message)return 0, "", statuses, time.Time{}, fmt.Errorf("invalid metrics (%v invalid out of %v), first error is: %v", invalidMetricsCount, len(metricSpecs), invalidMetricError)}setCondition(hpa, autoscalingv2.ScalingActive, v1.ConditionTrue, "ValidMetricFound", "the HPA was able to successfully calculate a replica count from %s", metric)return replicas, metric, statuses, timestamp, nil }針對具體某個metric指標。計算分為倆步:
(1)GetRawMetric函數: 得到 具體的metric值
(2)calcPlainMetricReplicas :計算期望副本值
這里需要注意一點就是targetUtilization進行了數據轉換。乘以了10^3。
// GetMetricReplicas calculates the desired replica count based on a target metric utilization // (as a milli-value) for pods matching the given selector in the given namespace, and the // current replica count func (c *ReplicaCalculator) GetMetricReplicas(currentReplicas int32, targetUtilization int64, metricName string, namespace string, selector labels.Selector, metricSelector labels.Selector) (replicaCount int32, utilization int64, timestamp time.Time, err error) {metrics, timestamp, err := c.metricsClient.GetRawMetric(metricName, namespace, selector, metricSelector)if err != nil {return 0, 0, time.Time{}, fmt.Errorf("unable to get metric %s: %v", metricName, err)} ?replicaCount, utilization, err = c.calcPlainMetricReplicas(metrics, currentReplicas, targetUtilization, namespace, selector, v1.ResourceName(""))return replicaCount, utilization, timestamp, err }2.4.1 GetRawMetric-具體的metric值
// GetRawMetric gets the given metric (and an associated oldest timestamp) // for all pods matching the specified selector in the given namespace func (c *customMetricsClient) GetRawMetric(metricName string, namespace string, selector labels.Selector, metricSelector labels.Selector) (PodMetricsInfo, time.Time, error) {// 1.這里直接調用 GetForObjects,發送restful請求獲取數據metrics, err := c.client.NamespacedMetrics(namespace).GetForObjects(schema.GroupKind{Kind: "Pod"}, selector, metricName, metricSelector)if err != nil {return nil, time.Time{}, fmt.Errorf("unable to fetch metrics from custom metrics API: %v", err)} ?if len(metrics.Items) == 0 {return nil, time.Time{}, fmt.Errorf("no metrics returned from custom metrics API")}// 2. 對獲取的數據進行處理。這里看起來是乘以了 10^3res := make(PodMetricsInfo, len(metrics.Items))for _, m := range metrics.Items {window := metricServerDefaultMetricWindowif m.WindowSeconds != nil {window = time.Duration(*m.WindowSeconds) * time.Second}res[m.DescribedObject.Name] = PodMetric{Timestamp: m.Timestamp.Time,Window: ? window,Value: ? ? int64(m.Value.MilliValue()),} ?m.Value.MilliValue()} ?timestamp := metrics.Items[0].Timestamp.Time ?return res, timestamp, nil }2.4.2 calcPlainMetricReplicas-計算期望副本值
這里代碼省略,直接貼邏輯。
3.1 先從apiserver端拿到所有相關的pod,將這些pod分為三類:
a.missingPods用于記錄處于running狀態,但不提供該metric的pod ? b.ignoredPods 用于處理resource類型cpu相關metric的延遲(就是pod未就緒),這里不深入討論 ? c.readyPodCount記錄狀態為running,且能提供該metric的pod3.2 調用GetMetricUtilizationRatio計算實際值與期望值的對比情況。計算時,對于所有可獲取到metric的pod,取它們metric value的平均值得到:usageRatio=實際值/期望值;utilization=實際值(平均)
3.3 計算期望pod數量DesiredReplicas。對于missingPods為0,即所有target pod都處于running可獲取metric value的情況:
a.如果實際值與期望值的對比usageRatio處于可容忍范圍內,不執行scale操作。默認情況下c.tolerance=0.1,即usageRatio處于
[0.9,1.1]時pod數量不變化
if math.Abs(1.0-usageRatio) <= c.tolerance {// return the current replicas if the change would be too smallreturn currentReplicas, utilization, nil }b.實際值與期望值的對比usageRatio不在可容忍范圍內,向上取整得到desiredReplicas return int32(math.Ceil(usageRatio * float64(readyPodCount))), utilization, nil
對于missingPods>0,即有target pod的metric value沒有獲取到的情況。 縮容時,對于找不到metric的pod,視為正好用了desired value
if usageRatio < 1.0 { // on a scale-down, treat missing pods as using 100% of the resource request for podName := range missingPods {metrics[podName] = metricsclient.PodMetric{Value: targetUtilization}} }擴容時,對于找不到metric的pod,視為該pod對指定metric的使用量為0
for podName := range missingPods {metrics[podName] = metricsclient.PodMetric{Value: 0} }經過上面的處理后,重新計算實際值與期望值的對比newUsageRatio。
在下面兩種情況下,不執行scale操作:新的實際值與期望值的對比newUsageRatio在容忍范圍內; 賦值處理前后,一個需要scale up,另一個需要scale down。
其它情況下,同樣地執行向上取整操作
if math.Abs(1.0-newUsageRatio) <= c.tolerance || (usageRatio < 1.0 && newUsageRatio > 1.0) || (usageRatio > 1.0 && newUsageRatio < 1.0) {// return the current replicas if the change would be too small,// or if the new usage ratio would cause a change in scale directionreturn currentReplicas, utilization, nil} return int32(math.Ceil(newUsageRatio * float64(len(metrics)))), utilization, nil最后,Hpa將desiredReplicas寫到scale.Spec.Replicas,調用a.scaleNamespacer.Scales(hpa.Namespace).Update(targetGR, scale)向apiserver發送更新hpa的請求,對某個hpa的一輪更新操作就完成了。
3. 舉例說明計算過程
3.1 hpa擴容計算邏輯
關鍵概念:tolerance(hpa擴容容忍度), 默認為0.1。
Custom server: 自定義metric服務。這里是一個抽象,用于給hpa提供具體的metric值。Custom server具體可以是prometheus,或者其他的監控系統。下一篇文章會講如何將Custom server和hpa聯系起來。
3.2 場景1
當前有deployA, 運行著倆個pod, A1和A2。 deploy設置了hpa,指標是內存使用量,并且規定,當平均使用量大于60就要擴容。
hpa擴容計算步驟:
第一步: 往monitor-adaptor發送請求, 要求獲得deployA下所有pod的metric值。 這里收到了 A1=50; A2=100
第二步: 補全metric值,給獲取不到metric值的pod賦值。 這里hpa會查看集群狀態,發現deployA 下有倆個pod,A1,A2。并且這兩個pod的metric值都獲取到了。 這個時候就不用補全。(下面例子就介紹需要補全metric的情況)
第三步: 開始計算
(1)計算 平均pod metric值和 target的比例。也可以叫擴容比例系數
ratio = (A1+A2)/(2*target) = (50+100)/120 = 1.25按理說不用再除target值,直接(50+100)/2=75,然后拿75和60比就行。 75比60大就應該擴容。
這里使用系數表示主要有倆個原因:
-
有容忍度的概念,使用比例方便和計算是否超出了容忍度
-
用于擴縮容計算
(2)判斷是否超過容忍度
這里 1.25-1 > 0.1(默認容忍度)。 因此這種情況是需要擴容的。
這里就體現了容忍度的作用。有了容忍度, 平均metric需要大于 66才會擴容(60*1.1)
(3)計算真正的副本數量
向上取整: 擴容比例系數*當前的副本數
這里就是: 1.25*2 = 2.5 , 取整后就是3。
3.3 場景2
和場景1不同在于:由于某件原因,導致 monitor-adaptor往hpa發送的時候,只有 A1=20。 A2的數據丟失。
?
?
hpa擴容計算步驟:
第一步: 往monitor-adaptor發送請求, 要求獲得deployA下所有pod的metric值。 這里收到了 A1=2;
第二步: 補全metric值,給獲取不到metric值的pod賦值。 這里hpa會查看集群狀態,發現deployA 下有倆個pod,A1,A2。但是這里發現只有A1的值,這個時候hpa就認為A2 有數據,但是獲取失敗。所以就會給A2自己賦值, 0/target。
賦值邏輯如下: 當 A1 > target的時候,A2=0; 當A1<= target的時候,賦值為 target。
這里由于 A1=2, 比target(60)小,所以最終hpa計算時:
A1=2; A2=60; target=60;
第三步: 開始計算
(1)計算 平均pod metric值和 target的比例。也可以叫擴容比例系數
ratio = (A1+A2)/(2*target) = (2+60)/120 = 0.517(2)判斷是否超過容忍度
這里 1-0.517 > 0.1(默認容忍度)。 因此這種情況是需要縮容的。
(3)計算真正的副本數量
向上取整: 擴容比例系數*當前的副本數(這里就是metric數量,A1,A2)
對應就是: 0.517*2 = 1.034 , 取整后就是2。
4. 總結
(1)hpa可以設置多個metric。當有多個metric時,誰算出來的副本值最大,取誰的值
(2)針對具體的metric而言(這里是以pods這種為例),首先獲得用戶定義的hpa指標。比如最大值,最小值,閾值等。
這里有一個點在于。閾值乘以了1000用于計算。
(3)獲取metric的值,這里是使用了自定義rest服務。hpa只要發送rest請求,就有數據。這種情況非常適用于公司使用自己的監控數據做擴縮容。 注意:這里每個值也乘以了1000。這樣和閾值就是相互抵消了。
(4)利用公式計算期望值。 期望值*X <= 當前pod所有的metric值。X取小的正整數。具體邏輯可以看上文的計算過程。
總結
- 上一篇: 【区块链实战】什么是 P2P 网络,区块
- 下一篇: Qt框架之情人节玫瑰花案例项目