0%

【Kubernetes】调度策略

本文分析了Kubernetes内置的各种调度策略。

Predicate

整体梳理

策略名称 策略算法 备注
CheckNodeUnschedulable 在 Node 节点上有一个 NodeUnschedulable 的标记,那这个节点就不会被调度了
CheckVolumeBinding 在 pvc 和 pv 的 binding 过程中对其进行逻辑校验
GeneralPredicates 是 PodFitsHostPorts,PodFitsResources,HostName,MatchNodeSelector这四个的组合
MatchInterPodAffinity 亲和性检查,当Node上所有正在运行的Pod与待调度的Pod不互相排斥时,则可调度
MaxAzureDiskVolumeCount 当Node上被挂载的Azure Disk Volume超过默认限制,该Node不可调度
MaxCSIVolumeCountPred 当Node上被挂载的CSI Volume超过默认限制,该Node不可调度
MaxEBSVolumeCount 当Node上被挂载的AWS EBS Volume超过默认限制39,该Node不可调度
MaxGCEPDVolumeCount 当Node上被挂载的GCD Persistent Disk超过默认限制16,该Node不可调度
MaxQcloudCbsVolumeCount 当Node上被挂载的Qcloud CBS Volume超过默认限制,该Node不可调度
NoDiskConflict 当Node上所有Pod使用的卷和待调度Pod使用的卷存在冲突,该Node不可调度
NoVolumeZoneConflict 当Node上的zone-lable包含Pod中PV卷下的zone-label时,可以调度。当Node上没有zone-label,表示没有zone限制,也可调度
PodToleratesNodeTaints 当Pod可以容忍Node上所有的taint时,该Node才可以调度
PodFitsHostPorts 当待调度Pod中所有容器所用到的HostPort与Node上已使用的Port存在冲突,则无法调度
PodFitsResources 当总资源-Node中所有Pod对资源的request总量 < 待调度的Pod request总量,则无法调度
HostName 如果待调度的Pod制定了pod.Spec.Host,则调度到该主机上
MatchNodeSelector 校验 Pod.Spec.Affinity.NodeAffinity 和 Pod.Spec.NodeSelector 是否与 Node 的 Labels 匹配
CheckNodeMemoryPressure 当Node剩余内存紧张时,BestEffort类型的Pod无法调度到该主机
CheckNodeDiskPressure 当Node剩余磁盘空间紧张时,无法调度到该主机
PodFitsHostPorts 当待调度Pod中所有容器所用到的HostPort与Node上已使用的Port存在冲突,则无法调度
PodFitsResources 当总资源-Node中所有Pod对资源的request总量 < 待调度的Pod request总量,则无法调度
HostName 如果待调度的Pod制定了pod.Spec.Host,则调度到该主机上
EvenPodsSpread 在1.18版本默认启动,符合条件的一组 Pod 在指定 TopologyKey 上的打散要求
CheckNodeLabelPresence 主要用于检查指定的Label是否在Node上存在
CheckServiceAffinityPred 根据当前POD对象所属的service已有的其他POD对象所运行的节点进行调度,其目的在于将相同service的POD 对象放置与同一个或同一类节点上以提高效率,此预选此类试图将那些在其节点选择器中带有特定标签的POD资源调度至拥有同样标签的节点上,具体的标签则取决于用户的定义。

存储相关

NoVolumeZoneConflictPred

当在 k8s 集群中使用 zone 时,所有的Node都会被标记上 zone label,下面四种是常见的lable的key:

k8s.io/api/core/v1
1
2
3
4
LabelZoneFailureDomain       = "failure-domain.beta.kubernetes.io/zone"
LabelZoneRegion = "failure-domain.beta.kubernetes.io/region"
LabelZoneFailureDomainStable = "topology.kubernetes.io/zone"
LabelZoneRegionStable = "topology.kubernetes.io/region"

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: v1
kind: Node
metadata:
name: 10.0.1.28
annotations:
node.alpha.kubernetes.io/ttl: "0"
volumes.kubernetes.io/controller-managed-attach-detach: "true"
creationTimestamp: "2020-07-20T12:11:34Z"
resourceVersion: "334106446"
selfLink: /api/v1/nodes/10.0.1.28
uid: 5943d3fc-0841-43f2-b519-c32af755c1c5
labels:
beta.kubernetes.io/arch: amd64
beta.kubernetes.io/instance-type: QCLOUD
beta.kubernetes.io/os: linux
cloud.tencent.com/node-instance-id: ins-r3gy6izp
failure-domain.beta.kubernetes.io/region: bj
failure-domain.beta.kubernetes.io/zone: "800002"
topology.kubernetes.io/region: bj
topology.kubernetes.io/zone: "800002"
kubernetes.io/arch: amd64
kubernetes.io/hostname: 10.0.1.28
kubernetes.io/os: linux
spec:
podCIDR: 172.18.0.128/26
podCIDRs:
- 172.18.0.128/26
providerID: qcloud:///800002/ins-r3gy6izp

当一个Pod有存储卷要求时,需要检查该存储卷的zone调度约束是否与Node的zone限制存在冲突。

