K8s 调度插件开发示例(翻译)

发布时间: 更新时间: 总字数:6713 阅读时间:14m 作者: IP上海 分享 网址

K8s 调度插件开发示例

说明

介绍

简而言之,K8S调度器负责将Pod分配给Nodes。调度一个pod的尝试分为两个阶段:SchedulingBinding cycle

调度周期中,节点被过滤,去除那些不符合要求的节点。接下来,剩余的节点根据给定的分数进行排名。最后,选择得分最高的节点。这些步骤被称为过滤(Filtering)评分(Scoring)

一旦选择了一个节点,调度器需要确保kubelet知道它需要在选定的节点上启动pod(容器)。与启动pod到所选节点有关的步骤被称为Binding Cycle

SchedulingBinding cycle是由按顺序执行的阶段组成的,以计算pod的运行节点。这些阶段被称为扩展点,可以用来塑造放置行为。不同pod的Scheduling Cycles是按顺序运行的,这意味着Scheduling Cycle步骤将一次为一个pod执行,而不同pod的Binding Cycles可能是同时执行的。

实现kubernetes调度器扩展点的组件被称为Plugins。原生调度行为也是使用Plugin模式实现的,与自定义扩展的方式相同,使kube-scheduler的核心轻量级,因为主要的调度逻辑被放在插件中。

插件可以应用的扩展点显示在下图中。一个插件可以实现一个或多个扩展点,每个扩展点的详细描述可以在[4]中找到(我不会在这里复制东西,继续之前先去那里看看😃)。

kubernetes scheduling framework pod scheduling content

图片来源

为了配置插件应该在每个扩展点执行,然后改变调度行为,kube-scheduler提供了Profiles[3]。可以提供多个配置文件,这意味着不需要部署多个调度器来拥有不同的调度行为[5]。

kube-scheduler

kube-scheduler是用Golang实现的,Plugins是在编译时包含在其中的。因此,如果你想拥有自己的插件,你需要有自己的调度器image。

一个新的插件需要被注册,并被配置到 Plugin API。同时,它需要实现kubernetes调度器框架包中定义的扩展点接口。示例如下:

// Plugin is the parent type for all the scheduling framework plugins.
type Plugin interface {
   Name() string
}

type QueueSortPlugin interface {
  Plugin
  Less(*QueuedPodInfo, *QueuedPodInfo) bool
}

type PreFilterPlugin interface {
  Plugin
  PreFilter(CycleState, *v1.Pod) *Status
  PreFilterExtensions() PreFilterExtensions
}

调度器的代码允许添加新的插件,而不需要fork它。为此,开发者只需要在调度器周围编写自己的main()包装器。由于插件必须与调度器一起编译,写一个包装器可以以一种干净的方式重新使用调度器的代码[7]。

为了做到这一点,主函数将导入 k8s.io/kubernetes/cmd/kube-scheduler/app 并使用 NewSchedulerCommand 来注册自定义插件,提供各自的名称和构造函数。

import (
    "k8s.io/kubernetes/cmd/kube-scheduler/app"
)

func main() {
    command := app.NewSchedulerCommand(
      app.WithPlugin("example-plugin1", ExamplePlugin1.New),
      app.WithPlugin("example-plugin2", ExamplePlugin2.New))
    if err := command.Execute(); err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", err)
        os.Exit(1)
    }
}

配置

kube-scheduler配置是可以配置配置文件的地方。每个配置文件允许插件被启用、禁用,并根据插件定义的配置参数进行配置。每个配置文件的配置被分成两部分[9]。

  1. 每个扩展点的启用插件列表以及它们应该运行的顺序。如果其中一个扩展点的列表被省略,将使用默认列表。
  2. 每一个插件都有一套可选的自定义插件参数。省略一个插件的配置参数,相当于使用该插件的默认配置。

在不同扩展点启用的插件必须在每个扩展点明确配置。

配置是通过 KubeSchedulerConfiguration 结构。要启用它,需要将其写入一个配置文件,并将其路径作为命令行参数提供给kube-scheduler。例如。

kube-scheduler --config=/etc/kubernetes/networktraffic-config.yaml

