Pod 水平自动伸缩(Horizontal Pod Autoscaler)是k8s的kube-controller-manager中已经集成的一个controller,主要功能是根据pod当前的资源使用率来对deploy/rs自动扩缩容。详细介绍可以参考社区的官方文档 。
对于Pod 水平自动伸缩而言,最重要的就是其扩缩容的算法,社区原生的算法主要就是根据当前实例个数、当前metric指标和hpa中设置的期望指标来计算的,具体可以看2.2章节。
期望副本数 = ceil[当前副本数 * ( 当前指标 / 期望指标 )]
 
1、启动参数 kube-controller-manager中包含了多个跟Pod 水平自动伸缩相关的启动参数:
参数名 
参数解释 
 
 
horizontal-pod-autoscaler-sync-period 
controller控制循环的检查周期(默认值为15秒) 
 
horizontal-pod-autoscaler-upscale-delay 
上次扩容之后,再次扩容需要等待的时间,默认 
 
horizontal-pod-autoscaler-downscale-stabilization 
上次缩容执行结束后,再次执行缩容的间隔,默认5分钟 
 
horizontal-pod-autoscaler-downscale-delay 
上次扩容之后,再次扩容需要等待的时间, 
 
horizontal-pod-autoscaler-tolerance 
缩放比例的容忍值,默认为0.1,即在0.9~1.1不会触发扩缩容 
 
horizontal-pod-autoscaler-use-rest-clients 
使用rest client获取metric数据,支持custom metric时需要使用 
 
horizontal-pod-autoscaler-cpu-initialization-period 
pod 的初始化时间, 在此时间内的 pod,CPU 资源指标将不会被采纳 
 
horizontal-pod-autoscaler-initial-readiness-delay 
pod 准备时间, 在此时间内的 pod 统统被认为未就绪 
 
