乐趣区

关于云计算:Kubernetes-源码解析-HPA-水平自动伸缩如何工作

HPA – Horizontal Pod Autoscaler 的缩写,Pod 程度主动伸缩。通过对 Pod 负载的监控,来主动减少或者缩小 Pod 的正本数量。

从字面意思来看,其次要蕴含了两局部:

  • 监控 Pod 的负载
  • 管制 Pod 的正本数量

那具体是如何实现的呢?以下基于 1.17 源码,来剖析下 HPA 如何工作。

留神:文章中的代码在源码的根底上进行了精简:删掉了正文、序列化等信息,或保留了局部外围代码,加上新的正文。

资源

HPA 的资源是 HorizontalPodAutoscaler,在v1 版本中,只反对基于 CPU 指标的计算;在 v2beta2 版本中退出了基于内存和自定义指标的计算。

v1

//staging/src/k8s.io/api/autoscaling/v1/types.go
type HorizontalPodAutoscaler struct {
    metav1.TypeMeta 
    metav1.ObjectMeta 
    Spec HorizontalPodAutoscalerSpec 
    Status HorizontalPodAutoscalerStatus 
}
type HorizontalPodAutoscalerSpec struct {
    ScaleTargetRef CrossVersionObjectReference // 监控的指标资源
    MinReplicas *int32 // 最小正本数
    MaxReplicas int32  // 最大正本数
    TargetCPUUtilizationPercentage *int32  // 触发调整的 CPU 使用率
}

v2

