[TOC]


0x01 Scheduler-任务调度器

Q: 什么是Scheduler?

答: Scheduler美 /ˈskedʒuːlər/是 kubernetes 中的调度器组件, 其主要任务是把指定的Pod分配到集群工作节点(或者说非污点节点)之上;
他是作为单独的程序运行的启动之后会一直和APIServer持续连接,当然有 pod 的资源清单中 Pod.Spec.NodeName 为空的时候,对每个pod都会创建一个binding,表明该 pod 应该放到哪个节点上

Tips: 由于Pod调度策略的随机性,其目的是自定义Pod调度到哪一个节点;


Q: 实现 Scheduler 调度器需要考虑的问题?

1.公平: 如何保证每个节点都能被分配.
2.资源高效利用: 集群所有资源最大化被使用.
3.效率: 调度的性能要好,能够尽快地对大批量的pod 完成调度工作.
4.灵活: 允许用户根据自己的需求控制调度的逻辑.


1.调度基础概念

(1)调度过程(构成部分)说明:

  • 1.首先是过滤掉不满足条件的节点,这个过程称为 predicate 英 /ˈprɛdɪˌkeɪt/ - v. 使……基于;
  • 2.然后对通过的节点按照优先级排序,这个过程称为priority 英 /praɪˈɒrəti/ - n. 优先;优先权;
  • 3.最后从中选择优先级最高的节点。

Tips: 如果中间任何一步骤有错误就直接返回错误;

  • Predicate 系列的算法
    • PodFitsResources :节点上剩余的资源是否大于 pod请求的资源
    • PodFitsHost :如果pod指定了NodeName,检查节点名称是否和NodeName相匹配
    • PodFitsHostPorts :节点上已经使用的port是否和 pod申请的port冲突
    • PodSelectorMatches : 过滤掉 和 pod 指定 的label 不匹配的节点
    • NoDiskConflict : 已经 mount 的 volume 和 pod 指定的 volume 不冲突,除非它们都是只读
  • priority 选项
    描述: 优先级由一系列键值对组成,键是该优先级项的名称,值是它的权重(非常重要)一般得权重越高即优先级越高,通过算法对所有的优先级项目和权重进行计算得出最终的结果;
    这些优先级选项包括:
    • LeastRequestedPriority : 通过计算CPU和 Memory 的使用率来决定权重,使用率越低权重越高。这个优先级指标倾向于资源使用比例更低的节点;
    • BalancedResourceAllocation : 节点上 CPU 和 Memory 使用率越接近,权重越高,该项需要与LeastRequestedPriority一起使用不能单独使用;
      1
      2
      3
      4
      # 其含义示例是表达如下:
      Node1:CPU 占用 20% , Memory 占用 60 %;
      Node2: CPU 占用 50% , Memory 占用 50 %;
      # 此时由于Node2资源分配更加均衡此时我们的Pod将会优先调度到此节点之上;
    • ImageLocalityPriority : 倾向于已经有要使用镜像的节点,镜像总大小值越大,权重越高

Tips: 如果在 predicate 过程中没有合适的节点,pod 会一直在 pending 状态,不断重试调度直到有节点满足条件(后面将会进行实践)。之后如果有多个节点满足此条件就继续 priorities 过程按照优先级大小对节点排序;


2.自定义调度器

描述: 除了 kubernetes 自带的调度器开发者也可以编写自己的调度器, 通过 spec:schedulername 参数指定调度器的名字,可以为 pod 选择某个调度器进行调度;

Kubernetes也允许用户编写自己的调度器组件,并在创建资源的时候引用它。多个调度器可以同时运行和工作,只要名字不冲突, Kubernetes提供的调度器名字是default

例如: 使用某个调度器就是在Pod的spec.schedulername字段中填写上调度器的名字。如果自定义的调度器名字是my-scheduler,那么只有当spec.schedulername字段是my-scheduler才会被调度;

  • 资源清单:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 资源对象: pod.spec.schedulername
    # 通过 spec:schedulername 参数指定调度器的名字,可以为pod 选择某个调度器进行调度
    apiVersion: v1
    kind: Pod
    metadata:
    name: annotation-second-scheduler
    labels:
    name: multischeduler-example
    spec:
    schedulername: my-scheduler # 关键点 (选择调度器)
    containers:
    - name: pod-with-second-annotation-container
    image: gcr.io/google_containers/pause:2.0
  • 简单调度脚本: 它通过kubectl命令从apiserver获取未调度的Pod(spec.schedulerName 是my-scheduler,并且spec.nodeName 为空),同样地,用kubectl从apiserver获取nodes的信息,然后随机选择一个node作为调度结果,并写入到apiserver中。我们可通过kubectl describe pod pod_name查看一个Pod采用的调度器;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
