暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

创建一个 kube-scheduler 插件

k8s技术圈 2023-04-19
491

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

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

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

Scheduling
Binding 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调度器框架包(https://github.com/kubernetes/kubernetes/blob/ed3e0d302fb546653b78df583569b0311687a7a8/pkg/scheduler/framework/interface.go#L268)中定义的扩展点接口。示例如下:

// 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
(https://kubernetes.io/docs/reference/config-api/kube-scheduler-config.v1/) 结构。要启用它,需要将其写入一个配置文件,并将其路径作为命令行参数提供给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
(https://github.com/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集群。
  2. 将Prometheus配置在note-exporter中。下载 kube-prometheus-stack
    (https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack)。

NetworkTraffic Plugin

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

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

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

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

调用 Prometheus 接口

prometheus.go
中,我们将首先声明用于与Prometheus交互的结构。它将有networkInterface
timeRange
字段,可以用来配置我们将要执行的查询。字段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
(https://github.com/kubernetes/kubernetes/blob/4aae71695a8dd43918702fafd81e0401721d79d9/pkg/scheduler/framework/runtime/registry.go#L29)接口。

我们还没有声明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.go
pkg/apis/config/v1beta1/types.go
config/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
。该函数将设置 NetworkInterface
TimeRangeInMinutes
的默认值,但仍需提供 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资源库中,有一个关于如何做的文档,请查看这里
(https://github.com/kubernetes-sigs/scheduler-plugins/blob/master/doc/install.md)。我们基本上需要用我们刚刚实现的插件来调整这些步骤。

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

由于我们的插件没有引入任何CRD,scheduler-plugins 安装文档
(https://github.com/kubernetes-sigs/scheduler-plugins/blob/master/doc/install.md)中的几个步骤可以跳过。正如我所提到的,我使用的是一个用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


原文链接:https://medium.com/@juliorenner123/k8s-creating-a-kube-scheduler-plugin-8a826c486a1

文章转载自k8s技术圈,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论