一. 背景
在 kubernetes 中,hpa(HorizontalPodAutoscaler)能够依据工作负载的指标(cpu/mem 等),主动对其进行伸缩,即主动减少、缩小其 pod 数量。
cronhpa 是一个定时扩缩容的组件,它反对依照 Crontab 表达式的策略,定时地对 workload 进行扩缩容,这里应用 aliyun 的 kubernetes-cronhpa-controller。
cronhpa 的例子如下:
apiVersion: autoscaling.alibabacloud.com/v1beta1
kind: CronHorizontalPodAutoscaler
metadata:
name: cronhpa-sample
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: nginx // 对 deploy: nginx 进行定时扩缩容;jobs:
- name: "scale-down" // 在每分钟的 30s,将 deploy 缩容到 1 个正本;schedule: "30 */1 * * * *"
targetSize: 1
- name: "scale-up" // 在每分钟的 0s,将 deploy 扩容到 3 个正本;schedule: "0 */1 * * * *"
targetSize: 3
问题
如果 CronHPA 和 HPA,同时操作一个 scaleTargetRef,就会呈现 replicas 被 CronHPA 和 HPA 轮番批改的状况,比方:
- HPA 依据指标,要将正本扩容为 5 个;
- CronHPA 依据 crontab 规定,将正本扩容为 3 个;
- 两个 controller 独立工作,两个扩容操作会互相笼罩;
二. 计划
kubernetes-cronhpa-controller 对该问题的计划:
- 将 CronHPA 的 scaleTargetRef 设置为 HPA;
- 再设置 HPA 的 target 为 deployment,从而让 CronHPA 感知 HPA 的以后状态;
最终:
- CronHPA 能够感知到 HPA 的 minReplicas/maxReplicas/desiredReplicas;
- CronHPA 能够通过 HPA 的 target,晓得 deploy 的 currentReplicas;
CronHPA 通过调整 hpa.minReplicas/hpa.maxReplicas/deploy.replicas 的形式,与 HPA 一起实现扩缩容操作。
策略调整
在 kubernetes-cronhpa-controller 计划的根底上,能够依据本人的需要进行调整,比方:
- 规定 cronhpa 不批改 hpa 的参数;
- 规定 cronhpa 在伸缩时,伸缩范畴为 [hpa.minReplica, hpa.maxReplica],不能超过范畴;
三. 源码
看一下 kubernetes-cronhpa-controller 对 cronhpa 和 hpa 共存伸缩的源码。
1. 源码入口
在执行定时工作的时候,对 cronhpa.TargetRef.Kind=”HPA” 独自解决:
// pkg/controller/cronjob.go
func (ch *CronJobHPA) Run() (msg string, err error) {startTime := time.Now()
times := 0
for {now := time.Now()
...
// hpa compatible
if ch.TargetRef.RefKind == "HorizontalPodAutoscaler" { // target=HPA
msg, err = ch.ScaleHPA()
if err == nil {break}
} else {msg, err = ch.ScalePlainRef() // target=Deploy/Sts
if err == nil {break}
}
...
}
return msg, err
}
CronHPA 对 HPA 伸缩时,进行两个操作:
- 批改 HPA 的 min/maxReplicas;
- 对 HPA.target(deploy/statefulset) 进行 Scale;
CronHPA 对 HPA 伸缩时,根据的参数:
- min: hpa.minReplicas
- max: hpa.maxReplicas
- deploy: hpa.Status.CurrentReplicas
- target: cronhpa.job.TargetSize
2. 伸缩逻辑
ScaleHPA() 做的事件:
- 根据上述不同的场景,更新 hpa.minReplicas 和 maxReplicas;
-
伸缩 hpa.targetRef:
- 若 hpa.targetRef 正本数 >= cronhpa.target,则不须要伸缩,间接返回;
- 否则,将 hpa.targetRef 正本数,伸缩至 cronhpa.target;
// pkg/controller/cronjob.go
func (ch *CronJobHPA) ScaleHPA() (msg string, err error) {
...
// 查问 HPA 对象
hpa := &autoscalingapi.HorizontalPodAutoscaler{}
err = ch.client.Get(ctx, types.NamespacedName{Namespace: ch.HPARef.Namespace, Name: ch.TargetRef.RefName}, hpa)
...
mappings, err := ch.mapper.RESTMappings(targetGK)
// 查问 HPA 的 target,即 scale 对象
for _, mapping := range mappings {targetGR = mapping.Resource.GroupResource()
scale, err = ch.scaler.Scales(ch.TargetRef.RefNamespace).Get(context.Background(), targetGR, targetRef.Name, v1.GetOptions{})
if err == nil {
found = true
break
}
}
// 依据不同场景,批改 hpa.minReplicas/maxReplicas
updateHPA := false
if ch.DesiredSize > hpa.Spec.MaxReplicas {
hpa.Spec.MaxReplicas = ch.DesiredSize
updateHPA = true
}
if ch.DesiredSize < *hpa.Spec.MinReplicas {
*hpa.Spec.MinReplicas = ch.DesiredSize
updateHPA = true
}
if hpa.Status.CurrentReplicas == *hpa.Spec.MinReplicas && ch.DesiredSize < hpa.Status.CurrentReplicas {
*hpa.Spec.MinReplicas = ch.DesiredSize
updateHPA = true
}
if hpa.Status.CurrentReplicas < ch.DesiredSize {
*hpa.Spec.MinReplicas = ch.DesiredSize
updateHPA = true
}
// 将 hpa 的批改长久化
if updateHPA {err = ch.client.Update(ctx, hpa)
if err != nil {return "", err}
}
// 若指标对象不须要伸缩,则间接返回
if hpa.Status.CurrentReplicas >= ch.DesiredSize {
// skip change replicas and exit
return fmt.Sprintf("Skip scale replicas because HPA %s current replicas:%d >= desired replicas:%d.", hpa.Name, scale.Spec.Replicas, ch.DesiredSize), nil
}
// 否则,伸缩指标对象到 target 正本数
scale.Spec.Replicas = int32(ch.DesiredSize)
_, err = ch.scaler.Scales(ch.TargetRef.RefNamespace).Update(context.Background(), targetGR, scale, metav1.UpdateOptions{})
return msg, nil
}
参考
1.https://help.aliyun.com/document_detail/151557.html
2.https://github.com/AliyunContainerService/kubernetes-cronhpa-controller