SERVER='localhost:8001'
while true;
do
for PODNAME in $(kubectl --server $SERVER get pods -o json | jq '.items[] | select(.spec.schedulerName == "my-scheduler") | select(.spec.nodeName == null) | .metadata.name' | tr -d '"')
;
do
NODES=($(kubectl --server $SERVER get nodes -o json | jq '.items[].metadata.name' | tr -d '"'))
NUMNODES=${#NODES[@]}
CHOSEN=${NODES[$[ $RANDOM % $NUMNODES ]]}
curl --header "Content-Type:application/json" --request POST --data '{"apiVersion":"v1", "kind": "Binding", "metadata": {"name": "'$PODNAME'"}, "target": {"apiVersion": "v1", "kind"
: "Node", "name": "'$CHOSEN'"}}' http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/
echo "Assigned $PODNAME to $CHOSEN"
done
sleep 1
done

3.nodeAffinity - 节点亲和性

资源对象: pod.spec.nodeAffinity

1
2
软策略 : ·preferredDuringSchedulingIgnoredDuringExecution: 【我想要去这个节点】 - adj. 优先的;首选的
硬策略 : ·requiredDuringschedulinglgnoredDuringExecution: 【我一定要去这个节点】 - adj. 必需的;(美)必修的


什么是Pod调度的软策略以及硬策略?

1.软策略: 即希望 , 表示想到满足条件的节点上去,当不满足时候就到其它节点上去。
2.硬策略: 即强制 , 表示一定到某一个节点上去, 当不满足条件时候其哪一个节点也不去,注意其优先级高于软策略;


键值运算关系(即operator的可用值):

  • In: label 的值在某个列表中
  • NotIn: label 的值不在某个列表中
  • Gt: label 的值大于某个值
  • Lt: label 的值小于某个值
  • Exists: 某个label存在
  • DoesNotExist: 某个label不存在


硬策略

资源清单: affinity-strong-demo.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cat > affinity-strong-demo.yaml<<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: strong-affinity
labels:
app: node-strong-affinity-pod
spec:
containers:
- name: with-strong-node-affinity
image: harbor.weiyigeek.top/test/busbox:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","sleep 700"]
restartPolicy: Never # 重启策略
affinity: # 亲和性
nodeAffinity: # 节点亲和性
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略(必须得)
nodeSelectorTerms: # 节点选择条件
- matchExpressions: # 匹配表达式
- key: kubernetes.io/hostname # Key 值
operator: NotIn # 表达式 表示 Node主机名称不能是k8s-node-4即表示不能在node-4的节点上运行该Pod;
values:
- k8s-node-4 # Value 值
EOF

操作示例:

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
44
45
46
47
# (1) 部署资源清单
~/K8s/Day9/demo1$ kubectl create -f affinity-strong-demo.yaml
# pod/affinity created

# (2) 查看调度器 (此时Pod将会调度到Node0-5之上)
$ kubectl get pod -o wide --show-labels
# NAME READY STATUS RESTARTS AGE IP NODE LABELS
# affinity 1/1 Running 0 64s 10.244.2.17 k8s-node-5 app=node-strong-affinity-pod


# (3) 修改调度器
~/K8s/Day9/demo1$ kubectl delete -f affinity-strong-demo.yaml # 先删除原来的Pod下同
# pod "affinity" deleted
$ vim affinity-strong-demo.yaml
# spec:
# affinity:
# nodeAffinity:
# requiredDuringSchedulingIgnoredDuringExecution:
# nodeSelectorTerms:
# - matchExpressions:
# - key: kubernetes.io/hostname
# operator: In # 修改点 表示 Node主机名称是k8s-node-4即表示只能在 node-4 的节点上运行该Pod
# values:
# - k8s-node-4
~/K8s/Day9/demo1$ kubectl create -f affinity-strong-demo.yaml

# (4) 验证
~/K8s/Day9/demo1$ kubectl get pod -o wide --show-labels
# NAME READY STATUS RESTARTS AGE IP NODE LABELS
# strong-affinity 1/1 Running 0 6s 10.244.1.141 k8s-node-4 app=node-strong-affinity-pod

# (5) 此时设置一个不可满足的条件时(我们根本都没有一个node-6得节点),此时Pod将会一直处于 Pending
# spec:
# affinity:
# nodeAffinity:
# requiredDuringSchedulingIgnoredDuringExecution:
# nodeSelectorTerms:
# - matchExpressions:
# - key: kubernetes.io/hostname # 表示 Node 主机名称
# operator: In # 即表示只能在 node-6 的节点上运行该Pod
# values:
# - k8s-node-6 # 匹配值是 k8s-node-6
~/K8s/Day9/demo1$ kubectl create -f affinity-strong-demo-2.yaml
# pod/strong-affinity created
~/K8s/Day9/demo1$ kubectl get pod -o wide
# NAME READY STATUS RESTARTS AGE IP NODE
# strong-affinity 0/1 Pending (关键点) 0 10s <none> <none>


