06-k8s调度

摘要

本文内容转自网络,个人学习记录使用,请勿传播

创建一个Pod的工作流程

k8s集群基于list-watch(消息队列监听?)机制的控制器架构,实现组件间交互的解耦。k8s集群中的组件一直监听自己负责的资源,当这些资源发生变化时,kube-apiserver会通知这些组件,这个过程类似于消息队列的发布和订阅。

image-20210823142706967

  1. kubectl run nginx-demo --image=nginx:1.17.10:用户通过kubectl命令发起创建pod指令,kubectl将用户输入的指令发送给apiserver,apiserver将数据存储到etcd中。
  2. scheduler根据自己的调度算法为创建的pod选择一个合适的节点,并给pod打一个label:nodeName=k8s-node01,并将结果返回给apiserver,apiserver将数据存储到etcd中。
  3. kubelet发现有新的pod分配到自己所在的节点,调用docker-api创建容器,并将容器状态返回给apiserver,apiserver将数据存储在etcd中
  4. 用户通过kubectl命令可以查询到pod的状态和信息。

controller-manager:负责常规的后台任务,如deployment

kube-proxy:负责容器的网络,如service的实现

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
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx02
name: nginx02
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: nginx02
template:
metadata:
labels:
app: nginx02
spec:
containers:
- image: nginx:1.17.10
name: nginx
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 30
periodSeconds: 20
tcpSocket:
port: 8080
resources: {} # 资源限制
restartPolicy: Always
schedulerName: default-scheduler # 调度策略
nodeName: "" # 节点名称
nodeSelector: {} # 节点选择器
affinity: {}
tolerations: []

image-20210824131419286

资源限制对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
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx02
name: nginx02
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: nginx02
template:
metadata:
labels:
app: nginx02
spec:
containers:
- image: nginx:1.17.10
name: nginx
resources:
requests: # 在对pod进行调度分配时参照的资源依据
cpu: "250m"
memory: "64Mi"
limits: # pod能够使用的最大资源上限
cpu: "500m"
memory: "128Mi"

CPU单位:可以写m也可以写浮点数,例如0.5=500m,1=1000m

K8s会根据Request的值去查找有足够资源的Node来调度此Pod

k8s调度器会根据pod的资源限制(requests)以及各个节点的资源使用情况对pod进行调度

  • limits:用来限制容器中应用使用的最大资源限制
  • requests:容器需要的资源值,在容器分配时会作为资源分配的参考依据,通过此参数判断node是否能容纳当前容器
  • requests一般建议小于limits的20%~30%,且requests必须小于limits
  • 节点上limits的总和建议不要超过物理机实际配置的20%(实际情况应根据业务属性,流量等诸多因素进行压测、评估)
  • 当requests的值没有节点能够满足时,pod会一直处于pendding状态,等待有足够的资源才能够进行分配

容器资源限制

  • resources.limits.cpu
  • resources.limits.memory

容器使用的最小资源要求(作为容器调度时资源分配的依据)

  • resources.requests.cpu
  • resources.requests.memory

NodeSelector和NodeAffinity

NodeSelector

可以将Pod调度到匹配Label的Node上,如果没有匹配的标签就会调度失败

作用

  • 约束Pod到特定节点上运行
  • 完全匹配节点标签
  • 如果目标节点没有资源可调度,或者目标标签不存在,则Pod会处于Pending状态

应用场景

  • 专用节点:根据业务线将Node进行分组管理(打不同的Label)
  • 配备特殊硬件:根据硬件熟悉为Nodo打不同的标签,调度时进行特殊调度,如:SSD,GPU

示例

