EvenChan's Ops.

Kubernetes的调度均衡器Descheduler

字数统计: 3k阅读时长: 12 min
2021/02/08

马上春节了,在这祝大家春节快乐。前阵子在使用K8S集群的时候,经常遇到Pod调度不均衡的问题,排查了许久也没找到所以然,最后人为的归结到Scheduler调度不够智能上(机智如我)。这不,为了使集群能够更均衡,充分利用节点,特地研究了一下Descheduler,下面内容主要来自官方文档,喜欢看官方文档的可以划到结尾处,喜欢看中文的,可以收藏慢慢看,对长期从事K8S集群管理的YAML工程师来说,还是有点用。


在Kubernetes中,kube-scheduler负责将Pod调度到合适的Node上,但是Kubernetes是一个非常动态的,高度弹性的环境,有时候会造成某一个或多个节点pod数分配不均,比如:

  • 一些节点利用率低下或过度使用
  • 添加删除标签或添加删除污点,pod或Node亲和性改变等造成原调度不再满足
  • 一些节点故障,其上运行的Pod调度到其他节点
  • 新节点加入集群

由于以上种种原因,可能导致多个Pod运行到不太理想的节点,而整个K8S集群也会处于一段时间不均衡的状态,这时候就需要重新平衡集群。Descheduler就是这样一个项目。

Descheduler

Descheduler可以根据一些规则配置来重新平衡集群状态,目前支持的策略有:

  • RemoveDuplicates
  • LowNodeUtilization
  • RemovePodsViolatingInterPodAntiAffinity
  • RemovePodsViolatingNodeAffinity
  • RemovePodsViolatingNodeTaints
  • RemovePodsViolatingTopologySpreadConstraint
  • RemovePodsHavingTooManyRestarts
  • PodLifeTime

这些策略可以启用,也可以关闭,默认情况下,所有策略都是启动的。

另外,还有一些通用配置,如下:

  • nodeSelector:限制要处理的节点
  • evictLocalStoragePods: 驱逐使用LocalStorage的Pods
  • ignorePvcPods: 是否忽略配置PVC的Pods,默认是False
  • maxNoOfPodsToEvictPerNode:节点允许的最大驱逐Pods数

比如:

1
2
3
4
5
6
7
8
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
nodeSelector: prod=dev
evictLocalStoragePods: true
maxNoOfPodsToEvictPerNode: 40
ignorePvcPods: false
strategies:
...

RemoveDuplicates

该策略确保只有一个Pod与在同一节点上运行的副本集(RS),Replication Controller(RC),Deployment或Job相关联。如果有更多的Pod,则将这些重复的Pod逐出,以更好地在群集中扩展。如果某些节点由于任何原因而崩溃,并且它们上的Pod移至其他节点,导致多个与RS或RC关联的Pod(例如,在同一节点上运行),则可能发生此问题。一旦发生故障的节点再次准备就绪,便可以启用此策略以驱逐这些重复的Pod。

参数列表有:

参数名 类型
excludeOwnerKinds list(string)
namespaces list(string)
thresholdPriority int
thresholdPriorityClassName string

其中excludeOwnerKinds用于排除类型,这些类型下的Pod则不会被驱逐,比如:

1
2
3
4
5
6
7
8
9
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
"RemoveDuplicates":
enabled: true
params:
removeDuplicates:
excludeOwnerKinds:
- "ReplicaSet"

LowNodeUtilization

该策略主要是找到那些未充分利用的节点,将驱逐的Pod在这些节点上创建,该策略配置在nodeResourceUtilizationThresholds下。

节点利用率低是由配置阈值决定的,配置在thresholds下,thresholds可以配置cpu、memory以及pods数量(百分比),如果节点利用率低于配置,则代表该节点未被充分利用。目前,Pod的请求资源需求被考虑用于计算节点资源利用率。

另外一个参数targetThresholds,用于计算可能驱逐Pods的潜在节点,该参数也是配置cpu、memory以及Pods数量的百分比。如果超过该配置,则表示该节点被过度利用,上面的Pods就可能被驱逐。而在thresholdstargetThresholds之间的任何节点则认为是正常利用。