pkg/scheduler/framework/plugins/volumezone/volume_zone.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for k, v := range pv.ObjectMeta.Labels {
if !volumeZoneLabels.Has(k) {
continue
}
nodeV, _ := nodeConstraints[k]
volumeVSet, err := volumehelpers.LabelZonesToSet(v)
if err != nil {
klog.Warningf("Failed to parse label for %q: %q. Ignoring the label. err=%v. ", k, v, err)
continue
}

if !volumeVSet.Has(nodeV) {
klog.V(10).Infof("Won't schedule pod %q onto node %q due to volume %q (mismatch on %q)", pod.Name, node.Name, pvName, k)
return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonConflict)
}
}

通过检查的条件是:属于该Pod的所有volumes都必须与Node上的zone label完全匹配。

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPredicateConfigProducer(NoVolumeZoneConflictPred,
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, volumezone.Name, nil)
return
})

CheckVolumeBindingPred

在 pvc 和 pv 的 binding 过程中对其进行逻辑校验,里头的逻辑写的比较复杂,主要都是如何复用 pv;

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
7
8
registry.registerPredicateConfigProducer(CheckVolumeBindingPred,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.PreFilter = appendToPluginSet(plugins.PreFilter, volumebinding.Name, nil)
plugins.Filter = appendToPluginSet(plugins.Filter, volumebinding.Name, nil)
plugins.Reserve = appendToPluginSet(plugins.Reserve, volumebinding.Name, nil)
plugins.PreBind = appendToPluginSet(plugins.PreBind, volumebinding.Name, nil)
return
})

NoDiskConflictPred

SCSI 存储不会被重复的 volume, 检查在此主机上是否存在卷冲突。如果这个主机已经挂载了卷,其它同样使用这个卷的Pod不能调度到这个主机上,不同的存储后端具体规则不同

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPredicateConfigProducer(NoDiskConflictPred,
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, volumerestrictions.Name, nil)
return
})

MaxCSIVolumeCountPred

一个Pod请求Volumes的时候,节点上可能已经有Volumes,需要检查加上这个Pod之后的Volumes是否超过Node最大允许的Volumes限制。MaxCSIVolumeCountPred 用来校验 pvc 上指定的 Provision 在 CSI plugin 上的单机最大 pv 数限制。

1
2
3
4
5
6
7
8
9
for volumeLimitKey, count := range newVolumeCount {
maxVolumeLimit, ok := nodeVolumeLimits[v1.ResourceName(volumeLimitKey)]
if ok {
currentVolumeCount := attachedVolumeCount[volumeLimitKey]
if currentVolumeCount+count > int(maxVolumeLimit) {
return framework.NewStatus(framework.Unschedulable, ErrReasonMaxVolumeCountExceeded)
}
}
}

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPredicateConfigProducer(MaxCSIVolumeCountPred,
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, nodevolumelimits.CSIName, nil)
return
})

MaxNonCSIVolumeCountPred

对于不是CSI标准的存储插件,也需要满足最大PV数限制,整体逻辑类似。

1
2
3
4
if numExistingVolumes+numNewVolumes > maxAttachLimit {
// violates MaxEBSVolumeCount or MaxGCEPDVolumeCount
return framework.NewStatus(framework.Unschedulable, ErrReasonMaxVolumeCountExceeded)
}
MaxEBSVolumeCountPred

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPredicateConfigProducer(MaxEBSVolumeCountPred,
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, nodevolumelimits.EBSName, nil)
return
})
MaxGCEPDVolumeCountPred

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPredicateConfigProducer(MaxGCEPDVolumeCountPred,
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, nodevolumelimits.GCEPDName, nil)
return
})
MaxAzureDiskVolumeCountPred

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPredicateConfigProducer(MaxAzureDiskVolumeCountPred,
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, nodevolumelimits.AzureDiskName, nil)
return
})
MaxCinderVolumeCountPred

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPredicateConfigProducer(MaxCinderVolumeCountPred,
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, nodevolumelimits.CinderName, nil)
return
})

Pod 与 Node 匹配相关

  • CheckNodeCondition:校验节点是否准备好被调度,校验node.condition的condition type :Ready为true和NetworkUnavailable为false以及Node.Spec.Unschedulable为false;
  • PodFitsHostPorts:校验 Pod 上的 Container 声明的 Ports 是否正在被 Node 上已经分配的 Pod 使用;
  • MatchNodeSelector: 校验 Pod.Spec.Affinity.NodeAffinity 和 Pod.Spec.NodeSelector 是否与 Node 的 Labels 匹配。

PodFitsHostPortsPred

PodFitsHostPorts策略主要用于校验 Pod 上的 Container 声明的 Ports 是否正在被 Node 上已经分配的 Pod 使用。

在 PreFilter 阶段,获取当前 Pod 对应的所有容器的Port,并且写入cycleState。

1
2
3
4
5
6
// PreFilter invoked at the prefilter extension point.
func (pl *NodePorts) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) *framework.Status {
s := getContainerPorts(pod)
cycleState.Write(preFilterStateKey, preFilterState(s))
return nil
}

在 Filter 阶段,从Cycle拿到当前Pod请求的Port,对比当前系统中已使用的 Port,看是否会发生冲突。

1
2
3
4
5
6
7
8
9
10
11
12
13
func (pl *NodePorts) Filter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
wantPorts, err := getPreFilterState(cycleState)
if err != nil {
return framework.NewStatus(framework.Error, err.Error())
}

fits := fitsPorts(wantPorts, nodeInfo)
if !fits {
return framework.NewStatus(framework.Unschedulable, ErrReason)
}