软策略

资源清单: affinity-soft-demo.yaml

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
cat > affinity-soft-demo.yaml<<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: soft-affinity
labels:
app: node-soft-affinity-pod
spec:
containers:
- name: with-soft-node-affinity
image: harbor.weiyigeek.top/test/busbox:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","sleep 700"]
restartPolicy: Never # 重启策略
affinity: # 亲和性
nodeAffinity: # 节点亲和性
preferredDuringSchedulingIgnoredDuringExecution: # 软策略(优先级首选得)
- weight: 1 # 权重(越高其匹配优先级越高)
preference: # 偏向
matchExpressions: # 匹配表达式
- key: source # Key 值
operator: In # 表达式
values:
- k8s-node-3 # Value 值 表示 最想匹配到node3节点
- weight: 2 # 权重(越高其匹配优先级越高)
preference: # 偏向
matchExpressions: # 匹配表达式
- key: source # Key 值
operator: NotIn # 表达式
values:
- k8s-node-4 # Value 值 表示 不想匹配到node4节点
EOF

操作示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# (1) 创建部署节点
~/K8s/Day9/demo1$ kubectl create -f affinity-soft-demo.yaml
# pod/soft-affinity created
~/K8s/Day9/demo1$ kubectl get pod -o wide #此时将会调度到node5上面
# NAME READY STATUS RESTARTS AGE IP NODE
# soft-affinity 1/1 Running 0 8s 10.244.2.19 k8s-node-5

# (2) 修改权重追加如下
# - weight: 3 # 权重(越高其匹配优先级越高)
# preference: # 偏向
# matchExpressions: # 匹配表达式
# - key: source # Key 值
# operator: In # 表达式
# values:
# - k8s-node-4 # Value 值表示想匹配到node4节点
~/K8s/Day9/demo1$ kubectl delete -f affinity-soft-demo-1.yaml && kubectl create -f affinity-soft-demo-1.yaml && sleep 10 && kubectl get pod -o wide
# 此时虽然想到node4节点的亲和性较高然后前面有权重为2的不希望到node4上,此时他任然不会将Pod调度在node4上;
# NAME READY STATUS RESTARTS AGE IP NODE
# soft-affinity 1/1 Running 0 5m38s 10.244.2.22 k8s-node-5


硬软策略联使

描述 : 硬策略与软策略(required & preferred)可以组合相互进行使用;
资源清单:

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
cat > requiredpreferred-strategy.yaml<<'END'
apiVersion: v1
kind: Pod
metadata:
name: affinity
labels:
app: node-affinity-pod
spec:
containers:
- name: with-node-affinity
image: harbor.weiyigeek.top/test/busbox:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","sleep 700"]
affinity:
nodeAffinity: # 节点亲和性
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- k8s-node02
preferredDuringSchedulingIgnoredDuringExecution: # 软策略
- weight: 1
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-node03
END

Tips : 该Pod一定不能调度到k8s-node02节点,但如果k8s-node03可以被调度时便会调度到k8s-node03节点;

固定节点调度策略(fixed)

描述: 上述讲了亲和性来选择运行的节点,如果想要Pod运行在指定的节点之上就需要按照下述进行设置固定调度的节点;

主要有以下资源对象字段设置Pod运行的固定节点:

  • 1) spec.template.spec.nodeName
  • 2) pod.spec.nodeSelector


方式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
cat > fixed-node-test.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: fixed-node-test
labels:
app: fixed-node
spec:
replicas: 5
selector: # 注意它是在Deployment控制器中需要依赖的
matchLabels:
app: fixed-node # 匹配的Pod标签非常重要
template:
metadata:
labels:
app: fixed-node
spec:
nodeName: k8s-node-4 # 节点绑定 (关键点)
containers:
- name: fixed-node
image: harbor.weiyigeek.top/test/busbox:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","sleep 700"]
ports:
- containerPort: 80
EOF

操作流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# (1) 部署`deployment`管理的`fiexd`固定控制器
~/K8s/Day9/demo2$ kubectl create -f fixed-node-test.yaml
# deployment.apps/fixed-node-test created

