Pod 被创建出来后需要选择一个合适的节点来运行这个 Pod ,这个步骤由 Scheduler 负责完成。在没有特别设置策略的情况下系统会使用默认的调度策略,除了默认的调度策略,系统还提供了多种配置方法,供用户配置 Pod 的特定调度逻辑。下面来具体介绍常用的调度策略。
1. 基于 Node Label 的调度策略
在 Pod 的配置中,可以方便地使用 LabelSelector 来声明需要调度到具有指定 Label 的 Node上,调度器会在具有这些 Label 的 Node 中再进行选择。
给 Node 设置 Label 的语法如下:
kubectl label nodes <node-name> <label-key>=<label-value>
Bash查看节点标签命令:
# 查看所有节点的标签
kubectl get nodes --show-labels
# 查看某个特定节点的标签
kubectl get nodes <node-name> --show-labels
# 查看带某个标签的节点
kubectl get nodes -l gpu=true
Bash删除节点标签命令:
kubectl label node <node-name> <label-key>-
Bash示例:
希望 mysql 调度到一个具有 SSD 磁盘的目标节点,就可以用 NodeSelector。
- 给带有 ssd 磁盘的物理节点打上自定义标签 disk=ssd。
kubectl label nodes k8s-node-01 disk=ssd
Bash- Pod 模版中设定 NodeSelecto r的值为 disk=ssd,则会调度到指定节点。
# node-selector-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-pod
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql
imagePullPolicy: Always
nodeSelector:
disk: ssd
Bash注意:
- 如果我们给多个 Node 都定义了相同的标签(disk=ssd),则 scheduler 会根据调度算法从这组 Node 中挑选一个可用的 Node 进行Pod 调度。
- NodeSelector 一定会调度到包含标签的节点,没有这种节点则调度失败,Pod 无法创建。
- 物理节点上有一些预定义标签,你也可以直接选中这些标签。
2. 亲和性与反亲和性调度策略
2.1. 核心概念
- 亲和性:让 Pod 靠近某些其他 Pod 或 Node。
- 反亲和性:让 Pod 远离某些其他 Pod 或 Node。
这两种策略主要在两个层面上工作:
- 节点亲和性:基于节点标签来调度 Pod。
- Pod 亲和性/反亲和性:基于已在节点上运行的 Pod 的标签来调度 Pod。
两个层面的亲和性与反亲和性,都对应有两种具体的策略:
- 软策略:在 Pod 调度时可以尽量满足其规则,在无法满足规则时,可以调度到一个不匹配规则的节点上,并且可以设置权重。
- 硬策略:是 Pod 调度时必须满足的规则,否则 Pod 对象的状态会一直是 Pending。
2.2. 节点亲和 nodeAffinity
节点亲和性类似于 nodeSelector
,但功能更强大、表达力更强,硬策略与软策略可以一起用,也可以单独使用。语法结构如下:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-west-2a
preferredDuringSchedulingIgnoredDuringExecution: # 软策略
- weight: 1
preference:
matchExpressions:
- key: another-node-label
operator: In
values:
- another-value
YAML关键字段解析:
requiredDuringScheduling...
(硬策略):- 必须满足的条件。如果找不到匹配的节点,Pod 将一直处于 Pending 状态。
- 可以设置多个
nodeSelectorTerms
,它们之间是 逻辑或 的关系。只需要有一个条件能匹配就能完成 Pod 的调度。 - 可以设置多个
matchExpressions
,它们之间是 逻辑与 的关系。必须满足所有条件才能完成 Pod 的调度。
preferredDuringScheduling...
(软策略):- 倾向于满足的条件。调度器会尝试寻找满足条件的节点,但如果找不到,也会调度到其他节点上。
weight
:权重,范围 1-100。在多个软策略同时存在时,调度器会优先选择权重更高的节点。
operator
(操作符):In
:标签值在列表中NotIn
:标签值不在列表中Exists
:标签存在(不关心值是什么)DoesNotExist
:标签不存在,此时不需要设置 values。Gt
(Greater than):值大于某个值(用于数值)Lt
(Less than):值小于某个值(用于数值)
- 需要注意的是软硬策略名字中后半段字符串 IgnoredDuringExecution 表示的是,在 Pod 资源基于节点亲和性规则调度到某个节点之后,如果节点的标签发生了改变,该 Pod 不再符合该节点的亲和性要求,调度器不会将 Pod 从该节点上移除,因为该规则仅对新建的Pod对象有效。
示例:
- 配置节点亲和策略
# node-affinity-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-affinity
labels:
app: node-affinity
spec:
replicas: 8
selector:
matchLabels:
app: node-affinity
template:
metadata:
labels:
app: node-affinity
spec:
containers:
- name: nginx
image: nginx:1.26.2
ports:
- containerPort: 80
name: nginxweb
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略:必须满足
nodeSelectorTerms:
- matchExpressions:
- key: node_type
operator: In
values:
- gpu
preferredDuringSchedulingIgnoredDuringExecution: # 软策略:尽量满足
- weight: 1 # 软策略可以定义权重,执行时权重值高的优先匹配
preference:
matchExpressions:
- key: preference
operator: In
values:
- test
YAML- 节点打上标签
kubectl label nodes k8s-node-01 node_type=gpu
node/k8s-node-01 labeled
kubectl label nodes k8s-node-02 node_type=gpu
node/k8s-node-02 labeled
kubectl label nodes k8s-node-01 preference=test
node/k8s-node-01 labeled
Bash说明:必须调度到 node_type=gpu 的节点上,如果这些节点里满足 preference=test 的话就尽量调度到该节点上。
# 可以看到全部调度到了 k8s-node-01 节点上
kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
node-affinity-544ffcb4f6-5nbl9 1/1 Running 0 97s 10.244.154.220 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-5tlgq 1/1 Running 0 97s 10.244.154.222 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-8rsb7 1/1 Running 0 97s 10.244.154.224 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-8xlp5 1/1 Running 0 97s 10.244.154.218 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-bf6pq 1/1 Running 0 97s 10.244.154.221 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-fgcjk 1/1 Running 0 97s 10.244.154.217 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-jcpdh 1/1 Running 0 97s 10.244.154.223 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-wcgfn 1/1 Running 0 97s 10.244.154.219 k8s-node-01 <none> <none>
# 增加副本数量,当 k8s-node-01 资源不足时就会调度到 k8s-node-02 上
kubectl scale deployment node-affinity --replicas=30
kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
node-affinity-544ffcb4f6-5nbl9 1/1 Running 0 5m16s 10.244.154.220 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-5tlgq 1/1 Running 0 5m16s 10.244.154.222 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-6k72l 1/1 Running 0 43s 10.244.154.228 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-8rsb7 1/1 Running 0 5m16s 10.244.154.224 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-8shq8 1/1 Running 0 43s 10.244.154.232 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-8xlp5 1/1 Running 0 5m16s 10.244.154.218 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-bf6pq 1/1 Running 0 5m16s 10.244.154.221 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-blgcr 1/1 Running 0 43s 10.244.154.229 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-bxk7b 1/1 Running 0 6s 10.244.154.242 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-dn2hl 1/1 Running 0 43s 10.244.154.227 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-fgcjk 1/1 Running 0 5m16s 10.244.154.217 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-fpc59 1/1 Running 0 6s 10.244.154.236 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-fsttz 1/1 Running 0 43s 10.244.154.225 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-gtqwp 1/1 Running 0 6s 10.244.154.238 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-hrqcz 1/1 Running 0 43s 10.244.154.226 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-j6lgh 1/1 Running 0 6s 10.244.154.240 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-jcpdh 1/1 Running 0 5m16s 10.244.154.223 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-jgpbm 1/1 Running 0 43s 10.244.44.225 k8s-node-02 <none> <none>
node-affinity-544ffcb4f6-lblfq 1/1 Running 0 43s 10.244.154.230 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-lq7p8 1/1 Running 0 6s 10.244.154.241 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-m8mqh 1/1 Running 0 43s 10.244.154.234 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-nxwrb 1/1 Running 0 6s 10.244.154.239 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-pxb2k 1/1 Running 0 6s 10.244.154.237 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-tpql8 1/1 Running 0 43s 10.244.154.233 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-tv4r6 1/1 Running 0 43s 10.244.44.226 k8s-node-02 <none> <none>
node-affinity-544ffcb4f6-v64cp 1/1 Running 0 6s 10.244.154.235 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-vhmd4 1/1 Running 0 43s 10.244.154.231 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-vrf6c 1/1 Running 0 6s 10.244.44.228 k8s-node-02 <none> <none>
node-affinity-544ffcb4f6-wcgfn 1/1 Running 0 5m16s 10.244.154.219 k8s-node-01 <none> <none>
node-affinity-544ffcb4f6-wztbt 1/1 Running 0 6s 10.244.44.227 k8s-node-02 <none> <none>
Bash2.3. 节点反亲和
对于 Node 亲和性来说,虽然没有反亲和性的字段,但是可以用 NotIn 和 DoesNotExist 就能实现反亲和性的要求。
2.4. Pod 亲和性与反亲和性
这允许根据已经运行在节点上的 Pod 来调度新的 Pod,而不是根据节点本身的标签。
2.4.1. 语法结构
spec:
affinity:
podAffinity: # Pod 亲和性(靠近)
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cache
topologyKey: topology.kubernetes.io/zone
podAntiAffinity: # Pod 反亲和性(远离)
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web
topologyKey: kubernetes.io/hostname
YAML关键字段解析:
labelSelector
:用于选择需要”靠近”或”远离”的 Pod 的标签。topologyKey
:这是最关键的概念。它定义了”域”或”拓扑”的边界。
2.4.2. 拓扑域
Pod 亲和性调度需要各个相关的 Pod 对象运行于”同一位置”, 而反亲和性调度则要求他们不能运行于”同一位置”,而这个位置就是通过 topologyKey 字段指定的一个拓扑域。
那么,什么是拓扑域,如何理解这个新概念呢?一个拓扑域由一些 Node 组成,这些 Node 通常有相同的地理空间坐标。比如:
- 在同一个机架、机房或地区。
- 在某些情况下,我们也可以认为一个 Node 就是一个拓扑区域。
为此,Kubernetes 为 Node 内置了一些常用的用于表示拓扑域概念的 Label。
- kubernetes.io/hostname:这个 Label 的值就会被设置为 Node 的 hostname,表示一个节点就是一个拓扑域。
公有云厂商还会为 Node 节点打上下面两种标签,来标识拓扑域:
- topology.kubernetes.io/zone:可用区级别。
- topology.kubernetes.io/region:区域级别,范围更大。
定义节点归属的拓扑域:
具体如何定义 Zone 及确定 Zone 包含的 Node,只要通过给 Node 打标签即可完成。常规做法是给 Node 增加 zone=zonename 的 Label,当然也可以使用系统自动设置的 topology.kubernetes.io/zone、topology.kubernetes.io/region 等标签。
例如,通过下面的zone标签设置(如下代码所示),可以认为集群中存在两个zone,分别是zoneA(包括nodel与node2)和zoneB(node3与node4)。
NAME STATUS LABELS
nodel Ready node-nodel,zone-zoneA
node2 Ready node-node2,zone-zoneA
node3 Ready node-node3,zone-zoneB
node4 Ready node-node4,zone-zoneB
Bash2.4.3. Pod 亲和调度—硬策略
- 准备好参照物 Pod,该 Pod 拥有标签 security=s1 和 app=nginx,后面的例子将使用该Pod 作为亲和的参照物。
# pod-flag.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
security: "s1"
app: "nginx"
name: pod-flag
spec:
containers:
- name: myapp
image: nginx:1.26.2
YAML- 部署查看
$ kubectl apply -f pod-flag.yaml
pod/pod-flag created
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-flag 1/1 Running 0 62s 10.244.154.243 k8s-node-01 <none> <none>
Bash- Pod 亲和性调度—硬策略示例如下
# required-podAffinity-pod1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pod-affinity
namespace: default
labels:
app: pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: pod-affinity
template:
metadata:
labels:
app: pod-affinity
spec:
containers:
- name: nginx
image: nginx:1.26.2
ports:
- containerPort: 80
name: nginxweb
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: kubernetes.io/hostname # 指定拓扑域
# namespaces:
# - kube-system # 则会去该名称空间下查找带有标签app=nginx的pod,与其亲和,不设置则默认从本pod当前的名称空间下找
YAML上述亲和性硬策略表示:新 pod 必须被调度到一个带有 app=nginx 标签的 pod 所在的拓扑域中。因为我们此时用的拓扑域为 kubernetes.io/hostname ,每台机器上打的这个标签对应的值均不同都是自己的主机名,所以用该 key 做拓扑域的话,拓扑域中只包含唯一一台机器。
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-affinity-7d8c4bf586-4vg2v 1/1 Running 0 21s 10.244.154.244 k8s-node-01 <none> <none>
pod-affinity-7d8c4bf586-bt8hs 1/1 Running 0 21s 10.244.154.245 k8s-node-01 <none> <none>
pod-affinity-7d8c4bf586-pvbk2 1/1 Running 0 21s 10.244.154.246 k8s-node-01 <none> <none>
pod-flag 1/1 Running 0 11m 10.244.154.243 k8s-node-01 <none> <none>
Bash- 你可以把参照物 pod 删掉,然后重新部署会发现一直处于 pending 状态。
2.4.4. Pod 亲和调度—软策略
- 准备好两个参照物 pod,以便我们演示多条软策略的权重与逻辑或的关系。
# pod-flag1.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
app: "nginx"
name: pod-flag1
spec:
containers:
- name: myapp1
image: nginx:1.26.2
# pod-flag2.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
app: "db"
name: pod-flag2
spec:
containers:
- name: myapp2
image: nginx:1.26.2
YAML- 查看
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-flag1 1/1 Running 0 59s 10.244.154.247 k8s-node-01 <none> <none>
pod-flag2 1/1 Running 0 55s 10.244.151.140 k8s-master-01 <none> <none>
Bash- Pod 亲和性调度—软策略示例如下
# deploy-with-preferred-podAffinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pod-affinity
labels:
app: pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: pod-affinity
template:
metadata:
labels:
app: pod-affinity
spec:
containers:
- name: nginx
image: nginx:1.26.2
ports:
- containerPort: 80
name: nginxweb
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: ["nginx"]
topologyKey: kubernetes.io/hostname # 指定拓扑域
- weight: 20
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: ["db"]
topologyKey: kubernetes.io/hostname
YAML上述的清单配置当中,pod 的软亲和调度需要将 Pod 调度到标签为 app=cache 的 pod 所在的拓扑域当中,或者调度到 app=db 标签的 pod 所在的拓扑域中,若不存在此类标签,调度器会根据软亲和调度进行随机调度到一个节点之上。
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-affinity-55c4945fdd-22gwh 1/1 Running 0 40s 10.244.154.249 k8s-node-01 <none> <none>
pod-affinity-55c4945fdd-6xd7q 1/1 Running 0 40s 10.244.154.250 k8s-node-01 <none> <none>
pod-affinity-55c4945fdd-zbbkk 1/1 Running 0 40s 10.244.154.248 k8s-node-01 <none> <none>
pod-flag1 1/1 Running 0 7m4s 10.244.154.247 k8s-node-01 <none> <none>
pod-flag2 1/1 Running 0 7m 10.244.151.140 k8s-master-01 <none> <none>
Bash- 调整权重,重新部署
apiVersion: apps/v1
kind: Deployment
metadata:
name: pod-affinity
labels:
app: pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: pod-affinity
template:
metadata:
labels:
app: pod-affinity
spec:
containers:
- name: nginx
image: nginx:1.26.2
ports:
- containerPort: 80
name: nginxweb
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 20
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: ["nginx"]
topologyKey: kubernetes.io/hostname # 指定拓扑域
- weight: 80
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: ["db"]
topologyKey: kubernetes.io/hostname
YAML$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-affinity-5958f888b8-q72wm 1/1 Running 0 16s 10.244.151.143 k8s-master-01 <none> <none>
pod-affinity-5958f888b8-sbvw2 1/1 Running 0 16s 10.244.151.141 k8s-master-01 <none> <none>
pod-affinity-5958f888b8-xbm48 1/1 Running 0 16s 10.244.151.142 k8s-master-01 <none> <none>
pod-flag1 1/1 Running 0 10m 10.244.154.247 k8s-node-01 <none> <none>
pod-flag2 1/1 Running 0 10m 10.244.151.140 k8s-master-01 <none> <none>
Bash- 删除参照物 pod,然后重新部署还是能正常启动。
2.4.5. Pod 反亲和调度
- 准备参照物 Pod,这里直接使用上文的 pod-flag.yaml。
- Pod反亲和示例如下
# pod-antiaffinity-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pod-affinity
labels:
app: pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: pod-affinity
template:
metadata:
labels:
app: pod-affinity
spec:
containers:
- name: nginx
image: nginx:1.26.2
ports:
- containerPort: 80
name: nginxweb
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
- labelSelector:
matchExpressions:
- key: app
operator: In
values: ["nginx"]
topologyKey: kubernetes.io/hostname
YAML- 查看
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-affinity-8dd454d44-dmdvz 1/1 Running 0 12s 10.244.95.11 k8s-master-02 <none> <none>
pod-affinity-8dd454d44-n24n8 1/1 Running 0 12s 10.244.151.145 k8s-master-01 <none> <none>
pod-affinity-8dd454d44-vsnd2 1/1 Running 0 12s 10.244.44.229 k8s-node-02 <none> <none>
pod-flag 1/1 Running 0 48s 10.244.154.253 k8s-node-01 <none> <none>
# 3个新的 pod 副本没有一个被调度到 app=nginx 标签的 pod 所在的主机域 k8s-node-01 上
Bash3. 指定 Node 名称的定向调度策略
Kubernetes 还支持在 Pod 的配置中通过 nodeName 字段指定要求调度的目标 Node 名称,即要求这个 Pod 只能调度到指定的 Node 上。这个字段的优先级比 nodeSelector 和亲和性策略都高。设置了nodeName 字段的 Pod将不参与调度器的调度过程,相当于已经完成了调度,系统将直接通知目标 Node 的 kubelet 开始创建这个 Pod。示例如下:
apiversion: v1
kind: Pod
metadata:
name:nginx
spec:
containers:
- name: nginx
image: nginx
nodeName: k8s-node-01
YAML使用nodeName 具有以下限制:
- 如果指定的 Node 不存在或者失联,则 Pod 将无法运行。
- 如果指定的 Node 资源不足,则 Pod 可能会运行失败。
- 在某些云环境下,Node 名称不一定是稳定的。
4. 污点与容忍度的调度策略
4.1. 污点与容忍介绍
- 污点(taints)
- 对于 nodeAffinity 无论是硬策略还是软策略方式,都是调度 pod 到预期节点上,而 Taints 污点恰好与之相反,如果一个节点标记为 Taints ,除非 pod 也被标识为可以容忍污点节点,否则该 Taints 节点不会被调度 pod。
- 容忍度(tolerations)
- 污点(taints)是定义在节点上的一组键值型属性数据,用来让节点拒绝将 Pod 调度到该节点上,除非该 Pod 对象具有容纳节点污点的容忍度。而容忍度(tolerations)是定义在 Pod 对象上的键值型数据,用来配置让 Pod 对象可以容忍节点的污点。
污点与容忍的应用场景:
比如用户希望把 Master 节点保留给 Kubernetes 系统组件使用,或者把一组具有特殊硬件设备的节点预留给某些 pod,或者让一部分节点专门给一些特定应用使用/独占,则可以为这些节点设置污点,pod 不会再被调度到 taint 标记过的节点。
4.2. 节点污点设置
污点是定义是在节点上的,而容忍度的定义是在 Pod 中的 Spec 字段下,都属于键值型数据,两种方式都支持一个 effect 标记,语法格式为 key=value:effect
。
# 语法
kubectl taint nodes <node_name> key=value:effect
# 示例
kubectl taint nodes node1 key=value:NoSchedule # 该污点Taint的键为key,值为value,该Taint的效果是 NoSchedule
kubectl taint nodes node1 key=:NoSchedule # value 可以省略,代表空值
Bash其中 effect 是用来定义对 Pod 对象的排斥等级,主要包含以下3种类型:
- NoSchedule:一定不被调度。属于强制约束,节点现存的 Pod 对象不受影响。
- PreferNoSchedule:NoSchedule 的软策略版本,表示尽量不调度到污点节点上去。
- NoExecute:该选项意味着一旦 Taint 生效,如该节点内正在运行的 pod 没有对应 Tolerate 设置,会直接被逐出 ,属于强制约束。
查看节点污点:
# 查看所有节点的污点信息
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
# 查看特定节点的污点
kubectl describe node <节点名称> | grep Taints
# 查看所有节点的污点(json 格式)
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.taints}{"\n"}{end}'
Bash去掉污点:
# 语法:kubectl taint node <节点名称> <key>[:<effect>]-
# 删除特定key的所有污点
kubectl taint node node1 key1-
# 删除特定key和effect的污点
kubectl taint node node1 key1=value1:NoSchedule-
# 删除所有污点
kubectl taint node node1 --all-
Bash4.3. Pod 设置容忍
在 Pod 对象上定义容忍度时,其支持2种 operator 操作符:Equal 和 Exists。
- Equal:等值比较,表示容忍度和污点必须在 key、value、effect 三者之上完全匹配。
- Exists:存在性判断,表示二者的 key 和 effect 必须完全匹配,而容忍度中的 value 字段使用空值。
- 如果不指定 operator 属性,则默认值为 Equal。
另外,还有两个特例:
- 空的 key 如果再配合 Exists 就能匹配所有的 key 与 value,也是是能容忍所有 node 的所有Taints。
- 空的 effect 匹配所有的 effect。
Pod 配置容忍示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: taint
labels:
app: taint
spec:
replicas: 6
selector:
matchLabels:
app: taint
template:
metadata:
labels:
app: taint
spec:
containers:
- name: nginx
image: nginx:1.26.2
ports:
- name: http
containerPort: 80
tolerations:
- key: "test"
operator: "Exists"
effect: "NoSchedule"
- key: "test"
operator: "Equal"
value: "value1"
effect: "NoExecute"
YAML4.4. NoExecute Taint Pod 驱逐时间
前面提到 NoExecute 这个 Taint 的效果会对节点上正在运行的 pod 有以下影响:
- 没有设置 Toleration 容忍的 Pod 会被立即驱逐。
- 配置了对应 Toleration 的 Pod,并且没有为 tolerationSeconds 赋值的 Pod,则会一直保留在该节点上。
- 配置了对应 Toleration 的 Pod,并且为 tolerationSeconds 赋了值的 Pod,则会在指定的时间后被驱逐。
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: nginx
image: nginx
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
# 没有设置 tolerationSeconds,表示无限期容忍
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: nginx
image: nginx
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
tolerationSeconds: 60 # 容忍60秒后被驱逐
YAML5. Pod 基于拓扑域均匀分布调度策略
5.1. 拓扑分布约束详解
前面介绍的 Pod 调度策略主要基于 Pod 与 Node 的关系、Pod 与 Pod 的关系进行管理。而有时候,可以将集群内的 Node 有意地划分为不同的拓扑域(Topology Area ),例如可以将某些 Node 标记为不同的 Region(地域)、Zone(区域)、Rack(机架)等逻辑概念,以使应用在多个区域的环境下实现负载均衡、高可用、容灾等要求。从 Kubernetes v1.18 版本开始,可以使用基于拓扑信息来均匀分布 Pod 的调度机制,在 Pod 的 spec.topologySpreadConstraints 字段进行设置,为 Pod 在多个拓扑域中如何调度提供更加精细化的管理策略。
topologySpreadConstraints 字段包含的配置信息如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-spec
spec:
topologySpreadConstraints:
- maxSkew: <integer>
minDomains: <integer> # 可选
topologyKey: <string>
whenUnsatisfiable: <string>
labelSelector:
matchLabels: <object>
matchLabelKeys: # 可选
- <list>
nodeAffinityPolicy: [Honor|Ignore] # 可选
nodeTaintsPolicy: [Honor|Ignore] # 可选
YAML- maxSkew:定义不同拓扑域中 Pod 数量之间允许的最大偏差。必须大于零。
- 作用:这是控制分布均匀性的核心参数。 skew 越小,分布越均匀。
- 示例:
maxSkew: 1
:表示任意两个拓扑域中的 Pod 数量相差不能超过 1。这是最严格的约束。maxSkew: 2
:表示相差不能超过 2,允许更宽松的分布。
- topologyKey:节点标签的键。系统根据这个键来划分拓扑域。
- whenUnsatisfiable:当调度器无法满足约束时应该采取的行动。
- 可选值:
- DoNotSchedule(默认):硬约束。如果找不到满足条件的节点,则不调度 Pod(保持
Pending
状态)。适用于必须满足分布要求的场景。 - ScheduleAnyway:软约束。仍然调度 Pod,但会优先选择能降低 Skew 值的节点。适用于尽量满足但不强求的场景。
- DoNotSchedule(默认):硬约束。如果找不到满足条件的节点,则不调度 Pod(保持
- 可选值:
- labelSelector:用于选择一组 Pod,计算它们在不同拓扑域中的分布情况。
- 作用:定义“哪些 Pod 需要被均匀分布”。
- minDomains:需要匹配的最小拓扑域数量。只有在至少有这么多个域时,约束才会被启用。
- 应用场景:例如,希望应用至少跨 3 个可用区部署,但如果集群只有 2 个可用区,这个约束就不生效,避免 Pod 无法调度。
- 注意:通常需要与
whenUnsatisfiable: DoNotSchedule
一起使用。
- matchLabelKeys:一个 Pod 标签键的列表。调度器会根据这些键的值来进一步分组 Pod,并在每个组内独立计算拓扑分布。
- 作用:实现更细粒度的“按X均匀分布”。例如,实现“同一服务的不同版本”在节点间均匀分布。
- nodeAffinityPolicy:定义在计算拓扑分布时,是否考虑节点的亲和性选择结果。
- Honor(默认):只考虑那些符合 Pod 的
nodeAffinity
/nodeSelector
规则的节点来进行分布计算。这是安全的选择。 - Ignore:忽略 nodeAffinity/nodeSelector,在所有节点上计算分布情况。可能更公平,但需谨慎使用。
- 不设置 nodeAffinityPolicy 时等同于选择 Honor。
- Honor(默认):只考虑那些符合 Pod 的
- nodeTaintsPolicy:定义在计算拓扑分布时,是否考虑节点的污点。
- Honor(默认):只考虑那些该 Pod 能容忍其污点的节点来进行分布计算。这是安全的选择。
- Ignore:忽略污点,在所有节点上计算分布情况。
- 不设置 nodeTaintsPolicy 时等同于选择 Ignore。
综合示例与解释:
apiVersion: v1
kind: Pod
metadata:
name: my-web-server
labels:
app: web
version: "3.0"
spec:
containers:
- name: nginx
image: nginx:1.26.2
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: web
nodeAffinityPolicy: Honor
nodeTaintsPolicy: Honor
YAML- 目标:将新创建的标签为
app: web
的这个 Pod 均匀地分布到不同的可用区。 - 均匀程度:任意两个可用区中运行的
web
Pod 数量相差不能超过 1 个 (maxSkew: 1
)。 - 调度策略:如果找不到能满足这个分布要求的节点,Pod 就不调度 (
DoNotSchedule
)。 - 计算范围:只会在该 Pod 能调度上去的节点(满足亲和性且容忍污点)上计算这个分布。
注意:topologySpreadConstraint.labelSelector 的搜索范围仅限于新建 Pod 所在的命名空间。
5.2. Descheduler 介绍
Descheduler 是一个均衡器,用来解决 k8s 运行过程中 Pod 分布的不均匀问题,之前学习的均匀分布机制,只负责创建时的均匀,不负责运行时。
为什么需要 Descheduler?
集群状态是动态变化的,初始的最佳调度可能随着时间推移变得不再最优:
- 节点故障或下线:导致 Pod 集中到少数节点。
- 新节点加入:旧 Pod 不会自动平衡到新的空节点上。
- 拓扑分布变化:初始调度后,Pod 分布可能不再满足高可用要求。
- 资源使用变化:某些节点可能从低负载变为高负载。
- 亲和性/反亲和性违规:后期创建的 Pod 可能导致之前的 Pod 违反规则。
Descheduler 的核心原理:
- Descheduler 本身并不会参与调度,它只是计算出该驱逐的 Pod 进行驱逐。
- 驱逐后重建新 Pod 的调度的任何还是会交给默认的调度器 kube-scheduler 来完成。
部署 Descheduler:
最常见的方式是使用 Helm chart 安装到 kube-system
命名空间。
helm repo add descheduler https://kubernetes-sigs.github.io/descheduler/
helm repo update
helm install descheduler descheduler/descheduler \
-n kube-system \
--set schedule="*/60 * * * *" # 每60分钟运行一次
# Descheduler 以 CronJob 方式运行。
Bash查看默认的驱逐策略:
kubectl get configmap -n kube-system descheduler -o yaml
BashapiVersion: v1
kind: ConfigMap
metadata:
...
data:
policy.yaml: |
apiVersion: "descheduler/v1alpha2"
kind: "DeschedulerPolicy"
profiles:
- name: default
pluginConfig:
- args:
evictLocalStoragePods: true
ignorePvcPods: true
name: DefaultEvictor
- name: RemoveDuplicates
- args:
includingInitContainers: true
podRestartThreshold: 100
name: RemovePodsHavingTooManyRestarts
- args:
nodeAffinityType:
- requiredDuringSchedulingIgnoredDuringExecution
name: RemovePodsViolatingNodeAffinity
- name: RemovePodsViolatingNodeTaints
- name: RemovePodsViolatingInterPodAntiAffinity
- name: RemovePodsViolatingTopologySpreadConstraint
- args:
targetThresholds:
cpu: 50
memory: 50
pods: 50
thresholds:
cpu: 20
memory: 20
pods: 20
name: LowNodeUtilization
plugins: # 启用的相关插件/策略
balance:
enabled:
- RemoveDuplicates # 删除调度到同一节点上的相同 Pod(通常指具有相同 Pod Template Hash 的 Pod),以避免不必要的冗余和资源浪费。
- RemovePodsViolatingTopologySpreadConstraint # 移除违反拓扑分布约束(Topology Spread Constraints)的 Pod,以确保 Pod 在集群中的分布更加均匀。
- LowNodeUtilization # 将工作负载从资源利用率低的节点迁移到其他节点,以释放资源或关闭低利用率节点。
deschedule:
enabled:
- RemovePodsHavingTooManyRestarts # 移除因频繁重启而可能存在问题的 Pod。
- RemovePodsViolatingNodeTaints # 移除违反节点污点(Node Taints)规则的 Pod。
- RemovePodsViolatingNodeAffinity # 移除违反节点亲和性(Node Affinity)规则的 Pod。
- RemovePodsViolatingInterPodAntiAffinity # :移除违反 Pod 间反亲和性(Inter-Pod Anti-Affinity)规则的 Pod。
YAML注意事项:
- 引入 Descheduler 之后,它会根据策略对 Pod 进行驱逐以便进行重新调度,使 k8s 的资源达到一个平衡状态。这样可能会使服务中断,强烈建议给应用创建一个对应的 PDB 对象进行保护。
- 默认情况下,Descheduler 不会驱逐
kube-system
命名空间下的关键系统 Pod。 - 它不会驱逐不受控制器管理的 Pod(如裸 Pod),以及有本地存储的 Pod等。
PDB 配置示例:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: pdb-demo
namespace: default
spec:
maxUnavailable: 1 # 设置最多不可用的副本数量,或者使用 minAvailable,可以使用整数或百分比
selector:
matchLabels: # 匹配Pod标签------------》针对被选中的pod,最大不可用副本为1
app: demo
YAML关于 PDB 的更多详细信息可以查看官方文档:https://kubernetes.io/docs/tasks/run-application/configure-pdb/。
6. Pod优先级和抢占调度策略
6.1. 核心概念
什么是 Pod 优先级?
Pod 优先级表示一个 Pod 相对于其他 Pod 的重要性。它是一个整数值(priorityValue
),值越大,优先级越高。
什么是抢占?
抢占是调度器的一种行为。当一个高优先级 Pod(我们称其为 “抢占者”)因为资源不足而无法被调度到任何节点时,调度器会尝试驱逐一个或多个低优先级 Pod 来自目标节点,从而为高优先级 Pod 腾出空间。
6.2. 配置和使用
定义 PriorityClass:
优先级通过 PriorityClass
对象定义,它是一种集群范围的、非命名空间的资源。
示例:创建一个高优先级类
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority # PriorityClass 的名称
value: 1000000 # 32位整数值,通常<10e6,值越大优先级越高。系统类如 system-cluster-critical 的值是 2000000000。
globalDefault: false # 可选字段。如果设置为 true,则此 PriorityClass 将成为所有未设置 priorityClassName 的 Pod 的默认优先级。整个集群中只能有一个 PriorityClass 的此字段为 true。
description: "此优先级类用于非常重要的服务Pod,可以抢占低优先级Pod。" # 可选描述
YAML示例:创建一个低优先级类
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: low-priority
value: 100
globalDefault: false
description: "用于非关键的批处理作业Pod。"
YAML在 Pod 中引用 PriorityClass:
apiVersion: v1
kind: Pod
metadata:
name: important-app
spec:
containers:
- name: nginx
image: nginx
priorityClassName: high-priority # 关键:指定使用之前创建的 high-priority 类
YAML