对于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的状态