# (2) 查看Pod部署状态
~/K8s/Day9/demo2$ kubectl get pod
# NAME READY STATUS RESTARTS AGE
# fixed-node-test-5f67b8645d-4c7vd 0/1 ContainerCreating 0 3s
# fixed-node-test-5f67b8645d-bcrj7 0/1 ContainerCreating 0 3s
# fixed-node-test-5f67b8645d-cnb5n 0/1 ContainerCreating 0 3s
# fixed-node-test-5f67b8645d-sh5ld 0/1 ContainerCreating 0 3s
# fixed-node-test-5f67b8645d-v5kfd 0/1 ContainerCreating 0 3s
# taint-tolerations 0/1 Pending 0 24m
~/K8s/Day9/demo2$ kubectl get pod -o wide
# NAME READY STATUS RESTARTS AGE IP NODE
# fixed-node-test-5f67b8645d-4c7vd 1/1 Running 0 7s 10.244.1.148 k8s-node-4
# fixed-node-test-5f67b8645d-bcrj7 1/1 Running 0 7s 10.244.1.146 k8s-node-4
# fixed-node-test-5f67b8645d-cnb5n 1/1 Running 0 7s 10.244.1.145 k8s-node-4
# fixed-node-test-5f67b8645d-sh5ld 1/1 Running 0 7s 10.244.1.147 k8s-node-4
# fixed-node-test-5f67b8645d-v5kfd 1/1 Running 0 7s 10.244.1.149 k8s-node-4


方式2.资源清单示例:

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
cat > fixed-node-test-1.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: fixed-node-test-1
labels:
app: fixed-node-1
spec:
replicas: 5
selector: # 注意它是在Deployment控制器中需要依赖的
matchLabels:
app: fixed-node-1 # 匹配的Pod标签非常重要
template:
metadata:
labels:
app: fixed-node-1
spec:
nodeSelector:
nodetype: fixed # 节点标签 (主要需要给节点打标签然后才能指定)
containers:
- name: fixed-node-1
image: harbor.weiyigeek.top/test/busbox:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","sleep 700"]
ports:
- containerPort: 80 # 容器暴露端口
EOF

部署流程:

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
# (1) 节点打标签(只有打了才能生效)
~/K8s/Day9/demo2$ kubectl label node k8s-node-5 nodetype=fixed
# node/k8s-node-5 labeled
kubectl get node --show-labels # 查看打的标签
# NAME STATUS ROLES AGE VERSION LABELS
# weiyigeek-ubuntu Ready master 18d v1.19.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=weiyigeek-ubuntu,kubernetes.io/os=linux,node-role.kubernetes.io/master=
# k8s-node-4 Ready <none> 18d v1.19.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node-4,kubernetes.io/os=linux
# k8s-node-5 Ready <none> 7d v1.19.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node-5,kubernetes.io/os=linux,nodetype=fixed


# (2) 部署
~/K8s/Day9/demo2$ kubectl create -f fixed-node-test-1.yaml
# deployment.apps/fixed-node-test-1 created

~/K8s/Day9/demo2$ kubectl get pod -o wide # 可以看见所有的Pod都运行在k8s-node-5上
# NAME READY STATUS RESTARTS AGE IP NODE
# fixed-node-test-1-6c996c5887-67ldb 1/1 Running 0 37s 10.244.2.30 k8s-node-5
# fixed-node-test-1-6c996c5887-g4gz8 1/1 Running 0 37s 10.244.2.28 k8s-node-5
# fixed-node-test-1-6c996c5887-j9f2k 1/1 Running 0 37s 10.244.2.27 k8s-node-5
# fixed-node-test-1-6c996c5887-msc5g 1/1 Running 0 37s 10.244.2.29 k8s-node-5
# fixed-node-test-1-6c996c5887-xfb6v 1/1 Running 0 37s 10.244.2.26 k8s-node-5

# (3) 更进一步将node4的标签更改一致然后在进行Pod的扩展操作查看有什么效果;
kubectl label node k8s-node-4 nodetype=fixed
# node/k8s-node-4 labeled

# (4) Deployment 进行扩容
~/K8s/Day9/demo2$ kubectl scale --replicas=7 deploy fixed-node-test-1
# deployment.apps/fixed-node-test-1 scaled

# (5) 结果验证
~/K8s/Day9/demo2$ kubectl get pod -o wide | grep "fixed-node-test-1"
# fixed-node-test-1-6c996c5887-67ldb 1/1 Running 0 37s 10.244.2.30 k8s-node-5
# fixed-node-test-1-6c996c5887-g4gz8 1/1 Running 0 37s 10.244.2.28 k8s-node-5
# fixed-node-test-1-6c996c5887-j9f2k 1/1 Running 0 37s 10.244.2.27 k8s-node-5
# fixed-node-test-1-6c996c5887-msc5g 1/1 Running 0 37s 10.244.2.29 k8s-node-5
# fixed-node-test-1-6c996c5887-xfb6v 1/1 Running 0 37s 10.244.2.26 k8s-node-5
# fixed-node-test-1-6c996c5887-tbnf4 1/1 Running 0 61s 10.244.1.152 k8s-node-4 # 此时增加两个Pod运行在K8s-node-4上
# fixed-node-test-1-6c996c5887-xfb6v 1/1 Running 0 10m 10.244.2.26 k8s-node-4