参数有:

参数名 类型
thresholds map
targetThresholds map
numberOfNodes int
thresholdPriority int
thresholdPriorityClassName string

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
"LowNodeUtilization":
enabled: true
params:
nodeResourceUtilizationThresholds:
thresholds:
"cpu" : 20
"memory": 20
"pods": 20
targetThresholds:
"cpu" : 50
"memory": 50
"pods": 50

需要注意的是:

  • 仅支持以下三种资源类型

    • cpu
    • memory
    • pods
  • thresholds和targetThresholds必须配置相同的类型

  • 参数值的访问是0-100(百分制)

  • 相同的资源类型,thresholds的配置不能高于targetThresholds的配置

如果未指定任何资源类型,则默认是100%,以避免节点从未充分利用变为过度利用。

与LowNodeUtilization策略关联的另一个参数称为numberOfNodes。仅当未充分利用的节点数大于配置的值时,才可以配置此参数以激活策略。这在大型群集中很有用,其中一些节点可能会频繁使用或短期使用不足。默认情况下,numberOfNodes设置为零。

RemovePodsViolatingInterPodAntiAffinity

该策略可确保从节点中删除违反Interpod反亲和关系的pod。例如,如果某个节点上有podA,并且podBpodC(在同一节点上运行)具有禁止它们在同一节点上运行的反亲和规则,则podA将被从该节点逐出,以便podBpodC正常运行。当 podBpodC已经运行在节点上后,反亲和性规则被创建就会发送这样的问题。

参数为:

参数名 类型
thresholdPriority int
thresholdPriorityClassName string
namespaces list(string)

如下开启该策略:

1
2
3
4
5
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
"RemovePodsViolatingInterPodAntiAffinity":
enabled: true

RemovePodsViolatingNodeAffinity

启用后,该策略requiredDuringSchedulingRequiredDuringExecution将用作kubelet 的临时实现并逐出该kubelet,不再考虑节点亲和力。

例如,在nodeA上调度了podA,该podA满足了调度时的节点亲缘性规则requiredDuringSchedulingIgnoredDuringExecution。随着时间的流逝,nodeA停止满足该规则。当执行该策略并且有另一个可用的节点满足该节点相似性规则时,podA被从nodeA中逐出。

参数有:

参数名 类型
thresholdPriority int
thresholdPriorityClassName string
namspaces list(string)
nodeAffinityType list(string)

例如:

1
2
3
4
5
6
7
8
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
"RemovePodsViolatingNodeAffinity":
enabled: true
params:
nodeAffinityType:
- "requiredDuringSchedulingIgnoredDuringExecution"

RemovePodsViolatingNodeTaints

该策略可以确保从节点中删除违反 NoSchedule 污点的 Pod。例如,有一个名为 podAPod,通过配置容忍 key=value:NoSchedule 允许被调度到有该污点配置的节点上,如果节点的污点随后被更新或者删除了,则污点将不再被 Pod 的容忍满足,然后将被驱逐。

参数:

参数名 类型
thresholdPriority int
thresholdPriorityClassName string
namespaces list(string)

例如:

1
2
3
4
5
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
"RemovePodsViolatingNodeTaints":
enabled: true

RemovePodsViolatingTopologySpreadConstraint

该策略确保从节点驱逐违反拓扑扩展约束的Pods,具体来说,它试图驱逐将拓扑域平衡到每个约束的maxSkew内所需的最小pod数,不过次策略需要k8s版本高于1.18才能使用。

默认情况下,此策略仅处理硬约束,如果将参数includeSoftConstraints 设置为True,也将支持软约束。

参数为:

参数名 类型
thresholdPriority int
thresholdPriorityClassName string
namespaces list(string)
includeSoftConstraints bool

例如:

1
2
3
4
5
6
7
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
"RemovePodsViolatingTopologySpreadConstraint":
enabled: true
params:
includeSoftConstraints: false

RemovePodsHavingTooManyRestarts

该策略确保从节点中删除重启次数过多的Pods,例如,具有EBS / PD的Pod无法将卷/磁盘附加到实例,则应该将该Pod重新安排到其他节点。它的参数包括podRestartThreshold(这是应将Pod逐出的重新启动次数),以及包括InitContainers,它确定在计算中是否应考虑初始化容器的重新启动。