return nil
}

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
registry.registerPredicateConfigProducer(PodFitsHostPortsPred,
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, nodeports.Name, nil)
plugins.PreFilter = appendToPluginSet(plugins.PreFilter, nodeports.Name, nil)
return
})

PodFitsResourcesPred

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
7
8
9
10
registry.registerPredicateConfigProducer(PodFitsResourcesPred,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, noderesources.FitName, nil)
plugins.PreFilter = appendToPluginSet(plugins.PreFilter, noderesources.FitName, nil)
if args.NodeResourcesFitArgs != nil {
pluginConfig = append(pluginConfig,
config.PluginConfig{Name: noderesources.FitName, Args: args.NodeResourcesFitArgs})
}
return
})

PodToleratesNodeTaintsPred

PodToleratesNodeTaints策略校验 Node 的 Taints 是否被 Pod Tolerates 包含。这里主要检查 NoScheduleNoExecute 这两个 taint,如果不容忍,那么返回错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (pl *TaintToleration) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
if nodeInfo == nil || nodeInfo.Node() == nil {
return framework.NewStatus(framework.Error, "invalid nodeInfo")
}

filterPredicate := func(t *v1.Taint) bool {
// PodToleratesNodeTaints is only interested in NoSchedule and NoExecute taints.
return t.Effect == v1.TaintEffectNoSchedule || t.Effect == v1.TaintEffectNoExecute
}

taint, isUntolerated := v1helper.FindMatchingUntoleratedTaint(nodeInfo.Node().Spec.Taints, pod.Spec.Tolerations, filterPredicate)
if !isUntolerated {
return nil
}

errReason := fmt.Sprintf("node(s) had taint {%s: %s}, that the pod didn't tolerate",
taint.Key, taint.Value)
return framework.NewStatus(framework.UnschedulableAndUnresolvable, errReason)
}

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPredicateConfigProducer(PodToleratesNodeTaintsPred,
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, tainttoleration.Name, nil)
return
})

HostNamePred

NodeNamePred策略主要用于检查Pod Spec声明的Node Name是否与Node实际的Name匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Filter invoked at the filter extension point.
func (pl *NodeName) Filter(ctx context.Context, _ *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
if nodeInfo.Node() == nil {
return framework.NewStatus(framework.Error, "node not found")
}
if !Fits(pod, nodeInfo) {
return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReason)
}
return nil
}

// Fits actually checks if the pod fits the node.
func Fits(pod *v1.Pod, nodeInfo *framework.NodeInfo) bool {
return len(pod.Spec.NodeName) == 0 || pod.Spec.NodeName == nodeInfo.Node().Name
}

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPredicateConfigProducer(HostNamePred,
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, nodename.Name, nil)
return
})

MatchNodeSelectorPred

MatchNodeSelectorPred策略用于校验 Pod.Spec.Affinity.NodeAffinity 和 Pod.Spec.NodeSelector 是否与 Node 的 Labels 匹配 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
func PodMatchesNodeSelectorAndAffinityTerms(pod *v1.Pod, node *v1.Node) bool {
// Check if node.Labels match pod.Spec.NodeSelector.
if len(pod.Spec.NodeSelector) > 0 {
selector := labels.SelectorFromSet(pod.Spec.NodeSelector)
if !selector.Matches(labels.Set(node.Labels)) {
return false
}
}

// 1. nil NodeSelector matches all nodes (i.e. does not filter out any nodes)
// 2. nil []NodeSelectorTerm (equivalent to non-nil empty NodeSelector) matches no nodes
// 3. zero-length non-nil []NodeSelectorTerm matches no nodes also, just for simplicity
// 4. nil []NodeSelectorRequirement (equivalent to non-nil empty NodeSelectorTerm) matches no nodes
// 5. zero-length non-nil []NodeSelectorRequirement matches no nodes also, just for simplicity
// 6. non-nil empty NodeSelectorRequirement is not allowed
nodeAffinityMatches := true
affinity := pod.Spec.Affinity
if affinity != nil && affinity.NodeAffinity != nil {
nodeAffinity := affinity.NodeAffinity
// if no required NodeAffinity requirements, will do no-op, means select all nodes.
// TODO: Replace next line with subsequent commented-out line when implement RequiredDuringSchedulingRequiredDuringExecution.
if nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil {
// if nodeAffinity.RequiredDuringSchedulingRequiredDuringExecution == nil && nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil {
return true
}

// Match node selector for requiredDuringSchedulingRequiredDuringExecution.
// TODO: Uncomment this block when implement RequiredDuringSchedulingRequiredDuringExecution.
// if nodeAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil {
// nodeSelectorTerms := nodeAffinity.RequiredDuringSchedulingRequiredDuringExecution.NodeSelectorTerms
// klog.V(10).Infof("Match for RequiredDuringSchedulingRequiredDuringExecution node selector terms %+v", nodeSelectorTerms)
// nodeAffinityMatches = nodeMatchesNodeSelectorTerms(node, nodeSelectorTerms)
// }

// Match node selector for requiredDuringSchedulingIgnoredDuringExecution.
if nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
nodeSelectorTerms := nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms
nodeAffinityMatches = nodeAffinityMatches && nodeMatchesNodeSelectorTerms(node, nodeSelectorTerms)
}

}
return nodeAffinityMatches
}