4.podAffinity - Pod 亲和性

描述: 同样PodAffinity也有软策略和硬策略其解释与节点亲和性相同;

1
2
3
# (1) 所属对象: pod.spec.affinity.podAffinity (亲和) / podAntiAffinity (反亲和)
软策略 :* preferredDuringSchedulinglgnoredDuringExecution
硬策略 : * requiredDuringSchedulinglgnoredDuringExecution


资源清单示例:

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
# (1) 根据Pod亲和性匹配该Pod运行的节点,反亲和就是不和满足条件的Pod在同一个节点运行;
cat > pod-affinity-demo-1.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: pod-affinity-demo1
labels:
app: pod-affinity
spec:
containers:
- name: pod-affinity
image: harbor.weiyigeek.top/test/busbox:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","sleep 700"]
affinity:
podAffinity: # Pod 亲和性
requiredDuringSchedulingIgnoredDuringExecution: # 硬亲和
- labelSelector: # 标签选择器
matchExpressions: # 匹配规则
- key: app
operator: In # 表示 匹配标签为app=node-soft-affinity-pod 的Pod在哪一个节点之上
values:
- node-soft-affinity-pod
topologyKey: kubernetes.io/hostname
podAntiAffinity: # Pod 反亲和
preferredDuringSchedulingIgnoredDuringExecution: # 软亲和
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In # 表示 匹配标签为`app=node-strong-affinity-pod`的 Pod 在哪一个节点之上
values:
- node-strong-affinity-pod
topologyKey: kubernetes.io/hostname
EOF

温馨提示: 什么是 topologyKey? 个人理解 pod affinity 的调度范围为 topology。

官方解释:如果该X已经在运行一个或多个满足规则Y的Pod,则该Pod应该(或者在非亲和性的情况下不应该)在 X 中运行,Y 表示为LabelSelector规则, X 是一个拓扑域,例如节点,机架,云提供者区域,云提供者区域等。您可以使用topologyKey这是系统用来表示这种拓扑域的节点标签的密钥。


操作流程:

1
2
3
4
5
6
7
8
9
10
# (1) 部署
~/K8s/Day9/demo1$ kubectl create -f pod-affinity-demo-1.yaml
# pod/pod-affinity-demo1 created

# (2) 验证可以看见运行在 k8s-node-5 节点上,这是由于我们的Pod亲和性的强策略;
~/K8s/Day9/demo1$ kubectl get pod --show-labels -o wide
# NAME READY STATUS RESTARTS AGE IP NODE LABELS
# pod-affinity-demo1 1/1 Running 0 4s 10.244.2.24 k8s-node-5 app=pod-affinity
# soft-affinity 1/1 Running 0 10s 10.244.2.23 k8s-node-5 app=node-soft-affinity-pod
# strong-affinity 0/1 Pending 0 88m <none> <none> app=node-strong-affinity-pod

PS : 在使用Pod亲和性时有一个问题需要非常重视即,与之匹配Pod必须是RUNNING状态,否则认为不满足调度条件则Pod将会被置为Pending状态;


节点与Pod的Affinity亲和性总结:
描述: 节点与Pod亲和性/反亲和性调度策略比较如下。

1
2
3
4
# 调度策略       匹配标签 操作符                                   拓扑域 支持调度目标
nodeAffinity 主机 In, NotIn, Exists,DoesNotExist, Gt, Lt 否 指定主机
podAffinity POD In, NotIn, Exists,DoesNotExist 是 POD与指定POD同一拓扑域
podAnitAffinity POD In, NotIn, Exists,DoesNotExist 是 POD与指定POD不在同一拓扑域


补充说明.解决负载不均衡的问题, 可以给Pod容器设置反亲和,让这些容器平均的分布在各个节点上(不要聚在一起)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 节点 & Pod 反亲和
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node
operator: In
values:
- work
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- ingress-nginx
topologyKey: kubernetes.io/hostname
weight: 100


5.Priority - Pod 优先级

描述: 与前面所讲的调度优选策略中的优先级(Priorities)不同,前文所讲的优先级指的是节点优先级,而pod priority指的是Pod的优先级,高优先级的Pod会优先被调度,或者在资源不足低情况牺牲低优先级的Pod以便于重要的Pod能够得到资源部署。

