0%

k8s 中的调度器 -- 亲和性、污点和容忍性

关于 Kubernetes 系列其他文章的传送门

  1. 《 部署 Kubernetes 也能如此简单吗? 》

  2. 《 初识 k8s,新时代的宠儿 》

  3. 《 深度学习 Pod 》

  4. 《 学会这 5 种 Pod 控制器,搞定发布、更新、回滚,从此告别人肉运维!! 》

  5. 《 k8s 中的网络入口 – Service 资源管理 》

  6. 《 k8s 中的 7 层调度 – Ingress 》

  7. 《 k8s 中的存储系统 – Volumes 》

  8. 《 k8s 中强大的配置中心 – ConfigMap 》

  9. 《 k8s 中的部署神器 – Statefulset 控制器 》

  10. 《 k8s 中的安全守护神 – 你不知道不代表它不在 》

  11. 《 k8s 中的图形接口 – Dashboard 》

  12. 《 k8s 中的网络插件 – flannel 和 Calico 》

  13. 《 k8s 中的调度器 – 亲和性、污点和容忍性 》     您当前所在位置

  14. 《 k8s 中对资源的监控 – 资源指标、Prometheus、弹性伸缩器 HPA 》

  15. 《 k8s 中的云原生应用管理利器 – Helm 》

  16. 《 理解 k8s 高可用,让你的集群稳如泰山 》



logo

一、k8s 中的调度器

k8s 中调度器也是插件化的,可以指定 Pod 使用哪种调度器,默认不指则使用 kube-scheduler 中默认的 default-scheduler 规则。

kube-scheduler 调度器的默认调度逻辑分为 3 个阶段:

  • 预选(predicate)阶段,通过一个或一组预选器,挑选出能够符合 Pod 运行环境的节点。
  • 优选(priority)阶段,把第一阶段挑选出的节点,逐个代入一组优选函数,而后一项一项进行评分并计算出每个节点的总评分,并排序。如果最优的节点分数相同,则进行下一步;
  • 随机(select)阶段,从最优得分相同的 Node 中随机选出一个运行 Pod;

二、高级调度机制

自己也可以在外部施加各种条件,来影响调度结果,有两种高级调度机制:

  • Node Affinity:节点亲和度。用于定义 Pod 对 Node 的倾向性。
    • requiredDuringSchedulingIgnoredDuringExecution:硬限制,即必须满足亲和性;
      • nodeSelectorTerms:固定字段
        • matchExpressions:定义匹配规则;
    • preferredDuringSchedulingIgnoredDuringExecution:软限制,即最好满足亲和性;
      • weight:定义权重;
      • preference:固定字段;
        • matchExpressions:定义匹配规则;
    • 后缀中的 IgnoredDuringExecution 意思是指:当调度完成后,即使节点标签发生改变而不再符合调度条件时,也不会影响已经调度完成的结果,这类似于 nodeSelector 的工作逻辑。
  • Pod Affinity:Pod 亲和度。用于定义 Pod 和 Pod 之间是否运行在一起。

三、Node Affinity 的配置方式

  • 当同时指定了 NodeSelectorNode Affinity,两者之间存在“与”关系,即符合条件的 Node 需要同时满足两个条件;
  • nodeAffinity 中,当 nodeSelectorTerms 中同时指定多个 matchExpressions 时,各条目间取“或”关系,即满足其中之一即可;
  • nodeSelectorTerms 中,当同一个 matchExpressions 中的多个 key 值之间取逻辑“与”关系,即 Node 需要同时满足多个条件;
  • 定义软限制时,使用 weight 可以定义权重,并加入优选值的计算总和;

配置“硬亲和”的示例 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-> # vim required-nodeAffinity-pod.yaml 

apiVersion: v1
kind: Pod
metadata:
name: with-required-nodeaffinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo"]} # 定义了节点必须有 zone 标签且值是 foo 的硬亲和性
containers:
- name: myapp
image: ikubernetes/myapp:v1