参数为:

参数名 类型
podRestartThreshold int
includingInitContainers bool
thresholdPriority int
thresholdPriorityClassName string
namespaces list(string)

例如:

1
2
3
4
5
6
7
8
9
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
"RemovePodsHavingTooManyRestarts":
enabled: true
params:
podsHavingTooManyRestarts:
podRestartThreshold: 100
includingInitContainers: true

PodLifeTime

该策略用于驱逐比maxPodLifeTimeSeconds更旧的Pods,可以通过podStatusPhases来配置哪类状态的Pods会被驱逐。

参数有:

参数名 类型
maxPodLifeTimeSeconds int
podStatusPhases list(string)
thresholdPriority int (see priority filtering)
thresholdPriorityClassName string (see priority filtering)
namespaces (see namespace filtering)

例如:

1
2
3
4
5
6
7
8
9
10
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
"PodLifeTime":
enabled: true
params:
podLifeTime:
maxPodLifeTimeSeconds: 86400
podStatusPhases:
- "Pending"

Filter Pods

在驱逐Pods的时候,有时并不需要所有Pods都被驱逐,Descheduler提供一些过滤方式。

Namespace filtering

该策略可以配置是包含还是排除某些名称空间。可以使用该策略的有:

  • PodLifeTime
  • RemovePodsHavingTooManyRestarts
  • RemovePodsViolatingNodeTaints
  • RemovePodsViolatingNodeAffinity
  • RemovePodsViolatingInterPodAntiAffinity
  • RemoveDuplicates
  • RemovePodsViolatingTopologySpreadConstraint

(1)只驱逐某些命令空间下的Pods,使用include参数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
"PodLifeTime":
enabled: true
params:
podLifeTime:
maxPodLifeTimeSeconds: 86400
namespaces:
include:
- "namespace1"
- "namespace2"

(2)排除掉某些命令空间下的Pods,使用exclude参数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
"PodLifeTime":
enabled: true
params:
podLifeTime:
maxPodLifeTimeSeconds: 86400
namespaces:
exclude:
- "namespace1"
- "namespace2"

Priority filtering

所有策略都可以配置优先级阈值,只有在该阈值以下的Pod才能被驱逐。您可以通过设置thresholdPriorityClassName(将阈值设置为给定优先级类别的值)或thresholdPriority(直接设置阈值)参数来指定此阈值。默认情况下,此阈值设置为系统集群关键优先级类别的值。

例如:

(1)使用thresholdPriority

1
2
3
4
5
6
7
8
9
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
"PodLifeTime":
enabled: true
params:
podLifeTime:
maxPodLifeTimeSeconds: 86400
thresholdPriority: 10000

(2)使用thresholdPriorityClassName

1
2
3
4
5
6
7
8
9
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
"PodLifeTime":
enabled: true
params:
podLifeTime:
maxPodLifeTimeSeconds: 86400
thresholdPriorityClassName: "priorityclass1"

注意:不能同时配置thresholdPriority和thresholdPriorityClassName,如果给定的优先级类不存在,则descheduler不会创建它,并且会引发错误。



Pod Evictions

当使用descheduler驱除Pods的时候,需要注意以下几点:

  • 关键性Pod不会为驱逐,比如priorityClassName设置为system-cluster-criticalsystem-node-critical的Pod
  • 不属于RC、RS、Deployment或Job管理的Pod不会被驱逐
  • DS创建的Pods不会被驱逐
  • 使用LocalStorage的Pod不会被驱逐,设置evictLocalStoragePods: true除外
  • 除非设置ignorePvcPods: true,否则具有PVC的Pods会被驱逐
  • LowNodeUtilizationRemovePodsViolatingInterPodAntiAffinity策略下,Pods按优先级从低到高进行驱逐,如果优先级相同,Besteffort类型的Pod要先于Burstable和Guaranteed类型被驱逐
  • annotations中带有descheduler.alpha.kubernetes.io/evict字段的Pod都可以被驱逐,该注释用于覆盖阻止驱逐的检查,用户可以选择驱逐哪个Pods