为了定义Pod优先级,需要先定义PriorityClass对象,该对象没有Namespace限制,官网示例:

1
2
3
4
5
6
7
8
9
10
11
12
# (1) 
~$ kubectl api-resources | grep "priorityclasses"
priorityclasses pc scheduling.k8s.io false PriorityClass

# (2) 资源清单
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."

然后通过在Pod的spec. priorityClassName中指定已定义的PriorityClass名称即可:

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority


6.Preemption - Pod 抢占

描述: 当节点没有足够的资源供调度器调度Pod、导致Pod处于pending时,抢占(preemption)逻辑会被触发。Preemption会尝试从一个节点删除低优先级的Pod,从而释放资源使高优先级的Pod得到节点资源进行部署。


0x02 Taints - 污点

描述: 前面我们讲述到节点亲和性是pod的一种属性(偏好或硬性要求), 它可以使得pod运行在满足条件的特定节点之上;

Q: 那什么又是Taint(污点)?

答: Taint 恰恰与之相反, 它使节点能够排斥一类特定的pod。注意每个节点上都可以应用一个或多个taint, 如果设置了容忍的Pod将可以容忍污点的存在,可以被调度到存在污点的 Node 上;

Tips : 使用 kubectl 的 taint 命令可以给某个Node节点设置污点,Node被设置上污点之后就和Pod之间存在了一种相斥的关系,可以让Node拒绝 Pod 的调度执行,甚至将Node已经存在的Pod驱逐出去;

1
2
3
4
5
6
7
8
# Syntax : 其中 value 可以为空 effect 描述污点的作用
kubectl taint node [nodeName] key=value:[effect] # 每个污点有一个key和 value作为污点的标签

# 参数值
# 其中 [effect] 可取值 : [ NoSchedule | PreferNoSchedule | NoExecute ]
* NoSchedule : 表示k8s将不会将Pod 调度到具有该污点的 Node 上;
* PreferNoSchedule : 表示k8s将尽量避免将Pod调度到具有该污点的 Node 上;
* NoExecute : 表示k8s将不会将 Pod调度到具有该污点的Node上,同时会将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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 0) 查看指定节点taint
$ kubectl describe node master1| grep "Taints" # 查找 Taints 字段
# Taints: node-role.kubernetes.io/master:NoSchedule
# 查看所有污点策略显示三个master节点都是NoSchedule
$ kubectl get no -o yaml | grep taint -A 5
# taints:
# - effect: NoSchedule
# key: node-role.kubernetes.io/master
# status:
# addresses:
# - address: master1的IP
# --
# taints:
# - effect: NoSchedule
# key: node-role.kubernetes.io/master
# status:
# addresses:
# - address: master2的IP
# --
# taints:
# - effect: NoSchedule
# key: node-role.kubernetes.io/master
# status:
# addresses:
# - address: master3的IP


# 1) 手动为master节点设置taints
$ kubectl taint nodes node1 key1=value1:NoSchedule
$ kubectl taint nodes master1 node-role.kubernetes.io/master=:NoSchedule


# 2) 去除污点的设置
kubectl taint nodes node1 key1:NoSchedule- # 这里的key可以不用指定value代表删除指定key所有的effect;
# de1 key1: NoSchedule-
$ kubectl taint nodes k8s-master-1 node-role.kubernetes.io/master=:NoSchedule-
# node/k8s-master-1untainted


# 3) 取消污点后结果
$ kubectl describe node k8s-master-1 | grep "Taints"
# Taints: <none> # 取消污点后

$ kubectl get node --show-labels
# NAME STATUS ROLES AGE VERSION LABELS
# weiyigeek-ubuntu Ready master 18d v1.19.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=weiyigeek-ubuntu,kubernetes.io/os=linux,node-role.kubernetes.io/master=
# k8s-node-4 Ready <none> 18d v1.19.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node-4,kubernetes.io/os=linux
# k8s-node-5 Ready <none> 7d v1.19.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node-5,kubernetes.io/os=linux,nodetype=fixed

~/K8s/Day9/demo1$ kubectl get no k8s-node-5 -o yaml | grep "key" -C 3
# - 10.244.2.0/24
# taints:
# - effect: NoExecute
# key: node-role.kubernetes.io/master
# status:
# addresses:
# - address: 10.10.107.215

$ kubectl taint nodes k8s-node-5 node-role.kubernetes.io/master-
# node/k8s-node-5 untainted


补充操作:

1
2
3
4
5
6
7
8
9
# 1) 去除污点允许 master 节点部署 pod;
[root@master1 ~]$ kubectl taint nodes --all node-role.kubernetes.io/master-
# node/master1 untainted
# node/master2 untainted
# node/master3 untainted
# error: taint "node-role.kubernetes.io/master" not found