下面你可以看到 NetworkTraffic 插件的一个配置例子。在这个例子中,clientConnection.kubeconfig 指向kube-scheduler使用的kubeconfig路径,其在控制平面节点中定义的授权。profiles部分覆盖了default-scheduler分阶段启用NetworkTraffic插件并禁用默认定义的其他插件。pluginConfig设置插件的配置,这将在其初始化时提供[8]。

  • networktraffic-config.yaml
apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: "/etc/kubernetes/scheduler.conf"
profiles:
- schedulerName: default-scheduler
  plugins:
    score:
      enabled:
      - name: NetworkTraffic
      disabled:
      - name: "*"
  pluginConfig:
  - name: NetworkTraffic
    args:
      prometheusAddress: "http://prometheus-1616380099-server.monitor"
      networkInterface: "ens192"
      timeRangeInMinutes: 3

PS: 如果你有多个控制平面节点的HA,配置需要应用于每个节点。

创建一个自定义插件

现在我们了解了kube-scheduler的基础知识,我们可以做我们来这里的目的了。正如我们之前看到的,添加一个自定义插件需要在编译时包含我们的代码,我们不需要为此fork调度器的代码。

要继续下去,我们可以创建一个空的仓库,并按照之前的描述包装调度器,然而,项目 kubernetes-sigs/scheduler-plugins 已经做到了这一点,并提供了一些自定义的插件,是可以借鉴的好例子。所以,我们就从这里开始吧。

fork kubernetes-sigs/scheduler-plugins 仓库并将其拉入 $GOPATH/src/sigs.k8s.io。做完这些,我们就可以开始了 :)

cd $GOPATH/src/sigs.k8s.io
git clone https://github.com/kbcx/scheduler-plugins.git

要继续遵循接下来的步骤,你需要。

  1. 拥有一个K8S集群,参考k8s安装部署
  2. 将Prometheus配置在note-exporter中。下载 kube-prometheus-stack

NetworkTraffic Plugin

在这个例子中,我们要建立一个名为 NetworkTraffic评分插件(Score Plugin),它偏向于网络流量较低的节点。为了收集这些信息,我们将从Prometheus查询。

首先,在你的fork的scheduler-plugins中创建pkg/networktraffic文件夹和networktraffic.goprometheus.go文件。其结构应该是这样的:

|- pkg
|-- networktraffic
|--- networktraffic.go
|--- prometheus.go

networktraffic.go中,我们将有ScorePlugin接口的实现;在prometheus.go中,我们将保留与Prometheus交互的逻辑。

调用 Prometheus 接口

prometheus.go中,我们将首先声明用于与Prometheus交互的结构。它将有networkInterfacetimeRange字段,可以用来配置我们将要执行的查询。字段address指向K8S上的Prometheus服务,也可以进行配置。字段api将用于存储Prometheus客户端,它是根据提供的address创建的。

type PrometheusHandle struct {
  networkInterface string
  timeRange        time.Duration
  address          string
  api              v1.API
}

现在我们有了基本的结构,我们也可以实现查询的功能。我们将使用每个节点特定网络接口在某一时间范围内收到的bytes之和。kubernetes_node过滤器将查询所提供的节点的指标,如下面的查询所描述。device过滤器将查询所提供的网络接口的指标,[%s]之间的最后一个值定义了考虑的时间范围。sum_over_time将对提供的时间范围内的所有数值进行求和。