2、处理逻辑 2.1、资源使用率来源 控制器将从一系列的聚合 API(metrics.k8s.io、custom.metrics.k8s.io和external.metrics.k8s.io) 中获取指标数据。 metrics.k8s.io API 通常由 metrics-server(需要额外部署)提供。通过 metrics-server获取到的数据如下,其中包含有cpu和memory的使用量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [paas@192-168-199-200 ~]$ curl 127.0.0.1:8080/apis/metrics.k8s.io/v1beta1/namespaces/default/pods/test-delete-bcf859ddb-b7mjd {   "kind": "PodMetrics",   "apiVersion": "metrics.k8s.io/v1beta1",   "metadata": {     "name": "test-delete-bcf859ddb-b7mjd",     "namespace": "default",     "selfLink": "/apis/metrics.k8s.io/v1beta1/namespaces/default/pods/test-delete-bcf859ddb-b7mjd",     "creationTimestamp": "2020-05-22T02:58:34Z"   },   "timestamp": "2020-05-22T02:58:01Z",   "window": "30s",   "containers": [     {       "name": "container-0",       "usage": {         "cpu": "0",         "memory": "1396Ki"       }     }   ] } 
可以看到通过metrics-server获取数据是以pod为单位的,在Pod Autoscaler中,也是以pod为单位来做的自动伸缩。
custom.metrics.k8s.io需要配合prometheus等组件一起实现,暂且不表,后续介绍完了prometheus之后再慢慢讲。
2.2、算法细节(来自社区文档) 从最基本的角度来看,pod 水平自动缩放控制器跟据当前指标和期望指标来计算缩放比例。其中当前指标就是通过2.1节中的接口来获取,期望指标则是hpa资源中设置的值。
1 期望副本数 = ceil [当前副本数 * ( 当前指标 / 期望指标 )] 
例如,当前指标为200m,目标设定值为100m,那么由于200.0 / 100.0 == 2.0, 副本数量将会翻倍。 如果当前指标为50m,副本数量将会减半,因为50.0 / 100.0 == 0.5。 如果计算出的缩放比例接近1.0(跟据--horizontal-pod-autoscaler-tolerance 参数全局配置的容忍值,默认为0.1), 将会放弃本次缩放。
如果 HorizontalPodAutoscaler 指定的是targetAverageValue 或 targetAverageUtilization, 那么将会把指定pod的平均指标做为currentMetricValue。 然而,在检查容忍度和决定最终缩放值前,我们仍然会把那些无法获取指标的pod统计进去。
所有被标记了删除时间戳(Pod正在关闭过程中)的 pod 和 失败的 pod 都会被忽略。
如果某个 pod 缺失指标信息,它将会被搁置,只在最终确定缩值时再考虑。
当使用 CPU 指标来缩放时,任何还未就绪(例如还在初始化)状态的 pod 或  最近的指标为就绪状态前的 pod, 也会被搁置
由于受技术限制,pod 水平缩放控制器无法准确的知道 pod 什么时候就绪, 也就无法决定是否暂时搁置该 pod。 --horizontal-pod-autoscaler-initial-readiness-delay 参数(默认为30s),用于设置 pod 准备时间, 在此时间内的 pod 统统被认为未就绪。 --horizontal-pod-autoscaler-cpu-initialization-period参数(默认为5分钟),用于设置 pod 的初始化时间, 在此时间内的 pod,CPU 资源指标将不会被采纳。
在排除掉被搁置的 pod 后,缩放比例就会跟据currentMetricValue / desiredMetricValue计算出来。
如果有任何 pod 的指标缺失,我们会更保守地重新计算平均值, 在需要缩小时假设这些 pod 消耗了目标值的 100%, 在需要放大时假设这些 pod 消耗了0%目标值。 这可以在一定程度上抑制伸缩的幅度。
此外,如果存在任何尚未就绪的pod,我们可以在不考虑遗漏指标或尚未就绪的pods的情况下进行伸缩, 我们保守地假设尚未就绪的pods消耗了试题指标的0%,从而进一步降低了伸缩的幅度。
在缩放方向(缩小或放大)确定后,我们会把未就绪的 pod 和缺少指标的 pod 考虑进来再次计算使用率。 如果新的比率与缩放方向相反,或者在容忍范围内,则跳过缩放。 否则,我们使用新的缩放比例。
注意,平均利用率的原始 值会通过 HorizontalPodAutoscaler 的状态体现( 即使使用了新的使用率,也不考虑未就绪 pod 和 缺少指标的 pod)。
如果创建 HorizontalPodAutoscaler 时指定了多个指标, 那么会按照每个指标分别计算缩放副本数,取最大的进行缩放。 如果任何一个指标无法顺利的计算出缩放副本数(比如,通过 API 获取指标时出错), 那么本次缩放会被跳过。
最后,在 HPA 控制器执行缩放操作之前,会记录缩放建议信息(scale recommendation)。 控制器会在操作时间窗口中考虑所有的建议信息,并从中选择得分最高的建议。 这个值可通过 kube-controller-manager 服务的启动参数 --horizontal-pod-autoscaler-downscale-stabilization 进行配置, 默认值为 5min。 这个配置可以让系统更为平滑地进行缩容操作,从而消除短时间内指标值快速波动产生的影响。
3、代码解析 3.1、重要的数据结构 在进入代码之前,我们需要先说明一下与Pod Autoscaler相关的几个重要的k8s资源,这些资源是弹性伸缩实现的基石。
3.1.1、HorizontalPodAutoscaler(v2beta1) 在最早期的v1版本的HorizontalPodAutoscaler中,只能指定targetCPUUtilizationPercentage,也就是说只能通过pod的cpu使用率来做弹性伸缩,功能比较局限。后面的v2beta1将这些资源做了拓展,已经不局限于cpu了。我们先举一个v2beta1的样例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 kind:  HorizontalPodAutoscaler apiVersion:  autoscaling/v2beta2 metadata:   name:  fff    namespace:  default    selfLink:  "/apis/autoscaling/v2beta2/namespaces/default/horizontalpodautoscalers/fff"    uid:  698d7d88-82f7-4b2d-ad1a-467e07a45edb    resourceVersion:  '44279715'    creationTimestamp:  '2020-05-14T06:51:45Z'  spec:   scaleTargetRef:        kind:  Deployment      name:  fffff      apiVersion:  apps/v1beta2    minReplicas:  1       maxReplicas:  10      metrics:       -  type:  Resource         resource:        name:  memory        target:          type:  Utilization             averageUtilization:  70    -  type:  Resource      resource:        name:  cpu        target:          type:  Utilization          averageUtilization:  60  status:   lastScaleTime:  '2020-05-14T06:57:01Z'      currentReplicas:  1    desiredReplicas:  1    currentMetrics:     -  type:  Resource      resource:        name:  memory        current:          averageValue:  '1433600'          averageUtilization:  1    -  type:  Resource      resource:        name:  cpu        current:          averageValue:  '0'          averageUtilization:  0    conditions:    -  type:  AbleToScale        status:  'True'      lastTransitionTime:  '2020-05-14T06:52:00Z'      reason:  ReadyForNewScale      message:  recommended  size  matches  current  size    -  type:  ScalingActive        status:  'True'      lastTransitionTime:  '2020-05-25T11:27:20Z'      reason:  ValidMetricFound      message:  the  HPA  was  able  to  successfully  calculate  a  replica  count  from  memory        resource  utilization  (percentage  of  request)    -  type:  ScalingLimited        status:  'False'      lastTransitionTime:  '2020-05-14T06:52:00Z'      reason:  DesiredWithinRange      message:  the  desired  count  is  within  the  acceptable  range  
在HorizontalPodAutoscaler中,很关键的一个字段就是spec.metrics,用来指定自动伸缩所根据的指标,分别有4种类型:
Resource:资源使用率,可以是pod的cpu、memory,对应的指标可以是使用率、使用量、平均使用量,前面的样例中使用的就是这种类型
Pods:表示指定的指标在不同Pod之间进行平均,并通过与一个目标值比对来确定副本的数量。 它们的工作方式与资源度量指标非常相像,差别是它们仅支持target 类型为AverageValue。样例如下:
1 2 3 4 5 6 7 type:  Pods pods:   metric:      name:  packets-per-second    target:      type:  AverageValue      averageValue:  1k  
Object:k8s中某一个可以描述资源指标的对象,例如有hits-per-second指标的Ingress对象,表示requests-per-second的度量指标样例如下。需要注意的是,这个资源对象需要与其作用的pod在相同namespace中。
1 2 3 4 5 6 7 8 9 10 11 type:  Object object:   metric:      name:  requests-per-second    describedObject:      apiVersion:  networking.k8s.io/v1beta1      kind:  Ingress      name:  main-route    target:      type:  Value      value:  2k  
External:使用外部的度量指标,这些指标依赖于外部的监控系统,这种方式不太推荐,更建议使用custom metric的方式。
 