# 2) 再次查看无显示说明污点去除成功
kubectl get no -o yaml | grep taint -A 5

Tips : 为 master 设置的这个 taint 中node-role.kubernetes.io/master为 key/value 为空 effect 为NoSchedule;
Tips : 如果输入命令时你丢掉了= 符号, 写成了node-role.kubernetes.io/master:NoSchedule, 会报 error: at least one taint update is required 错误;


0x03 Toleration - 容忍

Q: 什么是Toleration容忍?

答: 即表示这些 pod 可以(但不要求)被调度到具有匹配 taint 的节点上, 简单的说

Tips: Taint 和 Toleration 相互配合可以用来避免pod被分配到不合适的节点上。表示对于那些不能容忍这些 taint 的 pod 是不会被该节点接受的, 但是针对 Pod 没有节点运行时可以选一台污点的节点运行Pod;

在 pod 的 spec 中设置 tolerations 字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1.当不指定key值时表示容忍所有的污点key:
tolerations:
- operator: "Exists"

# 2.当不指定 effect 值时表示容忍所有的污点作用
tolerations:
- key: "key"
operator: "Exists"

# 3.查看容忍
~$ kubectl describe pod
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s


Tolerations 资源清单演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# pod.spec.tolerations
tolerationstolerations:
- key: "key1"
operator: "Equal" # 其中key, vaule, effect 要与Node上设置的 taint保持一致
value: "value1"
effect: "NoSchedule"
tolerationSeconds: 3600 # 用于描述当Pod需要被驱逐时可以在 Pod上继续保留运行的时间
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
- key: "key2"
operator: "Exists" # 为Exists将会忽略value值
effect: "NoSchedule"
- key: "node-role.kubernetes.io/master" # 容忍 tolerations 主节点的 taints
operator: "Equal"
value: ""
effect: "PreferNoSchedule" # 尽可能不调度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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# (0) 有多个Master 存在时防止资源浪费,可以如下设置
kubectl taint nodes Node-Name node-role.kubernetes.io/master=:PreferNoSchedule

# (1) 当节点有故障需要退出时,需要将Pod从指定Node节点进行驱逐后,方可删除节点
kubectl taint nodes k8s-master-4 node-role.kubernetes.io/master=:NoExecute
# node/k8s-node-5 tainted


# (2) 查看Pod运行节点
$ kubectl get pod -o wide
# NAME READY STATUS RESTARTS AGE IP NODE
# pod-affinity-demo1 1/1 Terminating 335 2d17h 10.244.2.24 k8s-node-5
~/K8s/Day9/demo1$ kubectl get no k8s-node-4 -o yaml | grep "key" -C 3
- 10.244.1.0/24
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master # 关键点
status:
addresses:
- address: 10.10.107.214


# (3) 演示案例
cat > taint-Tolerations-mode.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: taint-tolerations
labels:
app: taint-tolerations-mode
spec:
containers:
- name: with-soft-node-affinity
image: harbor.weiyigeek.top/test/busbox:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","sleep 700"]
restartPolicy: Never # 重启策略(绝不除非移除退出)
tolerations:
- key: "kubernetes.io/hostname"
operator: "Equal" # supported values: "Equal", "Exists"
value: "k8s-node-5"
effect: "NoExecute"
EOF

# (4) 此时Pod不会运行在k8s-node-5主机上
~/K8s/Day9/demo1$ kubectl create -f taint-Tolerations-mode.yaml
# pod/taint-tolerations created
~/K8s/Day9/demo1$ kubectl get pod -o wide
# NAME READY STATUS RESTARTS AGE IP NODE
# taint-tolerations 1/1 Running 0 33s 10.244.1.143 k8s-node-4


# (5) 此时如果将node-4、5主机都设置成为污点则删除重建Pod将会运行k8s-node-5有污点的这台机器,这是因为上面 tolerations 容忍配置的原因;
~/K8s/Day9/demo1$ kubectl taint nodes k8s-node-4 node-role.kubernetes.io/master=:NoSchedule
~/K8s/Day9/demo1$ kubectl taint nodes k8s-node-5 node-role.kubernetes.io/master=:NoSchedule
~/K8s/Day9/demo1$ kubectl delete -f taint-Tolerations-mode.yaml
~/K8s/Day9/demo1$ kubectl get pod -o wide
# NAME READY STATUS RESTARTS AGE IP NODE
# taint-tolerations 1/1 Running 0 10s 10.244.2.33 k8s-node-4 <none> <none>


0x04 Cordon - Node 隔离与恢复