//staging/src/k8s.io/api/autoscaling/v2beta2/types.go
type HorizontalPodAutoscaler struct {
    metav1.TypeMeta 
    metav1.ObjectMeta
    Spec HorizontalPodAutoscalerSpec
    Status HorizontalPodAutoscalerStatus 
}
type HorizontalPodAutoscalerSpec struct {
    ScaleTargetRef CrossVersionObjectReference // 监控的指标资源
    MinReplicas *int32 
    MaxReplicas int32
    Metrics []MetricSpec // 新退出的自定义指标}
type MetricSpec struct {
    Type MetricSourceType // 指标源的类型:Object(基于某个对象)、Pods(基于 pod 数)、Resource(基于资源应用计算,比方 v1 版本中 cpu)、External(基于内部的指标)。对应 MetricsClient 接口的四个办法
    Object *ObjectMetricSource  // 对应 Object 类型的指标源
    Pods *PodsMetricSource // 对应 Pod 类型的指标源
    Resource *ResourceMetricSource  // 对应 Resource 类型的指标源
    External *ExternalMetricSource  // 对应 External 类型的指标源
}
type ObjectMetricSource struct { 
    DescribedObject CrossVersionObjectReference  // 指标对象
    Target MetricTarget  // 指定指标的目标值、平均值或者均匀使用率
    Metric MetricIdentifier  // 指标标识:名字、label 选择器
}
type PodsMetricSource struct { 
    Metric MetricIdentifier 
    Target MetricTarget 
}
type ResourceMetricSource struct {
    Name v1.ResourceName 
    Target MetricTarget 
}
type ExternalMetricSource struct {
    Metric MetricIdentifier
    Target MetricTarget
}
type MetricTarget struct {
    Type MetricTargetType // 类型:Utilization、Value、AverageValue
    Value *resource.Quantity
    AverageValue *resource.Quantity 
    AverageUtilization *int32
}

控制器 HorizontalController

HorizontalController被通过 key horizontalpodautoscaling 退出到 controller manager 中。用来管制 HorizontalPodAutoscaler 实例。

///cmd/kube-controller-manager/app/controllermanager.go
func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc {
    ...
    controllers["horizontalpodautoscaling"] = startHPAController
    ...
}

获取负载指标

既然 Pod 正本数量的计算是基于 Pod 的负载状况,那边须要路径获取负载数据,这个路径就是MetricsClient

MetricsClient有两种实现:REST 形式和传统(Legacy)形式,别离是 restMetricsClientHeapsterMetricsClient。一个是 REST 实现以反对自定义的指标;一个是传统的 Heapster 指标(heapster 曾经从 1.13 版本开始被废除了)。

//cmd/kube-controller-manager/app/autoscaling.go
func startHPAController(ctx ControllerContext) (http.Handler, bool, error) {if !ctx.AvailableResources[schema.GroupVersionResource{Group: "autoscaling", Version: "v1", Resource: "horizontalpodautoscalers"}] {return nil, false, nil}

    if ctx.ComponentConfig.HPAController.HorizontalPodAutoscalerUseRESTClients {
        // use the new-style clients if support for custom metrics is enabled
        return startHPAControllerWithRESTClient(ctx)
    }

    return startHPAControllerWithLegacyClient(ctx)
}

控制器逻辑HorizontalController#Run()

//pkg/controller/podautoscaler/horizontal.go
func (a *HorizontalController) Run(stopCh <-chan struct{}) {defer utilruntime.HandleCrash()
    defer a.queue.ShutDown()

    klog.Infof("Starting HPA controller")
    defer klog.Infof("Shutting down HPA controller")

      // 期待 informer 实现 HorizontalPodAutoscaler 相干事件的同步
    if !cache.WaitForNamedCacheSync("HPA", stopCh, a.hpaListerSynced, a.podListerSynced) {return}

    // start a single worker (we may wish to start more in the future)
    // 执行 worker 逻辑,直到收到退出指令
    go wait.Until(a.worker, time.Second, stopCh)

    <-stopCh
}

worker的外围是从工作队列中获取一个 key(格局为:namespace/name),而后对 key 进行 reconcile(这个词是 Kubernetes 的外围,翻译为“和谐”、“和解”。集体更喜爱“调整”,即 将实例的状态调整为冀望的状态。此处,对于 hpa 的实例的每个事件,都会依照特定的逻辑调整指标实例的 Pod 的正本数量。)。

//pkg/controller/podautoscaler/horizontal.go
func (a *HorizontalController) worker() {for a.processNextWorkItem() { }
    klog.Infof("horizontal pod autoscaler controller worker shutting down")
}

func (a *HorizontalController) processNextWorkItem() bool {key, quit := a.queue.Get()
    if quit {return false}
    defer a.queue.Done(key)

    deleted, err := a.reconcileKey(key.(string))
    if err != nil {utilruntime.HandleError(err)
    }
    
    if !deleted {a.queue.AddRateLimited(key)
    }

    return true
}

对 key 进行 reconcile 的调用栈:HorizontalController#reconcileKey -> HorizontalController#reconcileAutoscaler -> HorizontalController#computeReplicasForMetrics -> ScaleInterface#Update

简略来说就是先从 Informer 中拿到 key 对应的 HorizontalPodAutoscaler 资源实例;而后通过 HorizontalPodAutoscaler 实例中的信息,查看指标资源的 Pod 负载以及以后的正本数,失去冀望的 Pod 正本数;最终通过 Scale API 来调整 Pod 的正本数。最初会将调整的起因、计算的后果等信息写入 HorizontalPodAutoscaler 实例的 condition 中。

计算冀望的正本数

对每个指标进行计算,都会失去倡议的正本数,而后最大的那个就是最终的冀望正本数。

//pkg/controller/podautoscaler/horizontal.go
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 = condition
                invalidMetricError = err
            }
            invalidMetricsCount++
        }
        if err == nil && (replicas == 0 || replicaCountProposal > replicas) {
            timestamp = timestampProposal
            replicas = replicaCountProposal
            metric = metricNameProposal
        }
    }
    ......
}

#computeStatusForObjectMetric(留神这个办法名少了个 “s”)应用 MetricsClient 失去指定指标的值。

这个流程的细节还能够持续深挖,但到此已够咱们了解 HPA​ 的实现形式了。​

文章对立公布在公众号 云原生指北

退出移动版