一,前言
對於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
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的狀態