由于spec.metrics是一个slice,因此可以同时指定多个指标类型,HorizontalPodAutoscaler 会计算每一个指标所提议的副本数量,然后最终选择一个最高值。
3.2、代码目录 跟k8s中其他官方的controller一样,Pod Autoscaler控制器的代码主要都位于k8s中,主要代码逻辑在horizontal.go中,其他文件中的代码见下面的说明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #  pkg/controller/podautoscaler/ ├── config   # HPAController的启动参数相关 │   ├── BUILD │   ├── doc.go │   ├── OWNERS │   ├── types.go │   ├── v1alpha1 │   │   ├── BUILD │   │   ├── conversion.go │   │   ├── defaults.go │   │   ├── doc.go │   │   ├── register.go │   │   ├── zz_generated.conversion.go │   │   └── zz_generated.deepcopy.go │   └── zz_generated.deepcopy.go ├── doc.go ├── horizontal.go   # 主代码逻辑 ├── horizontal_test.go ├── legacy_horizontal_test.go ├── legacy_replica_calculator_test.go ├── metrics │   ├── BUILD │   ├── interfaces.go   # 获取metric数值的接口 │   ├── legacy_metrics_client.go   # 调用heapster获取metric的实现 │   ├── legacy_metrics_client_test.go │   ├── rest_metrics_client.go   # 调用metric-server获取metric的实现 │   ├── rest_metrics_client_test.go │   ├── utilization.go   # 计算当前使用率的公共函数 │   └── utilization_test.go ├── OWNERS ├── rate_limiters.go   # HPAController限流器 ├── replica_calculator.go   # 根据当前使用率计算pod期望个数的函数实现 └── replica_calculator_test.go 
3.3、代码逻辑 为了方便理解HorizontalController本身的逻辑,这里不去细究kube-controller-manager本身的框架,也不去细究k8s中list-watch机制中的informer相关的具体细节,遇到时也仅解析其功能和用法。
3.3.1、创建HorizontalController 创建HorizontalController时一个十分关键的参数是resyncPeriod,即启动参数horizontal-pod-autoscaler-sync-period,controller中并不是通过Ticker的方式来定时去做检查,而是在informer中定时把所有资源发送到controller的处理队列中,这种方式的好处是不需要给每个hpa资源都创建一个Ticker。这个功能主要是informer自己去实现的,对这部分感兴趣的话可以去瞅瞅staging/src/k8s.io/client-go/tools/cache里面的代码,非常值得研究。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func  NewHorizontalController ( 	evtNamespacer v1core.EventsGetter, 	scaleNamespacer scaleclient.ScalesGetter, 	hpaNamespacer autoscalingclient.HorizontalPodAutoscalersGetter, 	mapper apimeta.RESTMapper, 	metricsClient metricsclient.MetricsClient, 	hpaInformer autoscalinginformers.HorizontalPodAutoscalerInformer, 	podInformer coreinformers.PodInformer, 	resyncPeriod time.Duration, 	downscaleStabilisationWindow time.Duration, 	tolerance float64 , 	cpuInitializationPeriod, 	delayOfInitialReadinessStatus time.Duration, )  *HorizontalController      	hpaInformer.Informer().AddEventHandlerWithResyncPeriod( 		cache.ResourceEventHandlerFuncs{ 			AddFunc:    hpaController.enqueueHPA, 			UpdateFunc: hpaController.updateHPA, 			DeleteFunc: hpaController.deleteHPA, 		}, 		resyncPeriod,  	)      	return  hpaController } 
3.3.2、运行controller 相比于创建controller时的各种涉及到informer参数的设置,运行controller的逻辑明显清晰很多,入口就是其Run函数,其中会起一个携程来运行实际的处理逻辑。在这个协程中,会不断从informer的队列中取出需要处理的HPA对象的key,然后使用这个key去缓存中获取对应的HPA对象的完整对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 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" ) 	if  !controller.WaitForCacheSync("HPA" , stopCh, a.hpaListerSynced, a.podListerSynced) { 		return  	}      	go  wait.Until(a.worker, time.Second, stopCh) 	<-stopCh } 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  } func  (a *HorizontalController)  reconcileKey (key string )  (deleted bool , err error) 	namespace, name, err := cache.SplitMetaNamespaceKey(key) 	if  err != nil  { 		return  true , err 	}      	hpa, err := a.hpaLister.HorizontalPodAutoscalers(namespace).Get(name) 	if  errors.IsNotFound(err) { 		klog.Infof("Horizontal Pod Autoscaler %s has been deleted in %s" , name, namespace) 		delete (a.recommendations, key) 		return  true , nil  	}      	return  false , a.reconcileAutoscaler(hpa, key) } 
在处理HPA对象时,主要分为3步:
获取目标deploy或者rs的scale数据,包含deploy或者rs中spec.replicas指定的实例个数 
根据当前的情况计算是否需要扩缩容以及期望的实例个数 
需要scale的情况则调用接口对改资源进行弹性伸缩,否则保持不变 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 func  (a *HorizontalController)  reconcileAutoscaler (hpav1Shared *autoscalingv1.HorizontalPodAutoscaler, key string )  error 	 	hpav1 := hpav1Shared.DeepCopy() 	 	hpaRaw, err := unsafeConvertToVersionVia(hpav1, autoscalingv2.SchemeGroupVersion) 	if  err != nil  { 		a.eventRecorder.Event(hpav1, v1.EventTypeWarning, "FailedConvertHPA" , err.Error()) 		return  fmt.Errorf("failed to convert the given HPA to %s: %v" , autoscalingv2.SchemeGroupVersion.String(), err) 	}      	hpa := hpaRaw.(*autoscalingv2.HorizontalPodAutoscaler) 	hpaStatusOriginal := hpa.Status.DeepCopy() 	reference := fmt.Sprintf("%s/%s/%s" , hpa.Spec.ScaleTargetRef.Kind, hpa.Namespace, hpa.Spec.ScaleTargetRef.Name) 	targetGV, err := schema.ParseGroupVersion(hpa.Spec.ScaleTargetRef.APIVersion)      	targetGK := schema.GroupKind{ 		Group: targetGV.Group, 		Kind:  hpa.Spec.ScaleTargetRef.Kind, 	} 	mappings, err := a.mapper.RESTMappings(targetGK) 	      	scale, targetGR, err := a.scaleForResourceMappings(hpa.Namespace, hpa.Spec.ScaleTargetRef.Name, mappings) 	 	currentReplicas := scale.Spec.Replicas 	a.recordInitialRecommendation(currentReplicas, key) 	var  ( 		metricStatuses        []autoscalingv2.MetricStatus 		metricDesiredReplicas int32  		metricName            string  	) 	desiredReplicas := int32 (0 )   	rescaleReason := ""  	rescale := true         	if  scale.Spec.Replicas == 0  { 		 		desiredReplicas = 0  		rescale = false  		setCondition(hpa, autoscalingv2.ScalingActive, v1.ConditionFalse, "ScalingDisabled" , "scaling is disabled since the replica count of the target is zero" ) 	} 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 	} else  if  currentReplicas == 0  {          		rescaleReason = "Current number of replicas must be greater than 0"  		desiredReplicas = 1  	} else  {          		var  metricTimestamp time.Time          		metricDesiredReplicas, metricName, metricStatuses, metricTimestamp, err = a.computeReplicasForMetrics(hpa, scale, hpa.Spec.Metrics) 		 		rescaleMetric := ""  		if  metricDesiredReplicas > desiredReplicas { 			desiredReplicas = metricDesiredReplicas 			rescaleMetric = metricName 		} 		 		desiredReplicas = a.normalizeDesiredReplicas(hpa, key, currentReplicas, desiredReplicas) 		rescale = desiredReplicas != currentReplicas 	}      	if  rescale { 		scale.Spec.Replicas = desiredReplicas 		_, err = a.scaleNamespacer.Scales(hpa.Namespace).Update(targetGR, scale) 		 	} else  { 		klog.V(4 ).Infof("decided not to scale %s to %v (last scale time was %s)" , reference, desiredReplicas, hpa.Status.LastScaleTime) 		desiredReplicas = currentReplicas 	} 	a.setStatus(hpa, currentReplicas, desiredReplicas, metricStatuses, rescale) 	return  a.updateStatusIfNeeded(hpaStatusOriginal, hpa) } 
前面的逻辑中,最重要的一步就是根据当前的资源使用情况来计算期望的实例个数,也就是computeReplicasForMetrics函数。在这个函数中,主要就是遍历所有的metric策略(3.1.1节中有提到不同类型),并计算各个策略下的期望实例个数,最终取最大的那个。这段逻辑最终进入到计算单个策略的函数computeReplicasForMetric中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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) 	specReplicas := scale.Spec.Replicas 	statusReplicas := scale.Status.Replicas 	statuses = make ([]autoscalingv2.MetricStatus, len (metricSpecs))      	selector, err := labels.Parse(scale.Status.Selector)      	invalidMetricsCount := 0  	var  invalidMetricError error      	for  i, metricSpec := range  metricSpecs { 		replicaCountProposal, metricNameProposal, timestampProposal, err := a.computeReplicasForMetric(hpa, metricSpec, specReplicas, statusReplicas, selector, &statuses[i])                   		if  err == nil  && (replicas == 0  || replicaCountProposal > replicas) { 			timestamp = timestampProposal 			replicas = replicaCountProposal 			metric = metricNameProposal 		} 	} 	 	return  replicas, metric, statuses, timestamp, nil  } 
对于每一个策略而言,需要根据其类型做不同的处理,computeReplicasForMetric中主要就是一个case语句,由于有4中类型,其中Resource较为常用,因此后面就只对这种类型进行代码分析。Resource类型的计算是由computeStatusForResourceMetric来处理的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func  (a *HorizontalController)  computeReplicasForMetric (hpa *autoscalingv2.HorizontalPodAutoscaler, spec autoscalingv2.MetricSpec, 	specReplicas, statusReplicas int32 , selector labels.Selector, status *autoscalingv2.MetricStatus)  (replicaCountProposal int32 , metricNameProposal string , 	timestampProposal time.Time, err error)      	switch  spec.Type { 	case  autoscalingv2.ObjectMetricSourceType: 		metricSelector, err := metav1.LabelSelectorAsSelector(spec.Object.Metric.Selector) 		 		replicaCountProposal, timestampProposal, metricNameProposal, err = a.computeStatusForObjectMetric(specReplicas, statusReplicas, spec, hpa, selector, status, metricSelector) 		 	case  autoscalingv2.PodsMetricSourceType: 		metricSelector, err := metav1.LabelSelectorAsSelector(spec.Pods.Metric.Selector) 		 		replicaCountProposal, timestampProposal, metricNameProposal, err = a.computeStatusForPodsMetric(specReplicas, spec, hpa, selector, status, metricSelector) 		 	case  autoscalingv2.ResourceMetricSourceType: 		replicaCountProposal, timestampProposal, metricNameProposal, err = a.computeStatusForResourceMetric(specReplicas, spec, hpa, selector, status) 		 	case  autoscalingv2.ExternalMetricSourceType: 		replicaCountProposal, timestampProposal, metricNameProposal, err = a.computeStatusForExternalMetric(specReplicas, statusReplicas, spec, hpa, selector, status) 		 	default : 		errMsg := fmt.Sprintf("unknown metric source type %q" , string (spec.Type)) 		 	} 	return  replicaCountProposal, metricNameProposal, timestampProposal, nil  } 
在computeStatusForResourceMetric中,主要是针对Resource类型的策略进行处理,在3.1.1章节中已经说明Resource类型的策略对应的指标可以是:
平均使用率AverageUtilization 
使用量Value 
平均使用量AverageValue 
 
