K8s 调度插件开发示例
说明
介绍
简而言之,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]中找到(我不会在这里复制东西,继续之前先去那里看看😃)。
图片来源
为了配置插件
应该在每个扩展点执行,然后改变调度行为,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]。
- 每个扩展点的启用插件列表以及它们应该运行的顺序。如果其中一个扩展点的列表被省略,将使用默认列表。
- 每一个插件都有一套可选的自定义插件参数。省略一个插件的配置参数,相当于使用该插件的默认配置。
在不同扩展点启用的插件必须在每个扩展点明确配置。
配置是通过 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
要继续遵循接下来的步骤,你需要。
- 拥有一个K8S集群,参考k8s安装部署
- 将Prometheus配置在note-exporter中。下载 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
字段中的值。我们基本上将为每个节点返回它。
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
函数下:
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
插件的实例,如下所述:
// 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
所需的全部内容:
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
,否则,它不会被正确解码,你将面临不被正确解析的问题。
// +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
}
// +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资源库中,有一个关于如何做的文档,请查看这里。我们基本上需要用我们刚刚实现的插件来调整这些步骤。
尽管如此,在对集群进行修改之前,请确保你已经建立了调度器容器镜像,并将其推送到可以从kubernetes访问的容器注册中心。我不会去研究细节,因为它将根据使用环境的不同而不同。你也可以查看Makefile,因为有一些命令可以构建和推送镜像,这个开发文档也可以帮助你。
由于我们的插件没有引入任何CRD,scheduler-plugins 安装文档中的几个步骤可以跳过。正如我所提到的,我使用的是一个用kubespray创建的带有HA的集群。因此,我需要在每个控制平面节点上重复以下步骤。
- 登录到控制平面节点
- 备份
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
- 创建
/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
- 修改
/etc/kubernetes/manifests/kube-scheduler.yaml
来运行带有网络流量的调度器插件。我们所做的改变是:
- 添加命令参数
--config=/etc/kubernetes/networktraffic-config.yaml
- 改变
image
的名称 - 添加一个
volume
,指向配置的绝对路径 - 添加一个
volumeMount
,使配置对调度器pod可用
看看下面的例子:
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
- https://kubernetes.io/docs/concepts/scheduling-eviction/kube-scheduler/
- https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/
- https://kubernetes.io/docs/reference/scheduling/config/#profiles
- https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/#extension-points
- https://kubernetes.io/docs/reference/scheduling/config/#multiple-profiles
- https://github.com/kubernetes/enhancements/blob/master/keps/sig-scheduling/624-scheduling-framework/README.md
- https://github.com/kubernetes/enhancements/blob/master/keps/sig-scheduling/624-scheduling-framework/README.md#custom-scheduler-plugins-out-of-tree
- https://github.com/kubernetes/enhancements/blob/master/keps/sig-scheduling/624-scheduling-framework/README.md#optional-args
- https://github.com/kubernetes/enhancements/blob/master/keps/sig-scheduling/624-scheduling-framework/README.md#configuring-plugins