對於kubernetes基礎性的知識,目前有不少資料,因而不會重複展開,想作一個對每一個模塊都深刻講解的系列,包括基礎使用,源碼解讀,和實踐中遇到的問題等,因此篇幅很比較長。html
(1) kubernetes版本:v1.9.2算法
(2) 適合對kubernetes基礎有必定了解的人羣api
HPA是kubernetes中自動水平擴展模塊,基於用戶設定和獲取到的指標(CPU,Memory,自定義metrics),對Pod進行伸縮(不是直接操做pod)。HPA controller屬於Controller Manager的一個controller。bash
咱們能夠在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
複製代碼
kubernetes中的代碼都是有必定的「套路」(後面會專門寫一篇來深刻分析這種「套路」),咱們首先從api入手,再到controller工具
這是一個標準的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 // 目前的狀態
}
複製代碼
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
}
}
複製代碼
雖說hpa能根據指標對pod進行彈性伸縮,達到根據使用量擴展機器的功能,可是,在實際運用中,我發現瞭如下的問題,但願能給要使用該模塊的人帶來一些啓發。
咱們遇到了這樣的一個業務場景:在某個時間段會忽然流量劇增十倍,此時因爲以前是處於低流量狀態,replicas一直處於較低值,那麼此時擴容因爲擴容算法的限制(最多爲2倍),此時擴容的數量是不足夠的。而後,一樣因爲擴容算法的限制,兩次擴容週期默認爲不低於三分鐘,那麼將沒法在短時間內到達一個理想的副本數。此時從監控上看pod的數量圖以下:
那麼這樣將會形成很大的問題,沒法及時處理這種實時性高的業務場景。同時,咱們還遇到了這樣的業務狀況,在一次大量擴容後,流量劇減,pod數量降到了一個極低值,可是因爲出現業務流量的抖動,在接下來很短期內,再一次出現大流量,此時pod數量沒法處理如此高的流量,影響業務的SLA等。
1,利用多指標 若是隻使用單一指標,例如CPU,整個hpa將嚴重依賴於這項指標,該指標的準確性等直接影響整個hpa。在這裏,咱們使用CRD進行了多指標的開發,結合某個業務的具體場景,開發合適的指標,而後結合着CPU等指標一塊兒使用。
2,調整默認參數 默認的擴容和縮容週期不必定是最合適大家的業務的,因此能夠根據業務自身的狀況進行調整。
3,自行開發hpa controller 這裏還有一個思路是修改hpa controller,可是這樣將會不利於之後的升級。因此能夠自行開發hpa controller,自行定義最使用大家業務的擴容縮容算法便可。可是這樣的開發成本就稍微有點大。
(1) 支持deployment的滾動升級,不支持RC的滾動升級
(1) 若是是我,會如何來設計
提出本身的愚蠢的思路:若是我是hpa這塊的負責人,那麼我將會將擴容和縮容算法這塊寫成是可擴展的,用戶可自定義的擴容縮容步長,可針對每一個hpa自定義擴容縮容的時間間隔,這樣將會大大方便使用。
該版本對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的狀態