但是在computeStatusForResourceMetric函数中,似乎并不支持使用量Value这种类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 func  (a *HorizontalController)  computeStatusForResourceMetric (currentReplicas int32 , metricSpec autoscalingv2.MetricSpec, hpa *autoscalingv2.HorizontalPodAutoscaler, selector labels.Selector, status *autoscalingv2.MetricStatus)  (int32 , time.Time, string , error) 	if  metricSpec.Resource.Target.AverageValue != nil  {          		var  rawProposal int64  		replicaCountProposal, rawProposal, timestampProposal, err := a.replicaCalc.GetRawResourceReplicas(currentReplicas, metricSpec.Resource.Target.AverageValue.MilliValue(), metricSpec.Resource.Name, hpa.Namespace, selector) 		 		metricNameProposal := fmt.Sprintf("%s resource" , metricSpec.Resource.Name) 		*status = autoscalingv2.MetricStatus{ 			Type: autoscalingv2.ResourceMetricSourceType, 			Resource: &autoscalingv2.ResourceMetricStatus{ 				Name: metricSpec.Resource.Name, 				Current: autoscalingv2.MetricValueStatus{ 					AverageValue: resource.NewMilliQuantity(rawProposal, resource.DecimalSI), 				}, 			}, 		} 		return  replicaCountProposal, timestampProposal, metricNameProposal, nil  	} else  {          		if  metricSpec.Resource.Target.AverageUtilization == nil  {              			errMsg := "invalid resource metric source: neither a utilization target nor a value target was set"  			a.eventRecorder.Event(hpa, v1.EventTypeWarning, "FailedGetResourceMetric" , errMsg) 			setCondition(hpa, autoscalingv2.ScalingActive, v1.ConditionFalse, "FailedGetResourceMetric" , "the HPA was unable to compute the replica count: %s" , errMsg) 			return  0 , time.Time{}, "" , fmt.Errorf(errMsg) 		}          		targetUtilization := *metricSpec.Resource.Target.AverageUtilization 		var  percentageProposal int32  		var  rawProposal int64           		replicaCountProposal, percentageProposal, rawProposal, timestampProposal, err := a.replicaCalc.GetResourceReplicas(currentReplicas, targetUtilization, metricSpec.Resource.Name, hpa.Namespace, selector) 		 		metricNameProposal := fmt.Sprintf("%s resource utilization (percentage of request)" , metricSpec.Resource.Name) 		*status = autoscalingv2.MetricStatus{ 			Type: autoscalingv2.ResourceMetricSourceType, 			Resource: &autoscalingv2.ResourceMetricStatus{ 				Name: metricSpec.Resource.Name, 				Current: autoscalingv2.MetricValueStatus{ 					AverageUtilization: &percentageProposal, 					AverageValue:       resource.NewMilliQuantity(rawProposal, resource.DecimalSI), 				}, 			}, 		} 		return  replicaCountProposal, timestampProposal, metricNameProposal, nil  	} } 
由于篇幅有限,本文只说明一下平均使用率AverageUtilization这种情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 func  (c *ReplicaCalculator)  GetResourceReplicas (currentReplicas int32 , targetUtilization int32 , resource v1.ResourceName, namespace string , selector labels.Selector)  (replicaCount int32 , utilization int32 , rawUtilization int64 , timestamp time.Time, err error)      	metrics, timestamp, err := c.metricsClient.GetResourceMetric(resource, namespace, selector) 	           	podList, err := c.podLister.Pods(namespace).List(selector) 	 	itemsLen := len (podList) 	      	readyPodCount, ignoredPods, missingPods := groupPods(podList, metrics, resource, c.cpuInitializationPeriod, c.delayOfInitialReadinessStatus)      	removeMetricsForPods(metrics, ignoredPods)      	requests, err := calculatePodRequests(podList, resource) 	      	usageRatio, utilization, rawUtilization, err := metricsclient.GetResourceUtilizationRatio(metrics, requests, targetUtilization) 	           	rebalanceIgnored := len (ignoredPods) > 0  && usageRatio > 1.0  	if  !rebalanceIgnored && len (missingPods) == 0  { 		if  math.Abs(1.0 -usageRatio) <= c.tolerance { 			             return  currentReplicas, utilization, rawUtilization, timestamp, nil  		} 		 		return  int32 (math.Ceil(usageRatio * float64 (readyPodCount))), utilization, rawUtilization, timestamp, nil  	} 	if  len (missingPods) > 0  { 		if  usageRatio < 1.0  { 			 			for  podName := range  missingPods { 				metrics[podName] = metricsclient.PodMetric{Value: requests[podName]} 			} 		} else  if  usageRatio > 1.0  { 			 			for  podName := range  missingPods { 				metrics[podName] = metricsclient.PodMetric{Value: 0 } 			} 		} 	} 	if  rebalanceIgnored { 		 		for  podName := range  ignoredPods { 			metrics[podName] = metricsclient.PodMetric{Value: 0 } 		} 	} 	 	newUsageRatio, _, _, err := metricsclient.GetResourceUtilizationRatio(metrics, requests, targetUtilization) 	 	if  math.Abs(1.0 -newUsageRatio) <= c.tolerance || (usageRatio < 1.0  && newUsageRatio > 1.0 ) || (usageRatio > 1.0  && newUsageRatio < 1.0 ) { 		 		return  currentReplicas, utilization, rawUtilization, timestamp, nil  	} 	 	return  int32 (math.Ceil(newUsageRatio * float64 (len (metrics)))), utilization, rawUtilization, timestamp, nil  } 
4、参考文档 https://kubernetes.io/zh/docs/tasks/run-application/horizontal-pod-autoscale/ 
https://kubernetes.io/zh/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/ 
https://docs.aws.amazon.com/zh_cn/eks/latest/userguide/horizontal-pod-autoscaler.html