kubernetes的HPA模組深度講解
對於kubernetes基礎性的知識,目前有很多資料,於是不會重複展開,想做一個對每個模組都深入講解的系列,包括基礎使用,原始碼解讀,和實踐中遇到的問題等,所以篇幅很比較長。
二,HPA模組
1,相關說明
(1) kubernetes版本:v1.9.2
(2) 適合對kubernetes基礎有一定了解的人群
2,基本概念和使用
(1) 概念
HPA是kubernetes中自動水平擴充套件模組,基於使用者設定和獲取到的指標(CPU,Memory,自定義metrics),對Pod進行伸縮(不是直接操作pod)。HPA controller屬於Controller Manager的一個controller。
(2) 基本使用
我們可以在pkg/apis/autoscaling 下可以看到,目前是有兩個版本:v1(僅支援CPU指標),v2beta1(支援CPU和Memory和自定義指標)。下面看一下,一個比較全面的hpa的寫法。
kind: HorizontalPodAutoscaler apiVersion: autoscaling/v2beta1 metadata: name: example-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: example-app minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu targetAverageUtilization: 50 - type: Resource resource: name: memory targetAverageUtilization: 50 - type: Pods pods: metricName: receive_bytes_total targetAverageValue: 100 - type: Object object: target: kind: endpoints name: example-app metricName: request_total targetValue: 500m 複製程式碼
3,原始碼分析
kubernetes中的程式碼都是有一定的“套路”(後面會專門寫一篇來深入分析這種“套路”),我們首先從api入手,再到controller
(1) api
這是一個標準的kubernetes的api寫法(可使用官方工具生成),register.go中添加了三個type:HorizontalPodAutoscaler/HorizontalPodAutoscalerList/Scale。接下來看types.go中關於這幾個的定義。對應上面的yaml定義來看。
// 1,HorizontalPodAutoscaler type HorizontalPodAutoscaler struct { ...... Spec HorizontalPodAutoscalerSpec Status HorizontalPodAutoscalerStatus ...... } // 使用者設定的值 type HorizontalPodAutoscalerSpec struct { MinReplicas *int32//設定的最小的replicas MaxReplicas int32//設定的最大的replicas Metrics []MetricSpec } // hpa的目前的狀態 type HorizontalPodAutoscalerStatus struct { ObservedGeneration *int64//觀察的最近的generaction LastScaleTime *metav1.Time//上次伸縮的時間 CurrentReplicas int32//目前的replicas數量 DesiredReplicas int32//期望的replicas數量 CurrentMetrics []MetricStatus//最近一次觀察到的metrics資料 Conditions []HorizontalPodAutoscalerCondition//在某個特定點的hpa狀態 } // Metrics定義 type MetricSpec struct { Type MetricSourceType//metrics type Object *ObjectMetricSource// Object型別的metrics定義 Pods *PodsMetricSource// pods型別的metrics定義 Resource *ResourceMetricSource // Resource型別的metrics定義 } // 2,Scale 是resources的一次scaling請求,最後hpa都是要使用這個來實現 type Scale struct { Spec ScaleSpec// 期望到達的狀態 Status ScaleStatus// 目前的狀態 } 複製程式碼
(2) controller
api定義完後,需要有一個controller來保證系統的狀態能符合我們定義的要求,這時候就需要hpa controller了,hpa controller通過從apiserver中獲取各個指標的值,根據特定的伸縮演算法,來維持在預期的狀態。
上面說過,hpa controller屬於controller manager,於是我們去cmd/kube-controller-manager下,經過一路跟蹤,可以看到hpa controller的啟動邏輯在options/autoscaling.go中
func startHPAController(ctx ControllerContext) (bool, error) { // 需要包含v1版本 if !ctx.AvailableResources[schema.GroupVersionResource{Group: "autoscaling", Version: "v1", Resource: "horizontalpodautoscalers"}] { return false, nil } // 如果要使用自定義metrics,需要開啟該選項 if ctx.Options.HorizontalPodAutoscalerUseRESTClients { return startHPAControllerWithRESTClient(ctx) } // 從Heapster拉取資料 return startHPAControllerWithLegacyClient(ctx) } func startHPAControllerWithMetricsClient(ctx ControllerContext, metricsClient metrics.MetricsClient) (bool, error) { ....... // 核心引數,根據metrics計算相應的replicas replicaCalc := podautoscaler.NewReplicaCalculator( metricsClient, hpaClient.CoreV1(), ctx.Options.HorizontalPodAutoscalerTolerance, ) // 新建HorizontalController go podautoscaler.NewHorizontalController( hpaClientGoClient.CoreV1(), scaleClient, // scale相關客戶端,實現最終的pod伸縮 hpaClient.AutoscalingV1(), restMapper, replicaCalc, // 副本計算器 ctx.InformerFactory.Autoscaling().V1().HorizontalPodAutoscalers(), //infomer ctx.Options.HorizontalPodAutoscalerSyncPeriod.Duration, // hpa獲取資料的間隔 ctx.Options.HorizontalPodAutoscalerUpscaleForbiddenWindow.Duration, // hpa擴容最低間隔 ctx.Options.HorizontalPodAutoscalerDownscaleForbiddenWindow.Duration, // hpa縮容最低間隔 ).Run(ctx.Stop) return true, nil } // 接下來看HorizontalController的定義 type HorizontalController struct { scaleNamespacer scaleclient.ScalesGetter// 負責scale的get和update hpaNamespacerautoscalingclient.HorizontalPodAutoscalersGetter // 負責HorizontalPodAutoscaler的Create, Update, UpdateStatus, Delete, Get, List, Watch等 mapperapimeta.RESTMapper replicaCalc*ReplicaCalculator // 負責根據指標計算replicas eventRecorder record.EventRecorder//event記錄 upscaleForbiddenWindowtime.Duration downscaleForbiddenWindow time.Duration // 從informer中list/get資料 hpaListerautoscalinglisters.HorizontalPodAutoscalerLister hpaListerSynced cache.InformerSynced queue workqueue.RateLimitingInterface } 複製程式碼
開始Run後,就是controller開發的那一套流程了,設計到相關的informer,workerqueue就不展開了,最關鍵的是下面的reconcileAutoscaler,其實就是通過一系列演算法調節當前副本數,期望副本數,邊界(最大最小)副本數三者的關係。(接下來分析可能比較長,只擷取部分關鍵程式碼,注意看註釋)
func (a *HorizontalController) reconcileAutoscaler(hpav1Shared *autoscalingv1.HorizontalPodAutoscaler) error { // 通過namespace和name獲取對應的scale scale, targetGR, err := a.scaleForResourceMappings(hpa.Namespace, hpa.Spec.ScaleTargetRef.Name, mappings) // 獲取當前副本 currentReplicas := scale.Status.Replicas rescale := true // 副本為0,則不進行scale操作 if scale.Spec.Replicas == 0 { desiredReplicas = 0 rescale = false // 當前副本大於期望的最大副本數量,不進行操作 } else if currentReplicas > hpa.Spec.MaxReplicas { rescaleReason = "Current number of replicas above Spec.MaxReplicas" desiredReplicas = hpa.Spec.MaxReplicas // 當前副本數小於期望的最小值 } else if hpa.Spec.MinReplicas != nil && currentReplicas < *hpa.Spec.MinReplicas { rescaleReason = "Current number of replicas below Spec.MinReplicas" desiredReplicas = *hpa.Spec.MinReplicas } // 當前副本為0也不進行操作 else if currentReplicas == 0 { rescaleReason = "Current number of replicas must be greater than 0" desiredReplicas = 1 } // 當前副本數量處於設定的Min和Max之間才進行操作 else { // 根據metrics指標計算對應的副本數 metricDesiredReplicas, metricName, metricStatuses, metricTimestamp, err = a.computeReplicasForMetrics(hpa, scale, hpa.Spec.Metrics) rescaleMetric := "" // 這裡是防止擴容過快,限制了最大隻能擴當前例項的兩倍 desiredReplicas = a.normalizeDesiredReplicas(hpa, currentReplicas, desiredReplicas) // 限制擴容和縮容間隔,預設是兩次縮容的間隔不得小於5min,兩次擴容的間隔不得小於3min rescale = a.shouldScale(hpa, currentReplicas, desiredReplicas, timestamp) } // 如果上面的限制條件都通過,則進行擴容,擴容注意通過scale實現 if rescale { scale.Spec.Replicas = desiredReplicas _, err = a.scaleNamespacer.Scales(hpa.Namespace).Update(targetGR, scale) } else { desiredReplicas = currentReplicas } } 複製程式碼
(3) 整套流程如下

4,HPA實戰經驗
雖然說hpa能根據指標對pod進行彈性伸縮,達到根據使用量擴充套件機器的功能,但是,在實際運用中,我發現了以下的問題,希望能給要使用該模組的人帶來一些啟發。
(1) 問題詳情
我們遇到了這樣的一個業務場景:在某個時間段會突然流量劇增十倍,此時由於之前是處於低流量狀態,replicas一直處於較低值,那麼此時擴容由於擴容演算法的限制(最多為2倍),此時擴容的數量是不足夠的。然後,同樣由於擴容演算法的限制,兩次擴容週期預設為不低於三分鐘,那麼將無法在短期內到達一個理想的副本數。此時從監控上看pod的數量圖如下:

那麼這樣將會造成很大的問題,無法及時處理這種實時性高的業務場景。同時,我們還遇到了這樣的業務情況,在一次大量擴容後,流量劇減,pod數量降到了一個極低值,但是由於出現業務流量的抖動,在接下來很短時間內,再一次出現大流量,此時pod數量無法處理如此高的流量,影響業務的SLA等。
(2) 問題解決思路
1,利用多指標 如果只使用單一指標,例如CPU,整個hpa將嚴重依賴於這項指標,該指標的準確性等直接影響整個hpa。在這裡,我們使用CRD進行了多指標的開發,結合某個業務的具體場景,開發合適的指標,然後結合著CPU等指標一起使用。
2,調整預設引數 預設的擴容和縮容週期不一定是最合適你們的業務的,所以可以根據業務自身的情況進行調整。
3,自行開發hpa controller 這裡還有一個思路是修改hpa controller,但是這樣將會不利於以後的升級。所以可以自行開發hpa controller,自行定義最使用你們業務的擴容縮容演算法即可。但是這樣的開發成本就稍微有點大。
三,總結
(1) TODO
接下來將會調研新版本中hpa的變化,看看是否有什麼好玩的新特性,再回來更新
(2) 如果是我,會如何來設計
提出自己的愚蠢的思路:如果我是hpa這塊的負責人,那麼我將會將擴容和縮容演算法這塊寫成是可擴充套件的,使用者可自定義的,這樣將會大大方便使用。