描述: 在某些场景下需要对Node进行隔离,比如硬件升级或者维护,目前隔离的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
# (1) 使用 kubectl cordon 命令可以对某一个Node进行隔离,在隔离后就不会向该Node节点调度Pod。
$ kubectl get node | grep "224"
# weiyigeek-224 Ready <none> 72d v1.19.6
$ kubectl cordon weiyigeek-224
# node/weiyigeek-224 cordoned
$ kubectl get node # Tips :隔离操作并不会停止或者删除正在运行的Pod需要人工进行干预,所以尽管已经对224进行隔离,但是其在上面运行的Pod状态并没有受到任何影响。
# NAME STATUS ROLES AGE VERSION
# weiyigeek-224 Ready,SchedulingDisabled <none> 72d v1.19.6
$ ansible weiyigeek-224 -m shell -a "docker ps"
# weiyigeek-224 | CHANGED | rc=0 >>
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# 558fa21e1993 caa6655434a5 "/conf/update-node.s…" 2 months ago Up 2 months k8s_redis_redis-cluster-3_database_db60bf69-de70-4aa4-b8e6-84ebbb37992b_0
# a515551a4f71 registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.2 "/pause" 2 months ago Up 2 months k8s_POD_redis-cluster-3_database_db60bf69-de70-4aa4-b8e6-84ebbb37992b_0
# ffe033537640 183b53858d7d "start_runit" 2 months ago Up 2 months k8s_calico-node_calico-node-7jcwh_kube-system_5c66e41b-f6d8-4ed8-a5b6-d08a5e734214_0
# 6dbb52c66413 registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.2 "/pause" 2 months ago Up 2 months k8s_POD_calico-node-7jcwh_kube-system_5c66e41b-f6d8-4ed8-a5b6-d08a5e734214_0
# 22bad44b05f4 dbcc366449b0 "/usr/local/bin/kube…" 2 months ago Up 2 months k8s_kube-proxy_kube-proxy-25tts_kube-system_89870e87-ecb3-48fd-b42d-2b7662631d51_0
# d0619946b54e registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.2 "/pause" 2 months ago Up 2 months k8s_POD_kube-proxy-25tts_kube-system_89870e87-ecb3-48fd-b42d-2b7662631d51_0


# (2) 统一的恢复方式:
~$ kubectl uncordon weiyigeek-224
# node/weiyigeek-224 uncordoned
~$ kubectl get node | grep "224"
# weiyigeek-224 Ready <none> 72d v1.19.6


FAQ - 知识扩展

Q:普通用户有自定义Pod优先级的权限吗?

A:可以,Pod优先级定义与创建普通Pod类似,并没有特别权限控制。定义Pod优先级,需要先定义kind为PriorityClass类型的资源对象,然后通过在Pod的spec. priorityClassName中指定已定义的PriorityClass名称即可。


Q:Kubernetes scheduler extender能介绍一下么?

A:extender可理解为Kubernetes调度策略和算法的扩展,属于自定义调度器的一种方式,与Kubernetes默认调度器的过程类似,主要是针对一些不算受集群本身控制的资源(比如网络),需要通过外部调用来进行调度的情况。


Q:用户使用了NodeSelector指定了Pod调度的node节点后,如果node不可用,那么scheduler会采用别的策略吗?

A:nodeSelector是目前最为简单的一种pod运行时调度限制,目前在Kubernetes 1.7.x及以下版本可用。Pod.spec.nodeSelector通过kubernetes的label-selector机制选择节点,由调度器调度策略匹配label,而后调度Pod到目标节点,该匹配规则属于强制约束,如果node不可用,Pod会一直处于pending状态。nodeAffinity具备nodeSelector的全部功能,所以未来Kubernetes会将nodeSelector废除。

Q: 如何让多个Pod均匀部署到各个节点上?

描述: 通过前面学习我们知道在K8S中kube-scheduler组件负责Pod的调度对每一个新创建的 Pod 或者是未被调度的 Pod,kube-scheduler 会选择一个最优的节点去运行这个 Pod,那我们如何将Pod部署到各个节点上呢?

通常两种方式,首先考虑使用工作负载反亲和特性让Pod之间尽量“互斥”,其次是可以使用daemonsets.apps资源控制器管理Pod资源,这样就能尽量均匀的分布在各节点上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 亲和性
affinity:
podAntiAffinity: # 工作负载反亲和
preferredDuringSchedulingIgnoredDuringExecution: # 尽量满足如下条件
- podAffinityTerm:
labelSelector: # 选择Pod的标签,与工作负载本身反亲和
matchExpressions:
- key: app
operator: In
values:
- nginx
namespaces:
- default
topologyKey: kubernetes.io/hostname # 在指定节点上起作用