这是一个典型的Node亲和性示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
pod: &v1.Pod{
Spec: v1.PodSpec{
Affinity: &v1.Affinity{
NodeAffinity: &v1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "kernel-version",
Operator: v1.NodeSelectorOpGt,
Values: []string{"0204"},
},
},
},
},
},
},
},
},
},
labels: map[string]string{
// We use two digit to denote major version and two digit for minor version.
"kernel-version": "0206",
},
name: "Pod with matchExpressions using Gt operator that matches the existing node",
},

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPredicateConfigProducer(MatchNodeSelectorPred,
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, nodeaffinity.Name, nil)
return
})

GeneralPred

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
registry.registerPredicateConfigProducer(GeneralPred,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
// GeneralPredicate is a combination of predicates.
plugins.Filter = appendToPluginSet(plugins.Filter, noderesources.FitName, nil)
plugins.PreFilter = appendToPluginSet(plugins.PreFilter, noderesources.FitName, nil)
if args.NodeResourcesFitArgs != nil {
pluginConfig = append(pluginConfig,
config.PluginConfig{Name: noderesources.FitName, Args: args.NodeResourcesFitArgs})
}
plugins.Filter = appendToPluginSet(plugins.Filter, nodename.Name, nil)
plugins.Filter = appendToPluginSet(plugins.Filter, nodeports.Name, nil)
plugins.PreFilter = appendToPluginSet(plugins.PreFilter, nodeports.Name, nil)
plugins.Filter = appendToPluginSet(plugins.Filter, nodeaffinity.Name, nil)
return
})

CheckNodeUnschedulablePred

CheckNodeUnschedulable 在 node 节点上有一个 NodeUnschedulable 的标记,那这个节点就不会被调度了,形如这种。

1
2
3
4
5
node: &v1.Node{
Spec: v1.NodeSpec{
Unschedulable: true,
},
},

在 1.16 的版本里,这个 Unschedulable 已经变成了一个 Taints。也就是说需要校验一下 Pod 上打上的 Tolerates 是不是可以容忍这个 Taints。如果容忍了这个不可调度的taint,那么它也可以容忍 NodeSpec的不可调度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func (pl *NodeUnschedulable) Filter(ctx context.Context, _ *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
if nodeInfo == nil || nodeInfo.Node() == nil {
return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonUnknownCondition)
}
// If pod tolerate unschedulable taint, it's also tolerate `node.Spec.Unschedulable`.
podToleratesUnschedulable := v1helper.TolerationsTolerateTaint(pod.Spec.Tolerations, &v1.Taint{
Key: v1.TaintNodeUnschedulable,
Effect: v1.TaintEffectNoSchedule,
})
// TODO (k82cn): deprecates `node.Spec.Unschedulable` in 1.13.
if nodeInfo.Node().Spec.Unschedulable && !podToleratesUnschedulable {
return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonUnschedulable)
}
return nil
}

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPredicateConfigProducer(CheckNodeUnschedulablePred,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, nodeunschedulable.Name, nil)
return
})

CheckNodeLabelPresencePred

CheckNodeLablePresencePred策略主要用于检查指定的Label是否在Node上存在。这里检查的是两种情况:

  • 一种检查Node上面是否有指定Label。比如有时候通过 region/zone/racks 这种label来划分空间,想要把Pod调度到有特定region/zone/racks的Node。
  • 一种是检查Node上面是否没有指定的Label。比如有的Node被打上 retiring 的 label,想要制定Pod不调度到这些Node上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func (pl *NodeLabel) Filter(ctx context.Context, _ *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
node := nodeInfo.Node()
if node == nil {
return framework.NewStatus(framework.Error, "node not found")
}
nodeLabels := labels.Set(node.Labels)
check := func(labels []string, presence bool) bool {
for _, label := range labels {
exists := nodeLabels.Has(label)
if (exists && !presence) || (!exists && presence) {
return false
}
}
return true
}
if check(pl.args.PresentLabels, true) && check(pl.args.AbsentLabels, false) {
return nil
}

return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonPresenceViolated)
}

对这个策略,需要在注册的时候设定策略插件的参数。

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
7
8
9
registry.registerPredicateConfigProducer(CheckNodeLabelPresencePred,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, nodelabel.Name, nil)
if args.NodeLabelArgs != nil {
pluginConfig = append(pluginConfig,
config.PluginConfig{Name: nodelabel.Name, Args: args.NodeLabelArgs})
}
return
})

Pod 与 Pod 匹配相关

MatchInterPodAffinityPred

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
registry.registerPredicateConfigProducer(MatchInterPodAffinityPred,
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, interpodaffinity.Name, nil)
plugins.PreFilter = appendToPluginSet(plugins.PreFilter, interpodaffinity.Name, nil)
return
})
registry.registerPredicateConfigProducer(CheckNodeLabelPresencePred,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, nodelabel.Name, nil)
if args.NodeLabelArgs != nil {
pluginConfig = append(pluginConfig,
config.PluginConfig{Name: nodelabel.Name, Args: args.NodeLabelArgs})
}
return
})

Pod 服务打散相关

EvenPodsSpread

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
registry.registerPredicateConfigProducer(EvenPodsSpreadPred,
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.PreFilter = appendToPluginSet(plugins.PreFilter, podtopologyspread.Name, nil)
plugins.Filter = appendToPluginSet(plugins.Filter, podtopologyspread.Name, nil)
return
})

