如何自己部署autoscaler实现节点的自动扩缩容(二):支持自定义调度器触发的扩容

原创 吴就业 126 0 2024-05-08

本文为博主原创文章,未经博主允许不得转载。

本文链接:https://wujiuye.com/article/9c91ebce8c5742129c75feb711cdb5df

作者:吴就业
链接:https://wujiuye.com/article/9c91ebce8c5742129c75feb711cdb5df
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。

默认调度器无法调度Pod的条件是没有节点满足Pod请求的最低cpu和内存了,只有当部署在节点上的所有Pod的requests加起来超过节点的cap,默认调度器才会无法调度Pod,否则,即便实际的cpu和内存负载接近cap,也不会阻止Pod调度到节点上。

我们自定义的调度器加了一个Filter插件,当节点cpu或内存使用率超过一个阈值的时候,例如cpu是45%,内存是75%,超过阈值后希望能触发扩容节点,而不是继续将Pod调度到这些超负载阈值的节点上。

在前面的两篇文章介绍了autoscaler会在什么情况下去扩容节点,可以看下这两篇文章,这里就不重复说明了:

autoscaler配合自定义调度器使用,自定义的调度器无法触发扩容节点,原因是autoscaler在扩容之前会有一个check逻辑,会调用一遍调度器的过滤插件,如果调度器的过滤插件选不出node才会扩容。

因此想要让autoscaler支持自定义调度器触发节点扩展,有两种方案。

对于第一种方案,可以通过*scheduler-config-file*启动参数指定KubeSchedulerConfiguration配置文件。

--scheduler-config-file=/Users/wujiuye/cloud_native/autoscaler/cluster-autoscaler/cloudprovider/gce/testdata/scheduler-config-file.yaml

这个文件的内容可以直接copy我们部署的自定义调度器的配置。

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
leaderElection:
  leaderElect: false
profiles:
- schedulerName: scheduler-plugins-scheduler
  plugins:
    multiPoint:
      enabled:
      - name: TargetLoadPacking
        weight: 10000
      - name: LoadVariationRiskBalancing
        weight: 10000
      - name: AdapterAutoscaler
        weight: 10000
      disabled:
      - name: NodeResourcesLeastAllocated
      - name: NodeResourcesBalancedAllocation
  pluginConfig: 
  - args:
      defaultRequests:
        cpu: 1000m
      defaultRequestsMultiplier: "1"
      metricProvider:
        type: KubernetesMetricsServer
      targetUtilization: 45
    name: TargetLoadPacking
  - args:
      metricProvider:
        type: KubernetesMetricsServer
      safeVarianceMargin: 1
    name: LoadVariationRiskBalancing
  - args:
      autoscalerThreshold:
        cpu: 45
        memory: 75
    name: AdapterAutoscaler

但启动会报错:

F0507 16:24:52.767626   69658 main.go:580] Failed to create autoscaler: couldn't create scheduler framework; PreFilterPlugin "TargetLoadPacking" does not exist
Exiting.

因为autoscaler这个项目是没有我们自定义的调度器相关插件的代码的,所以check逻辑是无法初始化的。

有一个解决方案,就是将自定义插件相关代码打包成单独的sdk,在autoscaler项目依赖,然后给每个插件实现一个PluginFactory,在NewFramework的时候,注册自定义插件的PluginFactory。这其实也是autoscaler本身推荐的,因为它提供PluginFactory就是这个目的。

inTreeRegistry := scheduler_plugins.NewInTreeRegistry();
inTreeRegistry.Register("", ...) // 注册自定义插件
framework, err := schedulerframeworkruntime.NewFramework(
    context.TODO(),
    inTreeRegistry,
    &schedConfig.Profiles[0],
    schedulerframeworkruntime.WithInformerFactory(informerFactory),
    schedulerframeworkruntime.WithSnapshotSharedLister(sharedLister),
)

只是这个要求插件独立于调度器项目能独立运行,不依赖调度器的其它逻辑。但因为我们的Filter依赖一些指标计算,而这些指标计算的逻辑与调度器项目完全分不开,所以这个方案对我们来说行不通。

第二种方案就是取消check逻辑,但是牵扯的代码逻辑太多了,改不动。

不过我们想到一种方式绕过去,达到取消check逻辑的效果:就是给KubeSchedulerConfiguration配置一个过滤器插件,这个插件永远返回framework.Unschedulable,具体做法如下。

一、修改配置文件:

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
leaderElection:
  leaderElect: false
profiles:
  - schedulerName: scheduler-plugins-scheduler
    plugins:
      multiPoint:
        enabled:
          - name: NoChecker

二、实现插件:

package wujiuye