1
2
3
4
5
6
7
# 将节点打标签
$ kubectl label node kube-node-01.epc.isme.com disktype=ssd
$ kubectl get node --show-labels
NAME STATUS ROLES AGE VERSION LABELS
node01 Ready <none> 3m32s v1.21.0 disktype=ssd
# 创建资源清单模板
kubectl create deploy nginx-disk --image=nginx:1.17.10 --dry-run=client -o yaml > nginx-disk.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-disk
name: nginx-disk
spec:
replicas: 3
selector:
matchLabels:
app: nginx-disk
template:
metadata:
labels:
app: nginx-disk
spec:
nodeSelector:
disktype: "ssd"
containers:
- image: nginx:1.17.10
name: nginx
1
2
3
4
5
6
$ kubectl apply -f nginx-disk.yaml
$ kubectl get pod -l app=nginx-disk -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-disk-7579fd6f66-bv76w 1/1 Running 0 2m49s 10.244.89.132 node01 <none> <none>
nginx-disk-7579fd6f66-gcqlr 1/1 Running 0 2m49s 10.244.89.134 node01 <none> <none>
nginx-disk-7579fd6f66-pc2vd 1/1 Running 0 2m49s 10.244.89.133 node01 <none> <none>

NodeAffinity

用于节点亲和策略,和NodeSelector类似,可以根据节点上的Label来约束Pod可以调度到哪些节点上

  • 相比NodeSelector有更多的逻辑判断条件
  • 支持的操作:InNotInExistsDoesNotExistGtLt
  • 调度策略分为软策略和硬策略,不是完全的影响要求
    • 软策略(preferred):尝试满足策略
    • 硬策略(required):必须满足策略

示例

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
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-affinity
name: nginx-affinity
spec:
replicas: 3
selector:
matchLabels:
app: nginx-affinity
template:
metadata:
labels:
app: nginx-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: #硬限制必须满足含有gpu=nvidia-tesla标签
nodeSelectorTerms:
- matchExpressions:
- key: gpu
operator: In
values:
- nvidia-tesla
preferredDuringSchedulingIgnoredDuringExecution: # 软限制,没有的时候也能分配
- weight: 1
preference:
matchExpressions:
- key: group
operator: In
values:
- ai
containers:
- image: nginx:1.17.10
name: nginx
1
2
3
4
5
6
7
8
9
10
11
12
13
$ kubectl get pod -l app=nginx-affinity
# 没有gpu=nvidia-tesla的节点所以无法调度
NAME READY STATUS RESTARTS AGE
nginx-affinity-5d9b54bb6c-dz955 0/1 Pending 0 12s
nginx-affinity-5d9b54bb6c-fp99z 0/1 Pending 0 12s
nginx-affinity-5d9b54bb6c-knk8z 0/1 Pending 0 12s
$ kubectl label node kube-node-01.epc.isme.com gpu=nvidia-tesla
# 将节点打一个对应标签即可完成调度
$ kubectl get pod -l app=nginx-affinity
NAME READY STATUS RESTARTS AGE
nginx-affinity-5d9b54bb6c-dz955 1/1 Running 0 45s
nginx-affinity-5d9b54bb6c-fp99z 1/1 Running 0 45s
nginx-affinity-5d9b54bb6c-knk8z 1/1 Running 0 45s

Taints和Tolerations

  • Taints:给node设置污点,避免Pod被调度到这个节点上
  • Tolerations:给Pod设置容忍污点,允许Pod被调度到有污点的节点上

应用场景

  • 专用节点:根据业务情况将部分Node分组,需要Pod默认不要调度到这组节点上,只有配置了污点容忍的应用才会分配到这组node上
  • 配备特殊硬件:部分Node配有特殊硬件,如:SSD,GPU,希望默认情况下不调度节点
  • 基于污点的Pod驱逐

调度策略[effect]

1
2
3
4
5
6
7
# 添加污点
kubectl taint node [node] key:[effect]
kubectl taint node [node] key=value:[effect]
# 去掉污点
kubectl taint node [node] key:[effect]-
kubectl taint node [node] key=value:[effect]-
kubectl taint node [node] key-
  • NoSchedule: 一定不能被调度
  • PreferNoSchedule:尽量不要调度,可以不配置容忍
  • NoExecute:不仅不会调度,还会驱逐Node上已有的Pod