CheckServiceAffinity

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
7
8
9
10
registry.registerPredicateConfigProducer(CheckServiceAffinityPred,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Filter = appendToPluginSet(plugins.Filter, serviceaffinity.Name, nil)
if args.ServiceAffinityArgs != nil {
pluginConfig = append(pluginConfig,
config.PluginConfig{Name: serviceaffinity.Name, Args: args.ServiceAffinityArgs})
}
plugins.PreFilter = appendToPluginSet(plugins.PreFilter, serviceaffinity.Name, nil)
return
})

Priority

整体梳理

策略名称 策略算法 权重
BalancedResourceAllocation CPU和内存利用率越接近,得分越高 1
ImageLocalityPriority 待调度的Pod会使用一些镜像,拥有这些镜像越多的节点,得分越高 1
InterPodAffinityPriority Pod与Node上正运行的其他Pod亲和性匹配度越高,得分越高 1
LeastRequestedPriority 剩余资源越多,得分越高 1
NodeAffinityPriority Pod与Node亲和性匹配度越高,得分越高 1
NodePreferAvoidPodsPriority 该Node的annotation scheduler.alpha.kubernetes.io/preferAvoidPods被设置时,说明该Node不希望被调度,得分低。 10000
SelectorSpreadPriority 相同service/rc的Pods越分散,得分越高 1
TaintTolerationPriority Pod对Node的taint容忍度越高,得分越高 1
ServiceSpreadingPriority 相同Service的Pods越分散,得分越高,被 SelectorSpreadPriority取代,保留在系统中并不使用 1
EqualPriority 所有机器得分一样 1
MostRequestPriority Request资源越多,得分越高,与LeastRequestPriority相反 1
EvenPodsSpreadPriority 在1.18版本默认启动,用来指定一组符合条件的 Pod 在某个拓扑结构上的打散需求,这样是比较灵活、比较定制化的一种方式,使用起来也是比较复杂的一种方式 2
RequestedToCapacityRatioName 允许用户对于CPU、内存和扩展加速卡等资源实现bin packing
NodeLabel 主要是为了实现对某些特定 label 的 Node 优先分配,算法很简单,启动时候依据调度策略 (SchedulerPolicy)配置的 label 值,判断 Node 上是否满足这个label条件,如果满足条件的节点优先分配。
ServiceAffinity 是为了支持 Service 下的 Pod 的分布要按照 Node 的某个 label 的值进行均衡。

打分算法主要解决的问题就是集群的碎片、容灾、水位、亲和、反亲和等,可以分为以下四个大类。

资源水位

  • 资源水位公式的概念:Request:Node 已经分配的资源;Allocatable:Node 的可调度的资源。
  • 优先打散:把 Pod 分到资源空闲率最高的节点上,而非空闲资源最大的节点,公式:资源空闲率 = (Allocatable - Request) / Allocatable,当这个值越大,表示分数越高,优先分配到高分数的节点。其中 (Allocatable - Request) 表示为 Pod 分配到这个节点之后空闲的资源数。
  • 优先堆叠:把 Pod 分配到资源使用率最高的节点上,公式:资源使用率 = Request / Allocatable ,资源使用率越高,表示得分越高,会优先分配到高分数的节点。
  • 碎片率:是指 Node 上的多种资源之间的资源使用率的差值,目前支持 CPU/Mem/Disk 三类资源, 假如仅考虑 CPU/Mem,那么碎片率的公式 = Abs[CPU(Request / Allocatable) - Mem(Request / Allocatable)] 。举一个例子,当 CPU 的分配率是 99%,内存的分配率是 50%,那么碎片率 = 99% - 50% = 50%,那么这个例子中剩余 1% CPU, 50% Mem,很难有这类规格的容器能用完 Mem。得分 = 1 - 碎片率,碎片率越高得分低。
  • 指定比率:可以在 Scheduler 启动的时候,为每一个资源使用率设置得分,从而实现控制集群上 node 资源分配分布曲线。

LeastRequestedPriority

LeastRequestedPriority 策略对于那些使用率越低的Node的优先级越高。通过这种算法,可以使得各个节点的资源得到均衡利用。

计算公式如下:

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPriorityConfigProducer(LeastRequestedPriority,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Score = appendToPluginSet(plugins.Score, noderesources.LeastAllocatedName, &args.Weight)
return
})

MostRequestedPriority

MostRequestedPriority 策略对于那些使用率更高的Node的优先级更高。这种算法在动态伸缩集群环境比较适用,会优先调度pod到使用率最高的主机节点,这样在伸缩集群时,就会腾出空闲机器,从而进行停机处理。

其计算公式如下:

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPriorityConfigProducer(MostRequestedPriority,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Score = appendToPluginSet(plugins.Score, noderesources.MostAllocatedName, &args.Weight)
return
})

BalancedResourceAllocation

BalancedResourceAllocation:尽量选择在部署Pod后各项资源更均衡的机器。BalancedResourceAllocation不能单独使用,而且必须和LeastRequestedPriority同时使用,它分别计算主机上的cpu和memory的比重,主机的分值由cpu比重和memory比重的“距离”决定。

计算公式如下:

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPriorityConfigProducer(BalancedResourceAllocation,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Score = appendToPluginSet(plugins.Score, noderesources.BalancedAllocationName, &args.Weight)
return
})

RequestedToCapacityRatioPriority

RequestedToCapacityRatioPriority允许用户对于CPU、内存和扩展加速卡等资源实现bin packing。