配置“硬亲和”的示例 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-> # vim required-nodeAffinity-pod2.yaml

apiVersion: v1
kind: Pod
metadata:
name: with-required-nodeaffinity-2
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo", "bar"]} # 在一个 matchExpressions 中定义的两个 Key 之间为“与”关系
- {key: ssd, operator: Exists, values: []}
containers:
- name: myapp
image: ikubernetes/myapp:v1

配置“硬亲和”的示例 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-> # vim required-nodeAffinity-pod2.yaml

apiVersion: v1
kind: Pod
metadata:
name: with-required-nodeaffinity-2
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions: # 而像这样有两个 matchExpressions,两者之间则为“或”关系
- {key: zone, operator: In, values: ["foo", "bar"]}
- matchExpressions:
- {key: ssd, operator: Exists, values: []}
containers:
- name: myapp
image: ikubernetes/myapp:v1

配置“软亲和”的示例 1

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
-> # vim deploy-with-preferred-nodeAffinity.yaml 

apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy-with-node-affinity
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp-pod
labels:
app: myapp
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 60
preference:
matchExpressions:
- {key: zone, operator: In, values: ["foo"]}
- weight: 30
preference:
matchExpressions:
- {key: ssd, operator: Exists, values: []}
containers:
- name: myapp
image: ikubernetes/myapp:v1

四、Pod Affinity 的配置方式

定义多个 Pod 间需要运行于同一位置的倾向性,或者不允许运行于同一位置的倾向性。

两个节点是否处于同一位置,取决于 topologyKey 的键值。

Pod 亲和性调度的限制:

  • 每次调度 Pod 时,要遍历所有的 Pod 以检测亲和性,因此在集群规模较大时,不建议使用此种调度方式。或不应在较小的颗粒度上定义 Pod 亲和性,而可以使用 Node Affinity 进行调度,并通过为 Node 定义标签达到一样的效果。
  • Pod anti-affinity(反亲和)要求集群中的每个节点都针对 topologyKey 设置了正确的标签,如果某些或所有节点未设置相应的标签,则可能会导致无法预料的后果。这就要求集群的每个节点都有定义好标签,并在缩容或扩容时规划好每台标签的管理,这在规模较大的集群中不易实现。

Pod “硬-亲和性”示例

这里有 Node1、2、3 台节点,先为 1、2 打上相同的标签划分为一个 zone=foo,Node3 为另一个 zone=bar

1
2
3
-> # kubectl label nodes node1.monster.com zone=foo
-> # kubectl label nodes node2.monster.com zone=foo
-> # kubectl label nodes node3.monster.com zone=bar # 与 1、2 不同

创建一个 Redis Pod,该 Pod 的会自动生值为 app=redis 的标签:

1
2
3
4
5
-> # kubectl create deployment redis --image=redis:4-alpine

-> # kubectl get pods redis-699d7df68d-sqt78 --show-labels
NAME READY STATUS RESTARTS AGE LABELS
redis-699d7df68d-sqt78 1/1 Running 0 34m app=redis,pod-template-hash=699d7df68d

创建 Pod 控制器,并定义了此 Deployment 控制器下的 3 个 Pod,在以 zone 标签为范围的拓扑结构区域中,与标签键为 app,值为 db 的 Pod 运行在同一个 zone 中。

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
-> # vim deploy-with-required-podAffinity.yaml 

apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-with-pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
affinity:
podAffinity: # 定义使用 Pod 亲和
requiredDuringSchedulingIgnoredDuringExecution: # 使用了硬亲和
- labelSelector:
matchExpressions: # 找到标签键为 app,值为 redis 的 Pod,与之保持亲和
- {key: app, operator: In, values: ["redis"]}
topologyKey: zone # 以 zone 标签为范围的拓扑结构区域中
containers:
- name: myapp
image: ikubernetes/myapp:v1

创建 Pod:

1
-> # kubectl apply -f deploy-with-required-podAffinity.yaml