如果Pods驱逐失败,可以设置--v=4或者从Descheduler日志中查找原因。

如果驱逐违反PDB约束,则不会驱逐这类Pods。

版本兼容

Descheduler Supported Kubernetes Version
v0.20 v1.20
v0.19 v1.19
v0.18 v1.18
v0.10 v1.17
v0.4-v0.9 v1.9+
v0.1-v0.3 v1.7-v1.8

实践

K8S集群版本:v1.18.9

(1)下载对应版本的Descheduler

1
$ wget https://github.com/kubernetes-sigs/descheduler/archive/v0.18.0.tar.gz

(2)创建RBAC

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
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: descheduler-cluster-role
namespace: kube-system
rules:
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list", "delete"]
- apiGroups: [""]
resources: ["pods/eviction"]
verbs: ["create"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: descheduler-sa
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: descheduler-cluster-role-binding
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: descheduler-cluster-role
subjects:
- name: descheduler-sa
kind: ServiceAccount
namespace: kube-system

(3)创建ConfigMap,该配置文件主要配置驱逐策略,如下:

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
---
apiVersion: v1
kind: ConfigMap
metadata:
name: descheduler-policy-configmap
namespace: kube-system
data:
policy.yaml: |
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
"RemoveDuplicates":
enabled: true
"RemovePodsViolatingInterPodAntiAffinity":
enabled: true
"LowNodeUtilization":
enabled: true
params:
nodeResourceUtilizationThresholds:
thresholds:
"cpu" : 20
"memory": 20
"pods": 20
targetThresholds:
"cpu" : 50
"memory": 50
"pods": 50

(4)使用Job来进行驱逐,配置文件如下:

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
---
apiVersion: batch/v1
kind: Job
metadata:
name: descheduler-job
namespace: kube-system
spec:
parallelism: 1
completions: 1
template:
metadata:
name: descheduler-pod
spec:
priorityClassName: system-cluster-critical
containers:
- name: descheduler
image: us.gcr.io/k8s-artifacts-prod/descheduler/descheduler:v0.10.0
volumeMounts:
- mountPath: /policy-dir
name: policy-volume
command:
- "/bin/descheduler"
args:
- "--policy-config-file"
- "/policy-dir/policy.yaml"
- "--v"
- "3"
restartPolicy: "Never"
serviceAccountName: descheduler-sa
volumes:
- name: policy-volume
configMap:
name: descheduler-policy-configmap

(5)如果需要配置定时任务进行驱逐,则使用CronJob,如下:

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
---
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: descheduler-cronjob
namespace: kube-system
spec:
schedule: "*/2 * * * *"
concurrencyPolicy: "Forbid"
jobTemplate:
spec:
template:
metadata:
name: descheduler-pod
spec:
priorityClassName: system-cluster-critical
containers:
- name: descheduler
image: us.gcr.io/k8s-artifacts-prod/descheduler/descheduler:v0.10.0
volumeMounts:
- mountPath: /policy-dir
name: policy-volume
command:
- "/bin/descheduler"
args:
- "--policy-config-file"
- "/policy-dir/policy.yaml"
- "--v"
- "3"
restartPolicy: "Never"
serviceAccountName: descheduler-sa
volumes:
- name: policy-volume
configMap:
name: descheduler-policy-configmap

参考:https://github.com/kubernetes-sigs/descheduler

CATALOG
  1. 1. Descheduler
    1. 1.1. RemoveDuplicates
    2. 1.2. LowNodeUtilization
    3. 1.3. RemovePodsViolatingInterPodAntiAffinity
    4. 1.4. RemovePodsViolatingNodeAffinity
    5. 1.5. RemovePodsViolatingNodeTaints
    6. 1.6. RemovePodsViolatingTopologySpreadConstraint
    7. 1.7. RemovePodsHavingTooManyRestarts
    8. 1.8. PodLifeTime
  2. 2. Filter Pods
    1. 2.1. Namespace filtering
    2. 2.2. Priority filtering
  3. 3. Pod Evictions
  4. 4. 版本兼容
  5. 5. 实践