示例

1
2
3
4
5
6
7
8
# 给节点添加污点
kubectl taint node node01 gpu=yes:NoSchedule
# 查看节点污点
kubectl describe node node01 | grep Taint
Taints: gpu=yes:NoSchedule

# 去掉污点
kubectl taint node node01 gpu:NoSchedule-

如果希望Pod可以分配到带有污点的节点上,需要添加容忍字段tolerations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-taint
name: nginx-taint
spec:
replicas: 10
selector:
matchLabels:
app: nginx-taint
template:
metadata:
labels:
app: nginx-taint
spec:
containers:
- image: nginx:1.17.10
name: nginx
tolerations:
- key: "gpu"
operator: "Equal"
value: "yes"
effect: "NoSchedule"
1
2
3
4
5
# 会匹配key的所有污点
tolerations:
- key: "gpu"
operator: "Equal"
effect: "NoSchedule"

污点结论

公共部分

  • pod调度策略允许污点并且表达式满足污点匹配的情况下,pod可以调度到污点节点上
  • 已调度允许的pod更新后不满足污点策略时,污点上的pod会销毁重新调度
  • 同理,污点不匹配的pod更新后满足污点容忍也有可能重新调度到污点节点上
  • 去除节点的污点不会触发pod重新调度

当污点调度策略为NoSchedule

  • 已分配Pod的节点新增不匹配的污点,节点上的Pod不会被驱逐,更新了Pod的配置后如果不满足Pod才会重新调度

当污点调度策略为NoExecute

  • 添加污点时会驱逐节点上所有没有污点容忍或污点容忍不匹配的Pod

有多个污点的情况

  • 多个污点只有一个满足容忍的情况下,Pod无法调度
  • 已调度到污点上的Pod
    • 节点新增NoSchedule策略不匹配污点,Pod不会重新调度
    • 节点新增NoExecute策略不匹配污点,Pod会重新调度
  • 多个污点都容忍的情况下Pod才能调度到污点节点

NodeName

指定节点名称,用于将Pod调度到指定的Node上,不经过调度器

  • 因此这种方式无视污点和节点标签,直接进行部署
1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Pod
metadata:
name: nginx-nodenam
labels:
app: nginx-nodename
spec:
nodeName: node03
containers:
- name: nginx
image: nginx:1.17.10
1
2
3
4
$ kubectl apply -f nginx-nodename.yaml
$ kubectl get pod -l app=nginx-nodename -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-nodenam 1/1 Running 0 19s 10.244.199.197 node03 <none> <none>

DaemonSet控制器

  • 会在每一个Node上运行一个Pod,且只会运行一个
  • 新加入到集群中的新Node也会自动创建运行一个Pod

image-20210908140733439

应用场景

  • 网络组件
  • 监控Agent
  • 日志Agent

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: filebeat
namespace: kube-system
spec:
selector:
matchLabels:
name: filebeat
template:
metadata:
labels:
name: filebeat
spec:
containers:
- name: log
image: elastic/filebeat:7.3.2
1
2
3
4
5
$ kubectl apply -f filebeat.yaml
$ kubectl get pod -l name=filebeat -n kube-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
filebeat-9qlmn 1/1 Running 0 106s 10.244.89.138 node01 <none> <none>
filebeat-f28qs 1/1 Running 0 106s 10.244.115.37 node02 <none> <none>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
app: nginx-daemonset
name: nginx-daemonset
spec:
selector:
matchLabels:
app: nginx-daemonset
template:
metadata:
labels:
app: nginx-daemonset
spec:
containers:
- image: nginx:1.17.10
name: nginx

调度失败的原因分析

1
2
3
4
# 查看Pod的调度结果
kubectl get pod <NAME> -o wide
# 查看调度失败原因
kubectl describe pod <NAME>

调度失败可能的原因

  • 节点资源不足(CPU、内存等)
  • 节点有污点,切调度策略没有容忍污点
  • 没有匹配到节点标签