import (
    "context"
    v1 "k8s.io/api/core/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/kubernetes/pkg/scheduler/framework"
    "strings"
)

var _ framework.FilterPlugin = &NoCheckerPlugin{}

const (
    Name = "NoChecker"
)

func New(obj runtime.Object, handle framework.Handle) (framework.Plugin, error) {
    return &NoCheckerPlugin{}, nil
}

type NoCheckerPlugin struct {
}

func (c *NoCheckerPlugin) Name() string {
    return Name
}

func (c *NoCheckerPlugin) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
    return framework.NewStatus(framework.Unschedulable, "No checker.")
}

三、注册插件:

inTreeRegistry := scheduler_plugins.NewInTreeRegistry()
if err := inTreeRegistry.Register(wujiuye.Name, func(ctx context.Context, configuration runtime.Object, f schedulerframework.Handle) (schedulerframework.Plugin, error) {
    return wujiuye.New(configuration, f)
}); err != nil {
    return nil, fmt.Errorf("couldn't register no-checker plugin")
}
framework, err := schedulerframeworkruntime.NewFramework(
    context.TODO(),
    inTreeRegistry,
    &schedConfig.Profiles[0],
    schedulerframeworkruntime.WithInformerFactory(informerFactory),
    schedulerframeworkruntime.WithSnapshotSharedLister(sharedLister),
)

但是实验结果还是没有扩容,通过debug代码逻辑发现,除了在判断是否需要扩容之前会执行check逻辑外,在扩容之前还有一次模拟扩容的操作,就是假设按照Pod亲和性和污点容忍度匹配的节点池,Mock一个Node之后,再调用一次check逻辑,只有过滤器插件全部返回成功,就认为扩容后能满足Pod的调度,然后才会真正的去扩容节点。

为此,我们还需要修改插件,根据Node的名称判断是否是Mock的节点,如果是,过滤器就返回Success。

package wujiuye

import (
    "context"
    v1 "k8s.io/api/core/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/kubernetes/pkg/scheduler/framework"
    "strings"
)

var _ framework.FilterPlugin = &NoCheckerPlugin{}

const (
    Name = "NoChecker"
)

func New(obj runtime.Object, handle framework.Handle) (framework.Plugin, error) {
    return &NoCheckerPlugin{}, nil
}

type NoCheckerPlugin struct {
}

func (c *NoCheckerPlugin) Name() string {
    return Name
}

func (c *NoCheckerPlugin) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
    if strings.HasPrefix(nodeInfo.Node().Name, "template") {
       return framework.NewStatus(framework.Success, "")
    }
    return framework.NewStatus(framework.Unschedulable, "No checker.")
}

扩容成功:

I0507 20:24:31.549015   36401 executor.go:147] Scale-up: setting group https://www.googleapis.com/compute/v1/projects/wujiuye-410808/zones/us-central1-c/instanceGroups/gke-autoscaler-cluster-test-group-9cb4eb39-grp size to 2
#云原生

声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

文章推荐

autoscaler缩减低利用率的节点,修改为使用节点的实时指标而不是requests

但其实我们忽略了一点,autoscaler是根据部署在节点上的所有Pod声明的requests来计算资源使用率的,这个并不能反应真实情况。

如何自己部署autoscaler实现节点的自动扩缩容(四):低负载自动缩容与重调度

低负载自动缩容节点的这个特性还是非常有必要的,能够避免资源的过渡浪费。因为随着时间的推移,集群中势必会出现非常多的弹性节点利用率很低的情况。

如何自己部署autoscaler实现节点的自动扩缩容(三):验证修改缩容节点时长

autoscaler,想要缩短空闲节点被回收的时间,需要同时考虑三个启动参数的配置:scale-down-unneeded-time、unremovable-node-recheck-timeout、scale-down-delay-after-add。

如何自己部署autoscaler实现节点的自动扩缩容(一):本地debug运行autoscaler

在google cloud上使用gke集群,gke已经集成autoscaler,通过在控制台创建节点池点击开启自动扩缩容就可以,那么为什么还要自己部署呢?怎么本地让autoscaler成功跑起来呢?

Helm chart在values文件中声明为字符串的变量,部署替换占位符后却变成了数字类型

helm在获取values.yaml文件中配置的值时,由于没有对应一个结构体来反序列化yaml,只能使用map来接收,例如map[string]interface{}。可能是将interface{}尝试转成数字,能够转换成功helm就误以为我们需要的是数字了。

kubebuilder如何Watch由Pod产生的Event的创建,触发控制器Reconcile方法的执行

自定义资源的Controller创建出来的子资源,子资源创建的子资源(子子资源),如何Watch子子资源的事件?我们以MyDeployment->创建Pod->创建Event,想要watch Pod创建的Event的Create事件为例。