[kubernetes系列]HPA模塊深度講解

一,前言

對於kubernetes基礎性的知識,目前有不少資料,因而不會重複展開,想作一個對每一個模塊都深刻講解的系列,包括基礎使用,源碼解讀,和實踐中遇到的問題等,因此篇幅很比較長。html

二,HPA模塊

1,相關說明

(1) kubernetes版本:v1.9.2算法

(2) 適合對kubernetes基礎有必定了解的人羣api

2,基本概念和使用

(1) 概念

HPA是kubernetes中自動水平擴展模塊,基於用戶設定和獲取到的指標(CPU,Memory,自定義metrics),對Pod進行伸縮(不是直接操做pod)。HPA controller屬於Controller Manager的一個controller。bash

(2) 基本使用

咱們能夠在pkg/apis/autoscaling 下能夠看到,目前是有兩個版本:v1(僅支持CPU指標),v2beta1(支持CPU和Memory和自定義指標)。下面看一下,一個比較全面的hpa的寫法。app

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中獲取各個指標的值,根據特定的伸縮算法,來維持在預期的狀態。ui

上面說過,hpa controller屬於controller manager,因而咱們去cmd/kube-controller-manager下,通過一路跟蹤,能夠看到hpa controller的啓動邏輯在options/autoscaling.go中spa

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
	hpaNamespacer   autoscalingclient.HorizontalPodAutoscalersGetter // 負責HorizontalPodAutoscaler的Create, Update, UpdateStatus, Delete, Get, List, Watch等
	mapper          apimeta.RESTMapper 
	replicaCalc   *ReplicaCalculator // 負責根據指標計算replicas
	eventRecorder record.EventRecorder  //event記錄
	upscaleForbiddenWindow   time.Duration
	downscaleForbiddenWindow time.Duration
    // 從informer中list/get數據
	hpaLister       autoscalinglisters.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) 支持deployment的滾動升級,不支持RC的滾動升級

四,總結

(1) 若是是我,會如何來設計

提出本身的愚蠢的思路:若是我是hpa這塊的負責人,那麼我將會將擴容和縮容算法這塊寫成是可擴展的,用戶可自定義的擴容縮容步長,可針對每一個hpa自定義擴容縮容的時間間隔,這樣將會大大方便使用。

五,新版本特性研究(持續更新)

(1) v1.12.1

該版本對hpa這塊改動挺大,總之增長了不少靈活性和可用性

1,可經過設置HorizontalPodAutoscalerDownscaleStabilizationWindow來防止流量抖動形成pod忽然的減小。該版本中會記錄HorizontalPodAutoscalerDownscaleStabilizationWindow時間內的每次擴展pod數量,每次取該時間內最大值,而後嘗試儘可能到達該值

2,目前對擴容這塊仍是有所限制,仍是存在兩倍大小的限制,可是取消了擴容和縮容的時間間隔限制

3,可自行設定偏差容忍度

4,增長了pod readiness和missing metrics的考量。如何pod處於not ready狀態或者有丟失的metrics,那麼會將該pod閒置不處理。處理邏輯以下:若是是Resource和Pods的類型,那麼判斷pod是否ready的時候,首先pod的startTime加上cpuInitializationPeriod(可設置)大於當前時間,再判斷pod的狀態和metric獲取的時間,從而來判斷pod是否ready。pod的startTime加上cpuInitializationPeriod小於當前時間的狀況下,則根據pod的狀態和pod的啓動時間加上readiness-delay(可設置)來判斷pod是否ready。對於Object和External類型,則直接判斷pod的狀態

六,參考文獻

(1) imkira.com/a24.html

(2) kubernetes.io/docs/tasks/…

相關文章
相關標籤/搜索