所谓 Bin Packing ,又称装箱问题,是运筹学中的一个经典问题。问题的背景是,现有若干个小盒子,想要把它们装进有限个给定大小的箱子中,如何既能够装的多油装的快,使得尽可能每个箱子都装满,从而减少箱子的使用数目。BinPack问题有很多变种,当限制箱子的数目为1,每个盒子给定value和weight,binpack问题就变成了背包问题。

Kubernetes默认开启的资源调度策略是Spread的策略,资源尽量打散,但是会导致较多的资源碎片,使得整体资源利用率下降。通过RequestedToCapacityRatioPriority配置支持CPU、内存和GPU等扩展卡的权重,在打分阶段计算对应资源的利用率,通过利用率进行排序,优先打满一个节点后再向后调度,从而实现bin packing。

RequestedToCapacityRatioResourceAllocation 优先级函数的行为可以通过名为 requestedToCapacityRatioArguments 的配置选项进行控制。 该标志由两个参数 shaperesources 组成。 shape 允许用户根据 utilizationscore 值将函数调整为最少请求(least requested)或 最多请求(most requested)计算。 resources 由 nameweight 组成,name 指定评分时要考虑的资源,weight 指定每种资源的权重。

以下是一个配置示例,该配置将 requestedToCapacityRatioArguments 设置为对扩展资源 intel.com/foointel.com/bar 的装箱行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"kind" : "Policy",
"apiVersion" : "v1",
...
"priorities" : [
...
{
"name": "RequestedToCapacityRatioPriority",
"weight": 2,
"argument": {
"requestedToCapacityRatioArguments": {
"shape": [
{"utilization": 0, "score": 0},
{"utilization": 100, "score": 10}
],
"resources": [
{"name": "intel.com/foo", "weight": 3},
{"name": "intel.com/bar", "weight": 5}
]
}
}
}
],
}

实际上,这里的shape参数定义的是不同utilization下对应的得分,是对 LeastRequestedPriorityMostRequestedPriority 的进一步抽象。

这种配置对应的是LeastRequestedPriority

1
2
{"utilization": 0, "score": 10},
{"utilization": 100, "score": 0}

这种配置对应的是MostRequestedPriority

1
2
{"utilization": 0, "score": 0},
{"utilization": 100, "score": 10}

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
7
8
9
registry.registerPriorityConfigProducer(noderesources.RequestedToCapacityRatioName,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Score = appendToPluginSet(plugins.Score, noderesources.RequestedToCapacityRatioName, &args.Weight)
if args.RequestedToCapacityRatioArgs != nil {
pluginConfig = append(pluginConfig,
config.PluginConfig{Name: noderesources.RequestedToCapacityRatioName, Args: args.RequestedToCapacityRatioArgs})
}
return
})

Pod 打散

Pod打散目的是支持符合条件的一组 Pod 在不同 topology 上部署的 spread 需求。

ServiceSpreadingPriority

ServiceSpreadingPriority:官方注释上说大概率会用来替换 SelectorSpreadPriority,为什么呢?我个人理解:Service 代表一组服务,我们只要能做到服务的打散分配就足够了。

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
registry.registerPredicateConfigProducer(EvenPodsSpreadPred,
func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.PreFilter = appendToPluginSet(plugins.PreFilter, podtopologyspread.Name, nil)
plugins.Filter = appendToPluginSet(plugins.Filter, podtopologyspread.Name, nil)
return
})

EvenPodsSpread

EvenPodsSpreadPriority:用来指定一组符合条件的 Pod 在某个拓扑结构上的打散需求,这样是比较灵活、比较定制化的一种方式,使用起来也是比较复杂的一种方式。因为这个使用方式可能会一直变化,我们假设这个拓扑结构是这样的:Spec 是要求在 node 上进行分布的,我们就可以按照上图中的计算公式,计算一下在这个 node 上满足 Spec 指定 labelSelector 条件的 pod 数量,然后计算一下最大的差值,接着计算一下 Node 分配的权重,如果说这个值越大,表示这个值越优先。

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
registry.registerPriorityConfigProducer(EvenPodsSpreadPriority,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.PreScore = appendToPluginSet(plugins.PreScore, podtopologyspread.Name, nil)
plugins.Score = appendToPluginSet(plugins.Score, podtopologyspread.Name, &args.Weight)
return
})

CheckServiceAffinity

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
7
8
9
10
11
12
13
registry.registerPriorityConfigProducer(serviceaffinity.Name,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
// If there are n ServiceAffinity priorities in the policy, the weight for the corresponding
// score plugin is n*weight (note that the validation logic verifies that all ServiceAffinity
// priorities specified in Policy have the same weight).
weight := args.Weight * int32(len(args.ServiceAffinityArgs.AntiAffinityLabelsPreference))
plugins.Score = appendToPluginSet(plugins.Score, serviceaffinity.Name, &weight)
if args.ServiceAffinityArgs != nil {
pluginConfig = append(pluginConfig,
config.PluginConfig{Name: serviceaffinity.Name, Args: args.ServiceAffinityArgs})
}
return
})

SelectorSpreadPriority