那么如果 Redis 运行在 Node1 或 2 中,这 3 个 Pod 也会运行于 Node 1 或 2,一定不会被调度至 Node3,因为要保持和 Redis 处于同一个 zone 区域中。

1
2
3
4
5
6
-> # kubectl get pods -o wide   
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-with-pod-affinity-6588d69fdc-dkzh7 1/1 Running 0 10m 10.244.2.10 node2.monster.com <none> <none>
myapp-with-pod-affinity-6588d69fdc-f8b67 1/1 Running 0 62s 10.244.1.5 node1.monster.com <none> <none>
myapp-with-pod-affinity-6588d69fdc-rxthd 1/1 Running 0 62s 10.244.2.11 node2.monster.com <none> <none>
redis-699d7df68d-sqt78 1/1 Running 0 77m 10.244.2.8 node2.monster.com <none> <none>

Pod “硬-反亲和性”示例

定义不能与标签键为 app,值为 myapp 的 Pod 运行在同一节点,也就是不能与自己在同一 Node 节点,达到了分散 Pod 的结果。

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
-> # vim k8s/Kubernetes_Advanced_Practical/chapter12/deploy-with-required-podAntiAffinity.yaml 

apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-with-pod-anti-affinity
spec:
replicas: 4
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions: # 不能与标签键为 app,值为 myapp 的 Pod 运行在同一节点
- {key: app, operator: In, values: ["myapp"]}
topologyKey: kubernetes.io/hostname # 以 hostname 为范围
containers:
- name: myapp
image: ikubernetes/myapp:v1

Pod “软-亲和性“示例

  • 如果在标签为 rack 的节点范围中,运行着标签键为 app,值为 db 的 Node,那么评分加 80
  • 如果在标签为 zone 的节点范围中,则评分加 20
  • 如果两者都符合,则加 100
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
-> # vim deploy-with-preferred-podAffinity.yaml 

apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-with-preferred-pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
podAffinityTerm:
labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["db"]}
topologyKey: rack
- weight: 20
podAffinityTerm:
labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["db"]}
topologyKey: zone
containers:
- name: myapp
image: ikubernetes/myapp:v1

五、Taints and Tolerations 污点与容忍度

Node Affinity 是基于 Node 的标签来选择 Node,而 Pod Affinity 是基于 Pod 选 Node,无论哪一种,Node 都是在等待被选择。

Taint 则刚好相反,它用于让 Node 排斥 Pod 对象。

可以通过节点规范(NodeSpec)将污染应用于节点,并通过 pod 规范(PodSpec)将容忍度应用于 pod。节点上的污染将指示节点击退所有不容忍污染的 Pod。

Taints 必须结合 Tolerations 才能协同工作,要保证在 Node 上有污点,在 Pod 上有容忍度。否则任何无法容忍污点的 Pod 都无法调度到有污点的 Node。

定义 Taints(污点)的参数

定义污点的参数由三部分组成:

  • key:定义键;
  • value:定义值;
  • effect(效用):如果 Node 上有污点,Pod 也不是一定无法调度,要看这里的效用是如何定义的:
    • NoSchedule:不调度。如果被调度后,污点或容忍度发生变化,无法容忍了,则保持原样不动;
    • PreferNoSchedule:尽量不调度;
    • NoExecute:不执行。如果被调度后,污点或容忍度发生变化,无法容忍了,则驱离 Pod,并可以定义驱离宽限期;

定义 Tolerations(容忍度)的参数

容忍度通过以下三个字段的值去匹配污点:keyvalueeffect,而容忍度的评估方式有两种:

  • operator:指定操作符:
    • Eqaual:以上三个字段必须都匹配;
    • Existskeyeffect 匹配即可;

下图说明了 effect(效用)类型为 NoExecute 时,污点和容忍度的工作流程:

taintsandtolerations

当使用多个污点时:

可以给一个节点添加多个 taint,也可以给一个 pod 添加多个 toleration
Kubernetes 在处理多个 taint 和 toleration 时就像一个过滤器:从一个节点的所有污点开始遍历,过滤掉那些 Pod 中存在与之相匹配的容忍度的污点。余下未被过滤的污点的 effect 值决定了 Pod 是否会被分配到该节点,特别是以下情况:

  • 如果存在一个以上 effect 值为 NoSchedule 的 taint,则 Kubernetes 不会将 pod 分配到该节点。
  • 如果不存在 effect 值为 NoSchedule 的 taint,但是存在 effect 值为 PreferNoSchedule 的 taint,则 Kubernetes 会 尝试 将 pod 分配到该节点。
  • 如果存在一个以上 effect 值为 NoExecute 的 taint,则 Kubernetes 不会将 pod 分配到该节点(如果 pod 还未在节点上运行),或者将 pod 从该节点驱逐(如果 pod 已经在节点上运行):
    • 不能容忍相应 taint 的 Pod 将会立即遭到驱逐;
    • 可以容忍相关的 taint,但没有指定 tolerationSeconde 的 Pod 可一直运行于相应的节点;
    • 可以容忍相关的 taint,且指定了tolerationSeconde 的 Pod 可于相应的节点运行此字段指定的时长。

管理污点和容忍度

给节点添加 Taint

语法:

1
kubectl taint nodes NAME_KEY_1=VAL_1:TAINT_EFFECT_1 ... ...

使用命令 kubectl taint 给节点增加一个污点:

1
2
3
4
# 给节点 node1 增加一个污点,它的键名是 key1,键值是 value1,效果是 NoSchedule。 
# 这表示只有拥有和这个污点相匹配的容忍度的 Pod 才能够被分配到 node1 这个节点
kubectl taint nodes node1 key1=value1:NoSchedule ... ...

移除节点的 Taint

语法:

1
2
3
4
5
# 移除名称为 KEY,且配置了指定的 EFFECT 的 taint
kubectl taint nodes NAME_KEY:TAINT_EFFECT

# 移除名为 KEY 的 taint
kubectl taint nodes NAME_KEY-

若要移除上述命令所添加的污点,你可以执行:

1
kubectl taint nodes node1 key:NoSchedule-

为 Pod 添加 Toleration

operator 的默认值是 Equal

一个容忍度和一个污点相“匹配”是指它们有一样的键名和效果,并且:

  • 如果 operatorExists (此时容忍度不能指定 value);
  • 如果 operatorEqual ,则它们的 value 应该相等。

注意:存在两种特殊情况:

  • 如果一个容忍度的 key 为空且 operatorExists, 表示这个容忍度与任意的 key 、value 和 effect 都匹配,即这个容忍度能容忍任意 taint。
  • 如果 effect 为空,则可以与相同键名 key 的所有 effect 效果相匹配。

示例:在 PodSpec 中定义 Pod 的容忍度。

下面两个容忍度均与上面例子中使用 kubectl taint 命令创建的污点是相匹配的。

因此如果 Pod 只要有下面两个任意一个,就满足对 node1 的容忍度,就能够被分配到 node1 上。

Eqaual 类型,需要全匹配。

1
2
3
4
5
6
7
8
9
spec:
tolerations:
- key: "key1" # key 要匹配
operator: "Equal" # 定义为 Equal
value: "value1" # value 也要匹配
effect: "NoSchedule" # 效用也必须匹配
tolerationSeconds: 3600

# 与 node1 的污点全匹配,可以被调度

Exists 类型,keyeffect 匹配即可。

1
2
3
4
5
6
7
8
spec:
tolerations:
- key: "key1" # 只要 key 要匹配
operator: "Exists" # 定义为 Exists
effect: "NoSchedule" # 和 效用 也匹配即可
tolerationSeconds: 3600

# 因为是 Exists 类型,所以只要与 node1 的污点 key 和 effect 匹配,就可以被调度