sum_over_time(node_network_receive_bytes_total{kubernetes_node=\"%s\",device=\"%s\"}[%s])

最后,prometheus.go文件将看起来像这样:

package networktraffic

import (
	"context"
	"fmt"
	"time"

	"github.com/prometheus/client_golang/api"
	v1 "github.com/prometheus/client_golang/api/prometheus/v1"
	"github.com/prometheus/common/model"
	"k8s.io/klog/v2"
)

const (
	// nodeMeasureQueryTemplate is the template string to get the query for the node used bandwidth
	nodeMeasureQueryTemplate = "sum_over_time(node_network_receive_bytes_total{kubernetes_node=\"%s\",device=\"%s\"}[%s])"
)

// Handles the interaction of the networkplugin with Prometheus
type PrometheusHandle struct {
	networkInterface string
	timeRange        time.Duration
	address          string
	api              v1.API
}

func NewPrometheus(address, networkInterface string, timeRange time.Duration) *PrometheusHandle {
	client, err := api.NewClient(api.Config{
		Address: address,
	})
	if err != nil {
		klog.Fatalf("[NetworkTraffic] Error creating prometheus client: %s", err.Error())
	}

	return &PrometheusHandle{
		networkInterface: networkInterface,
		timeRange:        timeRange,
		address:          address,
		api:              v1.NewAPI(client),
	}
}

func (p *PrometheusHandle) GetNodeBandwidthMeasure(node string) (*model.Sample, error) {
	query := getNodeBandwidthQuery(node, p.networkInterface, p.timeRange)
	res, err := p.query(query)
	if err != nil {
		return nil, fmt.Errorf("[NetworkTraffic] Error querying prometheus: %w", err)
	}

	nodeMeasure := res.(model.Vector)
	if len(nodeMeasure) != 1 {
		return nil, fmt.Errorf("[NetworkTraffic] Invalid response, expected 1 value, got %d", len(nodeMeasure))
	}

	return nodeMeasure[0], nil
}

func getNodeBandwidthQuery(node, networkInterface string, timeRange time.Duration) string {
	return fmt.Sprintf(nodeMeasureQueryTemplate, node, networkInterface, timeRange)
}

func (p *PrometheusHandle) query(query string) (model.Value, error) {
	results, warnings, err := p.api.Query(context.Background(), query, time.Now())

	if len(warnings) > 0 {
		klog.Warningf("[NetworkTraffic] Warnings: %v\n", warnings)
	}

	return results, err
}

ScorePlugin接口

在完成了与Prometheus的交互后,我们可以转向实现ScorePlugin。如前所述,我们将需要从调度器框架中实现ScorePlugin接口。

  • score_plugin_interface.go
// ScorePlugin is an interface that must be implemented by "Score"
// plugins to rank nodes that passed the filtering phase.
type ScorePlugin interface {
  Plugin

  // Score is called on each filtered node. It must return success
  // and an integer indicating the rank of the node. All scoring
  // plugins must return success or the pod will be rejected.
  Score(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) (int64, *Status)

  // ScoreExtensions returns a ScoreExtensions interface if it
  // implements one, or nil if does not.
  ScoreExtensions() ScoreExtensions
}

// ScoreExtensions is an interface for Score extended functionality.
type ScoreExtensions interface {
  // NormalizeScore is called for all node scores produced by the
  // same plugin's "Score" method. A successful run of
  // NormalizeScore will update the scores list and return a success
  // status.
  NormalizeScore(ctx context.Context, state *CycleState, p *v1.Pod, scores NodeScoreList) *Status
}

对每个节点调用Score函数,并返回是否成功和一个表示节点等级的整数。在Score插件执行结束时,我们应该有一个在0到100范围内的Score值。在某些情况下,如果不知道其他节点的得分,就很难在这个范围内得到一个值,例如。对于这些情况,我们可以使用ScoreExtensions接口中实现的NormalizeScore函数。NormalizeScore函数接收所有节点的结果并允许它们被改变。

此外,ScorePlugin接口也有Plugin接口作为一个嵌入字段。所以,我们必须实现它的Name() string函数。

现在我们了解了ScorePlugin的接口,让我们去看看networktraffic.go文件。我们将从定义 NetworkTraffic 结构开始。

// NetworkTraffic is a score plugin that favors nodes based on their
// network traffic amount. Nodes with less traffic are favored.
// Implements framework.ScorePlugin
type NetworkTraffic struct {
  handle     framework.FrameworkHandle
  prometheus *PrometheusHandle
}

有了结构的定义,我们就可以继续进行Score函数的实现。这将是直截了当的。我们将根据节点名称调用 GetNodeBandwidthMeasure 函数从Prometheus中获取数据。该调用将返回一个Sample,它持有Value字段中的值。我们基本上将为每个节点返回它。

  • score.go 网络流量插件得分功能
func (n *NetworkTraffic) Score(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) (int64, *framework.Status) {
	nodeBandwidth, err := n.prometheus.GetNodeBandwidthMeasure(nodeName)
	if err != nil {
		return 0, framework.NewStatus(framework.Error, fmt.Sprintf("error getting node bandwidth measure: %s", err))
	}

	klog.Infof("[NetworkTraffic] node '%s' bandwidth: %s", nodeName, nodeBandwidth.Value)
	return int64(nodeBandwidth.Value), nil
}

接下来,我们将返回每个节点在确定时间段内收到的总字节数。然而,调度器框架期望的是一个从0到100的值,因此,我们仍然需要将这些值归一化以满足这一要求。

为了进行规范化处理,我们将实现之前提到的 ScoreExtensions 接口。我们将实现嵌入 NetworkTraffic 结构中的接口。在 ScoreExtensions 函数中,我们将简单地返回实现该接口的结构。该逻辑被置于 NormalizeScore 函数下:

  • score_extensions.go
func (n *NetworkTraffic) ScoreExtensions() framework.ScoreExtensions {
	return n
}

func (n *NetworkTraffic) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {
	var higherScore int64
	for _, node := range scores {
		if higherScore < node.Score {
			higherScore = node.Score
		}
	}

	for i, node := range scores {
		scores[i].Score = framework.MaxNodeScore - (node.Score * framework.MaxNodeScore / higherScore)
	}

	klog.Infof("[NetworkTraffic] Nodes final score: %v", scores)
	return nil
}

NormalizeScore 基本上会将prometheus返回的最高值作为可能的最高值,对应于 framework.MaxNodeScore (100),其他数值将使用三原则相对于最高分计算。

最后,我们将有一个列表,网络流量大的节点在[0,100]范围内有更大的分数。如果我们像现在这样使用它,我们会偏向于有较高流量的节点,所以,我们需要把这些值倒过来。为此,我们将简单地用三原则的结果代替节点的得分,再减去最大得分。

下面是一个以三个节点 (a, b and c) 为例的计算实例,其数值是以字节为单位的。

a => 1000000   # 1MB
b => 1200000   # 1,2MB
c => 1400000   # 1,4MB

higherScore = 1400000

Y = (node.Score * framework.MaxNodeScore) / higherScore

Ya = 1000000 * 100 / 1400000
Yb = 1200000 * 100 / 1400000
Yc = 1400000 * 100 / 1400000

Ya = 71,42
Yb = 85,71
Yc = 100

Xa = 100 - Ya
Xb = 100 - Yb
Xc = 100 - Yc

Xa = 28,58
Xb = 14,29
Xc = 0

在解释了这一点之后,我们有了我们的插件的主要部分。然而,这并不是全部。如前所述,调度器插件是可以配置的,在我们的网络流量插件中,有三种配置是我们允许的,这一点已经提到:

  • Prometheus 地址
  • Prometheus 查询时间范围
  • Prometheus 查询节点网络接口

这些值将在调度器框架实例化 NetworkTraffic 插件时提供,我们需要声明一个名为 NetworkTrafficArgs 的新结构,用于解析 NetworkTraffic 中提供的配置 KubeSchedulerConfiguration。为此,我们需要添加一个新的函数,其逻辑是创建一个NetworkTraffic插件的实例,如下所述:

  • score_new.go
// New initializes a new plugin and returns it.
func New(obj runtime.Object, h framework.FrameworkHandle) (framework.Plugin, error) {
	args, ok := obj.(*config.NetworkTrafficArgs)
	if !ok {
		return nil, fmt.Errorf("want args to be of type NetworkTrafficArgs, got %T", obj)
	}

	return &NetworkTraffic{
		handle:     h,
		prometheus: NewPrometheus(args.Address, args.NetworkInterface, time.Minute*time.Duration(args.TimeRangeInMinutes)),
	}, nil
}

New函数遵循调度器框架PluginFactory接口。

我们还没有声明NetworkTrafficArgs结构,这将是下一步。然而,我们已经拥有了networktraffic.go所需的全部内容:

  • networktraffic.go
package networktraffic

import (
	"context"
	"fmt"
	"time"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/klog/v2"
	framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
	"sigs.k8s.io/scheduler-plugins/pkg/apis/config"
)

// NetworkTraffic is a score plugin that favors nodes based on their
// network traffic amount. Nodes with less traffic are favored.
// Implements framework.ScorePlugin
type NetworkTraffic struct {
	handle     framework.FrameworkHandle
	prometheus *PrometheusHandle
}

// Name is the name of the plugin used in the Registry and configurations.
const Name = "NetworkTraffic"

var _ = framework.ScorePlugin(&NetworkTraffic{})

// New initializes a new plugin and returns it.
func New(obj runtime.Object, h framework.FrameworkHandle) (framework.Plugin, error) {
	args, ok := obj.(*config.NetworkTrafficArgs)
	if !ok {
		return nil, fmt.Errorf("[NetworkTraffic] want args to be of type NetworkTrafficArgs, got %T", obj)
	}

	klog.Infof("[NetworkTraffic] args received. NetworkInterface: %s; TimeRangeInMinutes: %d, Address: %s", args.NetworkInterface, args.TimeRangeInMinutes, args.Address)

	return &NetworkTraffic{
		handle:     h,
		prometheus: NewPrometheus(args.Address, args.NetworkInterface, time.Minute*time.Duration(args.TimeRangeInMinutes)),
	}, nil
}

// Name returns name of the plugin. It is used in logs, etc.
func (n *NetworkTraffic) Name() string {
	return Name
}

func (n *NetworkTraffic) Score(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) (int64, *framework.Status) {
	nodeBandwidth, err := n.prometheus.GetNodeBandwidthMeasure(nodeName)
	if err != nil {
		return 0, framework.NewStatus(framework.Error, fmt.Sprintf("error getting node bandwidth measure: %s", err))
	}

	klog.Infof("[NetworkTraffic] node '%s' bandwidth: %s", nodeName, nodeBandwidth.Value)
	return int64(nodeBandwidth.Value), nil
}

func (n *NetworkTraffic) ScoreExtensions() framework.ScoreExtensions {
	return n
}

func (n *NetworkTraffic) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {
	var higherScore int64
	for _, node := range scores {
		if higherScore < node.Score {
			higherScore = node.Score
		}
	}

	for i, node := range scores {
		scores[i].Score = framework.MaxNodeScore - (node.Score * framework.MaxNodeScore / higherScore)
	}

	klog.Infof("[NetworkTraffic] Nodes final score: %v", scores)
	return nil
}

配置

scheduler-plugins项目在pkg/apis文件夹下保存配置。所以,我们的插件配置也会在那里。

我们将在两个地方添加配置:pkg/apis/config/types.gopkg/apis/config/v1beta1/types.goconfig/types.go存放着我们将在New函数中使用的结构,而v1beta1/types.go存放着用于解析来自于 KubeSchedulerConfiguration 的信息。

另外,配置结构必须遵循命名模式<Plugin Name>Args,否则,它不会被正确解码,你将面临不被正确解析的问题。

  • config/types.go
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// NetworkTrafficArgs holds arguments used to configure NetworkTraffic plugin.
type NetworkTrafficArgs struct {
	metav1.TypeMeta

	// Address of the Prometheus Server
	Address string
	// NetworkInterface to be monitored, assume that nodes OS is homogeneous
	NetworkInterface string
	// TimeRangeInMinutes used to aggregate the network metrics
	TimeRangeInMinutes int64
}
  • config/v1beta1/types.go
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:defaulter-gen=true

// NetworkTrafficArgs holds arguments used to configure NetworkTraffic plugin.
type NetworkTrafficArgs struct {
	metav1.TypeMeta `json:",inline"`

	// Address of the Prometheus Server
	Address *string `json:"prometheusAddress,omitempty"`
	// NetworkInterface to be monitored, assume that nodes OS is homogeneous
	NetworkInterface *string `json:"networkInterface,omitempty"`
	// TimeRangeInMinutes used to aggregate the network metrics
	TimeRangeInMinutes *int64 `json:"timeRangeInMinutes,omitempty"`
}

添加了结构后,我们需要执行 hack/update-codegen.sh脚本。它将用 DeepCopy 的功能来更新所生成的文件,以增加结构。

此外,我们将在 config/v1beta1/defaults.go 添加一个新的函数 SetDefaultNetworkTrafficArgs。该函数将设置 NetworkInterfaceTimeRangeInMinutes 的默认值,但仍需提供 Address

  • config/v1beta1/defaults.go 插件参数的默认值
// SetDefaultNetworkTrafficArgs sets the default parameters for the NetworkTraffic plugin
func SetDefaultNetworkTrafficArgs(args *NetworkTrafficArgs) {
	if args.TimeRangeInMinutes == nil {
		defaultTime := int64(5)
		args.TimeRangeInMinutes = &defaultTime
	}

	if args.NetworkInterface == nil || *args.NetworkInterface == "" {
		netInterface := "ens192"
		args.NetworkInterface = &netInterface
	}
}

为了完成默认值的配置,我们需要确保上面的函数已经在v1beta1模式中注册。因此,要确保它被注册在文件 pkg/apis/config/v1beta1/zz_generated.defaults.go

  • pkg/apis/config/v1beta1/zz_generated.defaults.go
package v1beta1

import (
	runtime "k8s.io/apimachinery/pkg/runtime"
)

// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
	scheme.AddTypeDefaultingFunc(&NetworkTrafficArgs{}, func(obj interface{}) {
		SetObjectDefaultNetworkTrafficArgs(obj.(*NetworkTrafficArgs))
	})

	return nil
}

func SetObjectDefaultNetworkTrafficArgs(in *NetworkTrafficArgs) {
	SetDefaultNetworkTrafficArgs(in)
}

注册插件和配置

现在参数结构已经定义,我们的插件已经准备好了。然而,我们仍然需要在调度器框架中注册该插件和配置。

scheduler-plugins项目已经注册了几个插件,这让事情变得更容易,因为我们有了例子。插件配置的注册是放在pkg/apis/config下。在文件register.go中,我们需要在调用AddKnownTypes函数时添加NetworkTrafficArgs。同样需要在pkg/apis/config/v1beta1/register.go文件。随着这两个文件的改变,配置注册就完成了。

接下来,我们转向插件注册,这是在 cmd/scheduler/main.go 文件。在main函数中, NetworkTraffic 插件名称和构造函数需要作为参数提供给 NewSchedulerCommand。它应该是这样的:

command := app.NewSchedulerCommand(
  app.WithPlugin(networktraffic.Name, networktraffic.New),
)

另外,注意到在main.go文件中,我们导入了 sigs.k8s.io/scheduler-plugins/pkg/apis/config/scheme,它用我们在pkg/apis/config文件中引入的所有配置来初始化该方案。

从代码的角度来说,我们已经完成了。完整的实现可以在这里找到,它还包括几个单元测试,所以请自行看一下吧

部署和使用该插件

现在我们已经完成了这个插件,我们可以把它部署到我们的K8S集群中并开始使用它。在scheduler-plugins资源库中,有一个关于如何做的文档,请查看这里。我们基本上需要用我们刚刚实现的插件来调整这些步骤。

尽管如此,在对集群进行修改之前,请确保你已经建立了调度器容器镜像,并将其推送到可以从kubernetes访问的容器注册中心。我不会去研究细节,因为它将根据使用环境的不同而不同。你也可以查看Makefile,因为有一些命令可以构建和推送镜像,这个开发文档也可以帮助你。

由于我们的插件没有引入任何CRD,scheduler-plugins 安装文档中的几个步骤可以跳过。正如我所提到的,我使用的是一个用kubespray创建的带有HA的集群。因此,我需要在每个控制平面节点上重复以下步骤。

  1. 登录到控制平面节点
  2. 备份kube-scheduler.yaml
# cp /etc/kubernetes/manifests/kube-scheduler.yaml /etc/kubernetes/kube-scheduler.yaml
cp /etc/kubernetes/manifests/kube-scheduler.yaml /etc/kubernetes/manifests/kube-scheduler.yaml.bak
  1. 创建 /etc/kubernetes/networktraffic-config.yaml 并根据你的环境改变这些值。
apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: "/etc/kubernetes/scheduler.conf"
profiles:
- schedulerName: default-scheduler
  plugins:
    score:
      enabled:
      - name: NetworkTraffic
      disabled:
      - name: "*"
  pluginConfig:
  - name: NetworkTraffic
    args:
      prometheusAddress: "http://prometheus-1616380099-server.monitor"
      networkInterface: "ens192"
      timeRangeInMinutes: 3
  1. 修改/etc/kubernetes/manifests/kube-scheduler.yaml来运行带有网络流量的调度器插件。我们所做的改变是:
  • 添加命令参数 --config=/etc/kubernetes/networktraffic-config.yaml
  • 改变 image 的名称
  • 添加一个 volume,指向配置的绝对路径
  • 添加一个 volumeMount,使配置对调度器pod可用

看看下面的例子:

  • kube-scheduler.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    component: kube-scheduler
    tier: control-plane
  name: kube-scheduler
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-scheduler
    - --authentication-kubeconfig=/etc/kubernetes/scheduler.conf
    - --authorization-kubeconfig=/etc/kubernetes/scheduler.conf
    - --bind-address=0.0.0.0
    - --kubeconfig=/etc/kubernetes/scheduler.conf
    - --leader-elect=true
    - --leader-elect-lease-duration=15s
    - --leader-elect-renew-deadline=10s
    - --port=0
    - --config=/etc/kubernetes/networktraffic-config.yaml
    image: <YOUR_CONTAINER_REGISTRY>/scheduler-plugins/kube-scheduler:<YOUR_TAG>
    imagePullPolicy: Always
    livenessProbe:
      failureThreshold: 8
      httpGet:
        path: /healthz
        port: 10259
        scheme: HTTPS
      initialDelaySeconds: 10
      periodSeconds: 10
      timeoutSeconds: 15
    name: kube-scheduler
    resources:
      requests:
        cpu: 100m
    startupProbe:
      failureThreshold: 30
      httpGet:
        path: /healthz
        port: 10259
        scheme: HTTPS
      initialDelaySeconds: 10
      periodSeconds: 10
      timeoutSeconds: 15
    volumeMounts:
    - mountPath: /etc/kubernetes/scheduler.conf
      name: kubeconfig
      readOnly: true
    - mountPath: /etc/kubernetes/networktraffic-config.yaml
      name: networktraffic-config
      readOnly: true
  hostNetwork: true
  priorityClassName: system-node-critical
  volumes:
  - hostPath:
      path: /etc/kubernetes/scheduler.conf
      type: FileOrCreate
    name: kubeconfig
  - hostPath:
      path: /etc/kubernetes/networktraffic-config.yaml
      type: File
    name: networktraffic-config

现在,我们可以开始利用我们的自定义插件了。一旦你检查了运行中的pod的日志,你应该看到从Prometheus返回的节点带宽的行,你可以确保行为是预期的。下面,我们可以看到node4正确地拥有较高的分数,因为它是网络流量较少的节点。

networktraffic.go:54] [NetworkTraffic] node 'node5 bandwidth: 312528236767
networktraffic.go:54] [NetworkTraffic] node 'node3' bandwidth: 327333347411
networktraffic.go:54] [NetworkTraffic] node 'node' bandwidth: 321718404122
networktraffic.go:54] [NetworkTraffic] node 'node4" bandwidth: 224935743744
networktraffic.go:54] [NetworkTraffic] node 'node6' bandwidth: 415270171795
networktraffic.go:54] [NetworkTraffic] node 'nodez' bandwidth: 270270915134
networktraffic.go:74] [NetworkTraffic] Nodes final score: [fnode5 25} fnode6 0} fnodel 23} fnode3 22] {node4 46} {nodez 35}]

希望这篇文章对你有用,并欢迎你在评论中给予反馈,我们非常感谢你!

References

  1. https://kubernetes.io/docs/concepts/scheduling-eviction/kube-scheduler/
  2. https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/
  3. https://kubernetes.io/docs/reference/scheduling/config/#profiles
  4. https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/#extension-points
  5. https://kubernetes.io/docs/reference/scheduling/config/#multiple-profiles
  6. https://github.com/kubernetes/enhancements/blob/master/keps/sig-scheduling/624-scheduling-framework/README.md
  7. https://github.com/kubernetes/enhancements/blob/master/keps/sig-scheduling/624-scheduling-framework/README.md#custom-scheduler-plugins-out-of-tree
  8. https://github.com/kubernetes/enhancements/blob/master/keps/sig-scheduling/624-scheduling-framework/README.md#optional-args
  9. https://github.com/kubernetes/enhancements/blob/master/keps/sig-scheduling/624-scheduling-framework/README.md#configuring-plugins
Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数