SelectorSpreadPriority:用于实现 Pod 所属的 Controller 下所有的 Pod 在 Node 上打散的要求。实现方式是这样的:它会依据待分配的 Pod 所属的 controller,计算该 controller 下的所有 Pod,假设总数为 T,对这些 Pod 按照所在的 Node 分组统计;假设为 N (表示为某个 Node 上的统计值),那么对 Node上的分数统计为 (T-N)/T 的分数,值越大表示这个节点的 controller 部署的越少,分数越高,从而达到 workload 的 pod 打散需求。

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
registry.registerPriorityConfigProducer(SelectorSpreadPriority,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Score = appendToPluginSet(plugins.Score, selectorspread.Name, &args.Weight)
plugins.PreScore = appendToPluginSet(plugins.PreScore, selectorspread.Name, nil)
return
})

Pod 亲和/反亲和

InterPodAffinityPriority

InterPodAffinityPriority:先介绍一下使用场景:第一个例子,比如说应用 A 提供数据,应用 B 提供服务,A 和 B 部署在一起可以走本地网络,优化网络传输;第二个例子,如果应用 A 和应用 B 之间都是 CPU 密集型应用,而且证明它们之间是会互相干扰的,那么可以通过这个规则设置尽量让它们不在一个节点上。pod亲和性选择策略,类似NodeAffinityPriority,提供两种选择器支持:requiredDuringSchedulingIgnoredDuringExecution(保证所选的主机必须满足所有Pod对主机的规则要求)、preferresDuringSchedulingIgnoredDuringExecution(调度器会尽量但不保证满足NodeSelector的所有要求),两个子策略:podAffinity和podAntiAffinity

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
7
8
9
10
registry.registerPriorityConfigProducer(InterPodAffinityPriority,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.PreScore = appendToPluginSet(plugins.PreScore, interpodaffinity.Name, nil)
plugins.Score = appendToPluginSet(plugins.Score, interpodaffinity.Name, &args.Weight)
if args.InterPodAffinityArgs != nil {
pluginConfig = append(pluginConfig,
config.PluginConfig{Name: interpodaffinity.Name, Args: args.InterPodAffinityArgs})
}
return
})

Node 亲和/反亲和

  • NodeAffinityPriority,这个是为了满足 Pod 和 Node 的亲和 & 反亲和;
  • ServiceAntiAffinity,是为了支持 Service 下的 Pod 的分布要按照 Node 的某个 label 的值进行均衡。比如:集群的节点有云上也有云下两组节点,我们要求服务在云上云下均衡去分布,假设 Node 上有某个 label,那我们就可以用这个 ServiceAntiAffinity 进行打散分布;
  • NodeLabelPrioritizer,主要是为了实现对某些特定 label 的 Node 优先分配,算法很简单,启动时候依据调度策略 (SchedulerPolicy)配置的 label 值,判断 Node 上是否满足这个label条件,如果满足条件的节点优先分配;
  • ImageLocalityPriority,节点亲和主要考虑的是镜像下载的速度。如果节点里面存在镜像的话,优先把 Pod 调度到这个节点上,这里还会去考虑镜像的大小,比如这个 Pod 有好几个镜像,镜像越大下载速度越慢,它会按照节点上已经存在的镜像大小优先级亲和。

NodePreferAvoidPodsPriority

NodePreferAvoidPodsPriority策略用于实现某些 controller 尽量不分配到某些节点上的能力;通过在 node 上加 annotation 声明哪些 controller 不要分配到 Node 上,如果不满足就优先。

具体实现就是会在Node上加上Annotation,形如这种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
annotations1 := map[string]string{
v1.PreferAvoidPodsAnnotationKey: `
{
"preferAvoidPods": [
{
"podSignature": {
"podController": {
"apiVersion": "v1",
"kind": "ReplicationController",
"name": "foo",
"uid": "abcdef123456",
"controller": true
}
},
"reason": "some reason",
"message": "some message"
}
]
}`,
}

在检查的时候,对于那些不被 ReplicaSetReplicationController 拥有的 Pod,直接跳过,给予最高分。如果和 annotation 中标记的相同,那么给予最低分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func (pl *NodePreferAvoidPods) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
if err != nil {
return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
}

node := nodeInfo.Node()
if node == nil {
return 0, framework.NewStatus(framework.Error, "node not found")
}

controllerRef := metav1.GetControllerOf(pod)
if controllerRef != nil {
// Ignore pods that are owned by other controller than ReplicationController
// or ReplicaSet.
if controllerRef.Kind != "ReplicationController" && controllerRef.Kind != "ReplicaSet" {
controllerRef = nil
}
}
if controllerRef == nil {
return framework.MaxNodeScore, nil
}

avoids, err := v1helper.GetAvoidPodsFromNodeAnnotations(node.Annotations)
if err != nil {
// If we cannot get annotation, assume it's schedulable there.
return framework.MaxNodeScore, nil
}
for i := range avoids.PreferAvoidPods {
avoid := &avoids.PreferAvoidPods[i]
if avoid.PodSignature.PodController.Kind == controllerRef.Kind && avoid.PodSignature.PodController.UID == controllerRef.UID {
return 0, nil
}
}
return framework.MaxNodeScore, nil
}

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPriorityConfigProducer(NodePreferAvoidPodsPriority,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Score = appendToPluginSet(plugins.Score, nodepreferavoidpods.Name, &args.Weight)
return
})

NodeAffinityPriority

NodeAffinityPriority策略用于满足Pod与Node之间的亲和与反亲和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
affinity := pod.Spec.Affinity

