原创 吴就业 146 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是根据部署在节点上的所有Pod声明的requests来计算资源使用率的,这个并不能反应真实情况。
低负载自动缩容节点的这个特性还是非常有必要的,能够避免资源的过渡浪费。因为随着时间的推移,集群中势必会出现非常多的弹性节点利用率很低的情况。
autoscaler,想要缩短空闲节点被回收的时间,需要同时考虑三个启动参数的配置:scale-down-unneeded-time、unremovable-node-recheck-timeout、scale-down-delay-after-add。
在google cloud上使用gke集群,gke已经集成autoscaler,通过在控制台创建节点池点击开启自动扩缩容就可以,那么为什么还要自己部署呢?怎么本地让autoscaler成功跑起来呢?
helm在获取values.yaml文件中配置的值时,由于没有对应一个结构体来反序列化yaml,只能使用map来接收,例如map[string]interface{}。可能是将interface{}尝试转成数字,能够转换成功helm就误以为我们需要的是数字了。
自定义资源的Controller创建出来的子资源,子资源创建的子资源(子子资源),如何Watch子子资源的事件?我们以MyDeployment->创建Pod->创建Event,想要watch Pod创建的Event的Create事件为例。
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。