var count int64
// A nil element of PreferredDuringSchedulingIgnoredDuringExecution matches no objects.
// An element of PreferredDuringSchedulingIgnoredDuringExecution that refers to an
// empty PreferredSchedulingTerm matches all objects.
if affinity != nil && affinity.NodeAffinity != nil && affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil {
// Match PreferredDuringSchedulingIgnoredDuringExecution term by term.
for i := range affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
preferredSchedulingTerm := &affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[i]
if preferredSchedulingTerm.Weight == 0 {
continue
}

// TODO: Avoid computing it for all nodes if this becomes a performance problem.
nodeSelector, err := v1helper.NodeSelectorRequirementsAsSelector(preferredSchedulingTerm.Preference.MatchExpressions)
if err != nil {
return 0, framework.NewStatus(framework.Error, err.Error())
}

if nodeSelector.Matches(labels.Set(node.Labels)) {
count += int64(preferredSchedulingTerm.Weight)
}
}
}

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPriorityConfigProducer(NodeAffinityPriority,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Score = appendToPluginSet(plugins.Score, nodeaffinity.Name, &args.Weight)
return
})

TaintTolerationPriority

TaintTolerationPriority 策略,Pod 对 Node 的 taint 容忍程度越高,优先级越大。

PreScore 阶段,拿到所有 all Tolerations with Effect PreferNoSchedule or with no effect,并将其写到cycleState。

1
2
3
4
5
6
7
8
9
10
func (pl *TaintToleration) PreScore(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodes []*v1.Node) *framework.Status {
if len(nodes) == 0 {
return nil
}
tolerationsPreferNoSchedule := getAllTolerationPreferNoSchedule(pod.Spec.Tolerations)
state := &preScoreState{
tolerationsPreferNoSchedule: tolerationsPreferNoSchedule,
}
cycleState.Write(preScoreStateKey, state)
return nil

Score 阶段,具体算法就是Pod不能容忍的taint越多,那么得分就越高(之后会在Normalize处正则化,将得分逆序),也就是其优先级越低。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func (pl *TaintToleration) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
if err != nil || nodeInfo.Node() == nil {
return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
}
node := nodeInfo.Node()

s, err := getPreScoreState(state)
if err != nil {
return 0, framework.NewStatus(framework.Error, err.Error())
}

score := int64(countIntolerableTaintsPreferNoSchedule(node.Spec.Taints, s.tolerationsPreferNoSchedule))
return score, nil
}

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
registry.registerPriorityConfigProducer(TaintTolerationPriority,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.PreScore = appendToPluginSet(plugins.PreScore, tainttoleration.Name, nil)
plugins.Score = appendToPluginSet(plugins.Score, tainttoleration.Name, &args.Weight)
return
})

ImageLocalityPriority

ImageLocalityPriority策略主要考虑的是镜像下载的速度。如果节点里面存在镜像的话,优先把 Pod 调度到这个节点上,这里还会去考虑镜像的大小,比如这个 Pod 有好几个镜像,镜像越大下载速度越慢,它会按照节点上已经存在的镜像大小优先级亲和。

1
2
3
4
5
6
7
8
9
func sumImageScores(nodeInfo *framework.NodeInfo, containers []v1.Container, totalNumNodes int) int64 {
var sum int64
for _, container := range containers {
if state, ok := nodeInfo.ImageStates[normalizedImageName(container.Image)]; ok {
sum += scaledImageScore(state, totalNumNodes)
}
}
return sum
}

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
registry.registerPriorityConfigProducer(ImageLocalityPriority,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
plugins.Score = appendToPluginSet(plugins.Score, imagelocality.Name, &args.Weight)
return
})

NodeLabel

NodeLabel策略主要是为了实现对某些特定 label 的 Node 优先分配,算法很简单,启动时候依据调度策略 (SchedulerPolicy)配置的 label 值,判断 Node 上是否满足这个label条件,如果满足条件的节点优先分配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func (pl *NodeLabel) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
if err != nil || nodeInfo.Node() == nil {
return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v, node is nil: %v", nodeName, err, nodeInfo.Node() == nil))
}

node := nodeInfo.Node()
score := int64(0)
for _, label := range pl.args.PresentLabelsPreference {
if labels.Set(node.Labels).Has(label) {
score += framework.MaxNodeScore
}
}
for _, label := range pl.args.AbsentLabelsPreference {
if !labels.Set(node.Labels).Has(label) {
score += framework.MaxNodeScore
}
}
// Take average score for each label to ensure the score doesn't exceed MaxNodeScore.
score /= int64(len(pl.args.PresentLabelsPreference) + len(pl.args.AbsentLabelsPreference))

return score, nil
}

算法注册逻辑:

pkg/scheduler/framework/plugins/legacy_registry.go
1
2
3
4
5
6
7
8
9
10
11
12
13
registry.registerPriorityConfigProducer(nodelabel.Name,
func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) {
// If there are n LabelPreference priorities in the policy, the weight for the corresponding
// score plugin is n*weight (note that the validation logic verifies that all LabelPreference
// priorities specified in Policy have the same weight).
weight := args.Weight * int32(len(args.NodeLabelArgs.PresentLabelsPreference)+len(args.NodeLabelArgs.AbsentLabelsPreference))
plugins.Score = appendToPluginSet(plugins.Score, nodelabel.Name, &weight)
if args.NodeLabelArgs != nil {
pluginConfig = append(pluginConfig,
config.PluginConfig{Name: nodelabel.Name, Args: args.NodeLabelArgs})
}
return
})