Kubernetes Pod Scheduler
学习参考:调度、抢占和驱逐
环境准备
bash
root@master30:~# kubectl create ns scheduler
root@master30:~# kubectl config set-context --current --namespace scheduler
一、调度介绍
K8s 调度:把 Pod 分配到合适的 Node 节点上运行 。 kube‑scheduler 是集群默认调度器 ,通过 Watch 监听还没有分配节点的 Pending 状态 Pod,选出合适节点。
二、直接创建 Pod 过程
精简流程
- 客户端发送创建Pod请求至kube-apiserver;
- apiserver完成认证、授权、准入校验,合法则把Pod存入etcd,Pod为Pending;
- kube-scheduler监听到未调度Pod,执行过滤、打分选出最优节点,向apiserver发起绑定;
- apiserver更新Pod,写入节点名称;
- 对应节点kubelet感知Pod绑定到本机,调用CNI准备网络、CSI准备存储;
- kubelet调用容器运行时拉取镜像、启动容器;
- kubelet检测容器正常运行,通知apiserver将Pod状态更新为Running。
三、通过控制器创建 Pod 过程
精简版本
重要区分:**controller‑manager 只负责增删 Pod,不做调度,不启动容器;调度交给 kube‑scheduler**
- 用户通过kubectl提交Deployment创建请求至apiserver;
- apiserver完成认证授权准入校验后将Deployment存入etcd。
- kube-controller-manager内的Deployment控制器监听到Deployment,对比期望副本数与现有Pod数量,缺失则调用apiserver创建Pending状态Pod存入etcd。
- kube-scheduler监听到未调度Pod,筛选选出最优节点并发起绑定;
- apiserver更新Pod绑定节点名称。
- 对应节点kubelet感知Pod调度到本机,准备存储与网络,调用容器运行时拉取镜像启动容器;
- kubelet检测容器正常运行后更新Pod状态为Running。
精简链路: Client → kube‑apiserver → etcd → kube‑controller‑manager → kube‑scheduler → kubelet → containerd
四、Pod 调度过程
Kubernetes 使用以下策略挑选出适合运行pod的最佳节点:
- 过滤,将满足 Pod 调度需求的所有节点选出来。
- 打分,根据当前启用的打分规则,调度器会给每一个可调度节点进行打分,调度器将 Pod 调度到得分最高的节点上。 如果存在多个得分最高的节点 ,kube-scheduler 会从中随机选取一个。
4.1 过滤
Kubernetes 使用**断言(Predicates)**过滤出适合调度的节点:
- 常用过滤规则:
- PodFitsResources:节点 CPU 内存满足 Pod requests 资源
- MatchNodeSelector:匹配 nodeSelector 节点标签
- PodToleratesNodeTaints:Pod 容忍度匹配节点污点
- PodFitsHostPorts:节点端口不冲突
- PodFitsHostPorts:Pod 要使用的 hostPort,节点上没有被占用冲突
- CheckVolumeBinding:PVC 存储可以绑定到节点存储
4.2 计分
Kubernetes 使用根据以下 优先级 Priorities 对主机进行评分:
- 常用打分策略:
-
LeastRequestedPriority:优先选资源占用更少、更空闲的节点
-
SelectorSpreadPriority:同一个控制器下的 Pod,尽量分散到不同节点(只是打分偏好,不是强制打散)
-
NodeAffinityPriority:nodeAffinity 里面 preferred 软亲和的权重,在这里计入总分
-
ImageLocalityPriority:优先选本地已经存在该镜像的节点,减少拉镜像耗时
-
TaintTolerationPriority:污点容忍度打分
-
EvenPodsSpreadPriority:Pod 拓扑分布约束打分
-
✅区分一句话:过滤 = 必须满足;打分 = 尽量优先,不强求。
五、4种控制 pod 运行位置的方式
参考学习:将 Pod 指派给节点
用途 :人为干预,指定 Pod 可以跑在哪些节点;例如 SSD 磁盘节点、GPU 节点。
**4 种方式:**与节点名匹配nodeName、与节点标签匹配nodeSelector、亲和 / 反亲和、Pod 拓扑分布约束。
5.1 nodeName(直接指定节点名,优先级最高)
直接写spec.nodeName: 节点主机名,绕过调度器,强制 Pod 运行在指定节点。
⚠️缺点:
- 节点不存在 ,Pod 无法运行甚至删除
- 节点资源不足 ,Pod 会失败(OutOfcpu/OutOfmemory)
- 云环境节点名字不稳定,不推荐生产使用。
示例:
bash
root@master30:~# kubectl create deployment webapp --image nginx --replicas 2 -o yaml --dry-run=client > deploy-with-nodeName.yaml
root@master30:~# vim deploy-with-nodeName.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: webapp
name: webapp
spec:
replicas: 2
selector:
matchLabels:
app: webapp
strategy: {}
template:
metadata:
labels:
app: webapp
spec:
# 添加nodeName配置
nodeName: worker32.laoma.cloud
containers:
- image: hub.laoma.cloud/library/nginx
name: nginx
root@master30:~# kubectl apply -f deploy-with-nodeName.yaml
root@master30 ~# kubectl get pods -o wide | awk '{print $1,$3,$7}'
NAME STATUS NODE
webapp-55c6b896bc-ns9m5 Running worker32.laoma.cloud
webapp-55c6b896bc-qr892 Running worker32.laoma.cloud
# 清理资源
root@master30:~# kubectl delete deployments.apps webapp
5.2 nodeSelector(节点标签硬性匹配,简单推荐)
Pod 的 spec.nodeSelector,键值对 ,节点必须全部拥有这些标签才可以调度 Pod,不满足 Pod 就 Pending。
⚠重要特性:只有新建 Pod 生效;已经运行的 Pod,节点标签修改 / 删除,不会驱逐 Pod。
提示:nodeSelector 是节点选择约束的推荐形式。
bash
# 查看node标签
root@master30:~# kubectl get node --show-labels
NAME STATUS ROLES AGE VERSION LABELS
master30.laoma.cloud Ready control-plane 7d8h v1.30.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master30.laoma.cloud,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node.kubernetes.io/exclude-from-external-load-balancers=
worker31.laoma.cloud Ready <none> 7d8h v1.30.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=worker31.laoma.cloud,kubernetes.io/os=linux
worker32.laoma.cloud Ready <none> 7d8h v1.30.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=worker32.laoma.cloud,kubernetes.io/os=linux
# 给节点打标签,标注worker31 disktype是ssd
root@master30:~# kubectl label node worker31.laoma.cloud disktype=ssd
root@master30:~# kubectl get node -L disktype
NAME STATUS ROLES AGE VERSION DISKTYPE
master30.laoma.cloud Ready control-plane 11d v1.30.2
worker31.laoma.cloud Ready <none> 11d v1.30.2 ssd
worker32.laoma.cloud Ready <none> 11d v1.30.2
在Pod模板的spec里通过nodeSelector指定将此Pod部署到具有label为 disktype=ssd 的Node上。
bash
root@master30:~# vim deploy-with-nodeSelector.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: webapp
name: webapp
spec:
replicas: 2
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
# 添加 nodeSelector
nodeSelector:
disktype: ssd
containers:
- image: hub.laoma.cloud/library/nginx
name: nginx
root@master30:~# kubectl apply -f deploy-with-nodeSelector.yaml
root@master30:~# kubectl get pods -o wide | awk '{print $1,$3,$7}'
NAME NODE
webapp-7d675f9d9c-rz9hx Running worker31.laoma.cloud
webapp-7d675f9d9c-zrj97 Running worker31.laoma.cloud
# 删除worker31标签
root@master30:~# kubectl label node worker31.laoma.cloud disktype-
root@master30:~# kubectl get node -L disktype
NAME STATUS ROLES AGE VERSION DISKTYPE
master30.laoma.cloud Ready control-plane 11d v1.30.2
worker31.laoma.cloud Ready <none> 11d v1.30.2
worker32.laoma.cloud Ready <none> 11d v1.30.2
# 此时不会触发重新部署deployment
# 删除deployment的nodeSelector
root@master30:~# kubectl patch deployment webapp --type json \
-p '[{"op":"remove","path":"/spec/template/spec/nodeSelector"}]'
# 自动触发重新部署
root@master30:~# kubectl get pods -o wide | awk '{print $1,$3,$7}'
NAME NODE
webapp-7fb6dc88f9-hj6m6 Running worker31.laoma.cloud
webapp-7fb6dc88f9-qcvtr Running worker32.laoma.cloud
# 清理资源
root@master30:~# kubectl delete deployments.apps webapp
5.3 Affinity and AntiAffinity亲和与反亲和
亲和性为两大类:
nodeAffinity节点亲和:看节点标签podAffinity / podAntiAffinityPod 亲和 / 反亲和:看节点上已经运行的其他 Pod 标签
后缀统一
IgnoredDuringExecution:调度的时候检查规则;Pod 已经运行后,标签变化,不会驱逐 Pod。
5.3.1 nodeAffinity 节点亲和
两种模式:
- ==
requiredDuringSchedulingIgnoredDuringExecution:硬亲和,必须满足,不满足 Pod Pending,==功能类似 nodeSelector preferredDuringSchedulingIgnoredDuringExecution:软亲和,优先偏好;设置 weight 权重 1‑100;符合就给节点加分;不满足也可以调度
逻辑规则
- nodeSelector + nodeAffinity 同时写:两个条件必须全部满足
- 多个
nodeSelectorTerms:满足任意一个 term 即可(或) - 同一个 term 内部多个
matchExpressions:全部表达式必须满足(且)
完整示例 yaml
bash
root@master30:~# vim deploy-with-nodeAffinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: CPU
operator: In
values:
- L1
- L2
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: MEM
operator: In
values:
- L1
- L2
containers:
- image: hub.laoma.cloud/library/nginx
name: nginx
示例说明:
- 硬规则:Pod 只能调度到带有标签
CPU=L1或者CPU=L2的节点;没有该标签 → Pod Pending。 - 软规则:在满足硬规则的候选节点池中,如果节点拥有
MEM=L1/MEM=L2,给该节点加权重weight=1,优先调度到此节点。
如果你修改或删除了 pod 所调度到的节点的标签,Pod 不会被删除。 换句话说,亲和性选择只在 Pod 调度期间有效。
验证1:节点未打标签
bash
root@master30:~# kubectl apply -f deploy-with-nodeAffinity.yaml
root@master30:~# kubectl get pods
NAME READY STATUS RESTARTS AGE
web-854cd4986-598lb 0/1 Pending 0 2s
web-854cd4986-mp266 0/1 Pending 0 2s
# Pod全部Pending,不满足硬亲和条件
验证2:节点打 CPU 标签
bash
root@master30:~# kubectl label nodes worker31.laoma.cloud CPU=L1
root@master30:~# kubectl label nodes worker32.laoma.cloud CPU=L2
# 再次查看pod调度情况
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web-854cd4986-598lb Running worker31.laoma.cloud
web-854cd4986-mp266 Running worker31.laoma.cloud
# worker31打上标签后,满足pod调度条件,此时所有pod调度到worker31节点。
# 重新部署
root@master30:~# kubectl rollout restart deployment web
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web-85c8ffff8-q2j7r Running worker31.laoma.cloud
web-85c8ffff8-rkq7s Running worker32.laoma.cloud、
# worker31和worker32都打上标签后,满足pod调度条件,此时pod均衡调度到worker31和worker32节点。
⚠重点:软亲和只是打分偏好,不保证一定打散 Pod;分数相同会随机选择,会出现 Pod 扎堆 。想要强制打散副本,使用 Pod 反亲和podAntiAffinity。
验证3:worker32节点打标签MEM,触发软亲和权重
bash
root@master30:~# kubectl label nodes worker32.laoma.cloud MEM=L2
# 重新部署
root@master30:~# kubectl rollout restart deployment web
# 只在worker32上运行
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web-6c45779498-glxbg Running worker32.laoma.cloud
web-6c45779498-sn996 Running worker32.laoma.cloud
清理环境
bash
root@master30:~# kubectl delete deployments.apps web
root@master30:~# kubectl label nodes worker31.laoma.cloud CPU-
root@master30:~# kubectl label nodes worker32.laoma.cloud CPU-
root@master30:~# kubectl label nodes worker32.laoma.cloud MEM-
软亲和 weight 权重打分规则
weight范围:1‑100,数值越大优先级越高。- 调度器遍历所有
preferredDuringSchedulingIgnoredDuringExecution规则;节点每命中一条规则,就累加对应的 weight 权重。 - 累加出来的总权重,会和调度器其他打分函数结果合并;节点最终总分越高,越优先被选中。
例如,考虑下面的 Pod 规约:
yaml
apiVersion: v1
kind: Pod
metadata:
name: with-affinity-anti-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: label-1
operator: In
values:
- key-1
- weight: 50
preference:
matchExpressions:
- key: label-2
operator: In
values:
- key-2
containers:
- name: with-node-affinity
image: hub.laoma.cloud/library/nginx
候选节点 A 拥有label‑1=key‑1,得 1 分;候选节点 B 拥有label‑2=key‑2,得 50 分;B 总分更高,优先调度 B。
5.3.2 Inter-pod affinity and anti-affinity
💡核心区别: nodeAffinity:看节点标签 做调度判断 podAffinity/podAntiAffinity:不看节点标签,看同一个拓扑域内已经运行的其他 Pod 的标签
两个概念
- podAffinity(Pod 亲和) :希望新 Pod,和满足标签条件的 Pod跑到同一个拓扑域(挨在一起)
- podAntiAffinity(Pod 反亲和) :希望新 Pod,不要和满足标签条件的 Pod跑到同一个拓扑域(打散副本,提高高可用)
关键字段 topologyKey
topologyKey:节点上的标签,用来定义 "拓扑域范围"
kubernetes.io/hostname:拓扑域 = 单个节点(以一台机器作为判断范围)topology.kubernetes.io/zone:拓扑域 = 一个可用区(同一机房 / 机架)
⚠重要限制
- 硬、软的 podAffinity/podAntiAffinity,
topologyKey不能为空。 - 集群所有节点必须拥有这个 topologyKey 对应的标签,否则调度行为会异常。
- 缺点:大规模集群(几百节点以上),Pod 亲和 / 反亲和会大量遍历 Pod 信息,消耗调度性能,不推荐大规模使用。
两种规则模式(和 nodeAffinity 一样)
requiredDuringSchedulingIgnoredDuringExecution【硬规则】:条件不满足,Pod 直接 PendingpreferredDuringSchedulingIgnoredDuringExecution【软规则】:尽量满足,给节点加 weight 权重打分,不满足也可以调度
后缀
IgnoredDuringExecution:只在 Pod 新建调度那一刻生效;Pod 已经运行后,其他 Pod 标签变化,不会驱逐已经运行的 Pod。
语法说明
本示例定义了一条 Pod 亲和性规则和一条 Pod 反亲和性规则。
bash
root@master30:~# vim pod-with-podaffinity.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-with-podaffinity
spec:
affinity:
podAffinity: # Pod亲和:想跟符合条件Pod同拓扑域
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: topology.kubernetes.io/zone
podAntiAffinity: # Pod反亲和:尽量避开符合条件Pod的拓扑域
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: topology.kubernetes.io/zone
containers:
- name: pod-with-podaffinity
image: hub.laoma.cloud/library/nginx
- podAffinity 解读(硬) : 当前拓扑可用区
zone里面,必须至少已经跑了一个标签security=S1的 Pod,新 Pod 才允许调度进这个可用区。该可用区没有这个 Pod → 不能调度。 - podAntiAffinity 解读(软) : 如果该可用区已经存在
security=S2的 Pod,尽量不要把新 Pod 调度到此可用区;不是强制禁止。
支持运算符:
In、NotIn、Exists、DoesNotExist
示例 1:根据节点拓扑--亲和性调度
bash
# node节点打标签
root@master30:~# kubectl label node worker31.laoma.cloud \
topology.kubernetes.io/zone=v
root@master30:~# kubectl label node worker32.laoma.cloud \
topology.kubernetes.io/zone=v
# 创建两个pod
root@master30:~# vim deploy-with-podaffinity.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web1
spec:
selector:
matchLabels:
app: web1
replicas: 1
template:
metadata:
labels:
app: web1
spec:
nodeName: worker31.laoma.cloud
containers:
- name: web-server
image: hub.laoma.cloud/library/nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web2
spec:
selector:
matchLabels:
app: web2
replicas: 1
template:
metadata:
labels:
app: web2
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web1
topologyKey: topology.kubernetes.io/zone
containers:
- name: web-server
image: hub.laoma.cloud/library/nginx
root@master30:~# kubectl apply -f deploy-with-podaffinity.yaml
root@master30 ~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web1-849b694c75-t2spf Running worker31.laoma.cloud
web2-84fdf8f999-vn5l9 Running worker32.laoma.cloud
**实验结果:**pod-with-podaffinity 调度到 worker32节点。
如果此时将work32节点的 topology.kubernetes.io/zone 标签删除,重新创建pod pod-with-podaffinity,会调度到哪个节点呢?
bash
root@master30:~# kubectl label nodes worker32.laoma.cloud \
topology.kubernetes.io/zone-
root@master30 ~# kubectl rollout restart deployment web2
# 调度到worker31节点
root@master30 ~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web1-849b694c75-t2spf Running worker31.laoma.cloud
web2-7947994f89-pwnkv Running worker31.laoma.cloud
# 清理环境
root@master30:~# kubectl delete -f deploy-with-podaffinity.yaml
root@master30:~# kubectl label nodes worker31.laoma.cloud \
topology.kubernetes.io/zone-
现象:worker32 不再属于 zone=v,整个 zone=v 只剩下 worker31,web2 只能调度到 worker31。
✅podAffinity 生效两个必要条件:
- 节点拥有
topologyKey对应的标签,划分出拓扑域; - 该拓扑域内部,至少已经运行一个匹配 labelSelector 的 Pod
示例 2:根据节点主机名--反亲和性调度
(topologyKey=kubernetes.io/hostname,以单个节点作为拓扑域)
经典用途:同一个应用副本不要部署到同一个节点,实现高可用。
bash
# 准备deployment
root@master30:~# vim deploy-with-podAntiAffinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: store
spec:
selector:
matchLabels:
app: store
replicas: 3
template:
metadata:
labels:
app: store
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- store
topologyKey: "kubernetes.io/hostname"
containers:
- name: web-server
image: hub.laoma.cloud/library/nginx
# 查看pod调度情况
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
store-5596cf4c84-9z5ch Pending <none>
store-5596cf4c84-dswkq Running worker32.laoma.cloud
store-5596cf4c84-ww2l4 Running worker31.laoma.cloud
规则解读: topologyKey: kubernetes.io/hostname 拓扑域 = 单个节点; 硬反亲和:同一个节点上,不能存在 2 个app=store的 Pod,每个节点最多 1 个。
实验现象: 集群只有 2 个 worker 节点,replicas=3。
- worker31:跑 1 个 store
- worker32:跑 1 个 store
- 第 3 个 Pod 找不到节点,状态 Pending
重点:副本数量 > 拓扑域(节点)数量,硬 podAntiAffinity 会造成 Pod Pending!
示例 3:多个应用亲和性和反亲和性调度
需求:
- podAntiAffinity(硬):同一个节点不能跑多个 web‑store 副本(自己和自己打散)
- podAffinity(硬):必须和
app=store的 Pod 处在同一个节点(要跟 store 挨在一起)
bash
# 缩容 deployment
root@master30:~# kubectl scale deployment store --replicas 1
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
store-5596cf4c84-dswkq Running worker32.laoma.cloud
# 准备新deployment
root@master30:~# vim deploy-with-multi-Affinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-store
spec:
selector:
matchLabels:
app: web-store
replicas: 1
template:
metadata:
labels:
app: web-store
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-store
topologyKey: "kubernetes.io/hostname"
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- store
topologyKey: "kubernetes.io/hostname"
containers:
- name: web-app
image: hub.laoma.cloud/library/nginx
root@master30:~# kubectl apply -f deploy-with-multi-Affinity.yaml
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
store-5596cf4c84-f4dfb Running worker32.laoma.cloud
web-store-7b4779c958-vpl58 Running worker32.laoma.cloud
# 扩容:验证新副本位置
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
store-5596cf4c84-f4dfb Running worker32.laoma.cloud
web-store-7b4779c958-bswws Pending <none>
web-store-7b4779c958-vpl58 Running worker32.laoma.cloud
# 在亲和性作用下,只有worker32节点具备满足条件的pod
# 在反亲和性作用下,worker32节点上已经运行了一个副本
# 最终结果:第二个 pod 调度 挂起。
5.4 Taint 和 Toleration
nodeAffinity:Pod 挑选节点 ;Taint:节点挑选 Pod
- node 使用 Taint(污点),允许特定Pod在本机运行,未匹配的Pod则不能在该节点运行。
- pod 使用 Toleration(容忍度),允许被调度到带有与之匹配的污点的节点上。
后缀IgnoredDuringExecution不适用污点;污点的NoExecute可以驱逐已经运行的 Pod。
5.4.1 给节点设置污点 Taint
使用命令 kubectl taint 给节点**增加一个污点**。
示例 :给节点 worker31.laoma.cloud 增加一个污点,键名 CPU,键值 L1,效果 NoSchedule。
shell
# 表示拥有和这个污点相匹配的tolerations的 Pod 才能够被分配到 worker31.laoma.cloud`这个节点。
root@master30:~# kubectl taint nodes worker31.laoma.cloud \
CPU=L1:NoSchedule
若要**移除worker污点**:
shell
# 删除污点,末尾加 `-`
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU:NoSchedule-
5.4.2 Pod 设置容忍度 tolerations
在 Pod.Spec 中定义 Pod 的 tolerations。
示例:
yaml
tolerations:
- key: "CPU"
operator: "Equal"
value: "L1"
effect: "NoSchedule"
operator 可选值:
-
Equal(默认 ):key、value、effect三者全部匹配才算命中。 -
Exists:只匹配 key,不需要写 value,只要污点 key 存在即匹配。bash tolerations: - key: "CPU" operator: "Exists" effect: "NoSchedule"
特殊:key:"" + operator:Exists,可以容忍任意污点。
effect 三种效果(重中之重)
NoSchedule- 仅约束新 Pod 调度;
- 不能容忍该污点 → 禁止调度到此节点;
- ✅已经在节点上运行的 Pod 不会被驱逐。
PreferNoSchedule(软性)- 尽量不要调度,只是偏好;不强制,有可能还会调度上来。
NoExecute(最强,会影响正在运行的 Pod)- Pod 不能容忍:立刻驱逐 Pod
- Pod 能容忍、不写
tolerationSeconds:Pod 永久留在节点,不会驱逐 - Pod 能容忍、配置
tolerationSeconds: 秒数:还可以继续运行指定秒数,时间到驱逐 - 还没调度的 Pod,禁止调度到该节点。
**注意:**如果容忍度的 key 为空且 operator 为 Exists, 表示这个容忍度与任意的 key 、value 和 effect 都匹配,即这个容忍度能容忍任意 taint。
5.4.3 一个容忍度和一个污点 匹配
示例:
设置 node 污点:
bash
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoSchedule
root@master30:~# kubectl taint nodes worker32.laoma.cloud CPU=L2:NoSchedule
创建 pod :
bash
root@master30:~# vim deploy-with-tolerations.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
tolerations:
- key: "CPU"
operator: "Equal"
value: "L3"
effect: "NoSchedule"
containers:
- name: nginx
image: hub.laoma.cloud/library/nginx
imagePullPolicy: IfNotPresent
# 创建上述pod示例
root@master30:~# kubectl apply -f deploy-with-tolerations.yaml
root@master30:~# kubectl get pods
NAME READY STATUS RESTARTS AGE
web-5bf7b7b98f-kh7hv 0/1 Pending 0 2s
web-5bf7b7b98f-km7w5 0/1 Pending 0 2s
# pod状态为Pending
# 事件表明3个节点上的污点与pod不匹配,所以无法创建pod
root@master30:~# kubectl describe pod web-5bf7b7b98f-kh7hv
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 29s default-scheduler 0/3 nodes are available: 1 node(s) had untolerated taint {CPU: L1}, 1 node(s) had untolerated taint {CPU: L2}, 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.
# 删除 deployments,将容忍度改为CPU=L1
root@master30:~# kubectl delete deployments.apps web
root@master30:~# vim deploy-with-tolerations.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
tolerations:
- key: "CPU"
operator: "Equal"
value: "L1"
effect: "NoSchedule"
containers:
- name: nginx
image: hub.laoma.cloud/library/nginx
imagePullPolicy: IfNotPresent
root@master30:~# kubectl apply -f deploy-with-tolerations.yaml
root@master30:~# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-7c99d767-w9rzt 1/1 Running 0 5s 10.224.162.142 worker31.laoma.cloud <none> <none>
web-7c99d767-wwjhk 1/1 Running 0 5s 10.224.19.13 worker31.laoma.cloud <none> <none>
# Pod调度成功,而且分配到了worker31
# 清理环境
root@master30:~# kubectl taint node worker31.laoma.cloud CPU:NoSchedule-
root@master30:~# kubectl taint node worker32.laoma.cloud CPU:NoSchedule-
root@master30:~# kubectl delete deployments.apps web
5.4.4 多个容忍度和多个污点 匹配
一个节点可以打多个污点 ;一个 Pod 可以写多条容忍度。
匹配过滤逻辑:
- 遍历节点全部污点;Pod 容忍度匹配上哪些污点,就过滤掉这些污点。
- 剩下没有被容忍的污点,决定 Pod 能不能调度 / 是否驱逐
- 剩余污点包含
NoSchedule→ 禁止调度 - 没有 NoSchedule,但存在
PreferNoSchedule→ 尽量不调度 - 剩余污点包含
NoExecute→ 禁止调度;已经运行的 Pod 直接驱逐。
- 剩余污点包含
例如,某个节点添加了如下污点:
shell
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoSchedule
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoExecute
root@master30:~# kubectl taint nodes worker31.laoma.cloud MEM=L2:NoSchedule
假定某个 Pod 有两个容忍度:
yaml
tolerations:
- key: "CPU"
operator: "Equal"
value: "L1"
effect: "NoSchedule"
- key: "CPU"
operator: "Equal"
value: "L1"
effect: "NoExecute"
Pod 容忍度只写了前 2 个污点,缺少对MEM=L2:NoSchedule的容忍。
👉 结果:新 Pod 无法调度到 worker31;
👉 如果 Pod 已经提前运行在节点上:因为只有MEM=L2:NoSchedule(NoSchedule,不驱逐运行 Pod),Pod 可以继续跑,只是不能新增调度。
示例:
设置 node 多个污点
bash
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoSchedule
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoExecute
root@master30:~# kubectl taint nodes worker31.laoma.cloud MEM=L2:NoSchedule
root@master30:~# kubectl taint nodes worker32.laoma.cloud CPU=L2:NoSchedule
创建 Deployment:
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
tolerations:
- key: "CPU"
operator: "Equal"
value: "L1"
effect: "NoSchedule"
- key: "CPU"
operator: "Equal"
value: "L1"
effect: "NoExecute"
containers:
- name: nginx
image: hub.laoma.cloud/library/nginx
imagePullPolicy: IfNotPresent
# 创建上述pod示例
root@master30:~# kubectl apply -f deploy-with-tolerations.yaml
root@master30:~# kubectl get pods
NAME READY STATUS RESTARTS AGE
web-6844d698c4-b8cz7 0/1 Pending 0 4s
web-6844d698c4-wrpkj 0/1 Pending 0 4s
# pod状态为Pending
# 事件表明节点上的3个污点与pod不匹配,所以无法创建pod
root@master30:~# kubectl describe pod web-6844d698c4-b8cz7
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 50s default-scheduler 0/3 nodes are available: 1 node(s) had untolerated taint {CPU: L2}, 1 node(s) had untolerated taint {MEM: L2}, 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.
# 删除deployments,添加容忍度MEM=L2
root@master30:~# kubectl delete deployments.apps web
root@master30:~# vim deploy-with-tolerations.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
tolerations:
- key: "CPU"
operator: "Equal"
value: "L1"
effect: "NoSchedule"
- key: "CPU"
operator: "Equal"
value: "L1"
effect: "NoExecute"
- key: "MEM"
operator: "Equal"
value: "L2"
effect: "NoSchedule"
containers:
- name: nginx
image: hub.laoma.cloud/library/nginx
imagePullPolicy: IfNotPresent
root@master30:~# kubectl apply -f deploy-with-tolerations.yaml
root@master30:~# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-77d5464ccf-656fd 1/1 Running 0 5s 10.224.19.18 worker31.laoma.cloud <none> <none>
web-77d5464ccf-wjk6m 1/1 Running 0 5s 10.224.19.27 worker31.laoma.cloud <none> <none>
# Pod 调度成功,而且分配到了worker31
# 清理环境
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoSchedule-
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoExecute-
root@master30:~# kubectl taint nodes worker31.laoma.cloud MEM=L2:NoSchedule-
root@master30:~# kubectl taint nodes worker32.laoma.cloud CPU=L2:NoSchedule-
root@master30:~# kubectl delete deployments.apps web
实验现象:Pod 状态 Pending,事件提示存在未被容忍污点。 补充完整 MEM 的容忍度,Pod 即可正常调度。
5.4.5 Taint 和 Toleration 用例
- 专用节点 给节点打污点
dedicated=groupA:NoSchedule; 只有对应 Pod 配置容忍度,才允许调度。
⚠注意:仅容忍度只能 "允许进来",不能限制 Pod只跑在这批节点 。 如果要求 Pod只能跑在专用节点 :容忍度 + nodeAffinity 节点亲和一起配合使用。
- GPU 特殊硬件节点 节点打污点,普通无 GPU 业务没有容忍度就不会占用 GPU 节点;使用 GPU 的 Pod 配置容忍度。
5.4.6 系统内置自动污点(NoExecute)
节点异常时,node‑controller 自动给节点打上下面这些污点:
node.kubernetes.io/not-ready节点未就绪node.kubernetes.io/unreachable节点访问不到node.kubernetes.io/memory‑pressure内存压力node.kubernetes.io/disk‑pressure磁盘压力node.kubernetes.io/pid‑pressurePID 资源压力node.kubernetes.io/network‑unavailable网络不可用node.kubernetes.io/unschedulable节点不可调度
自动容忍行为(考试高频)
- 普通业务 Pod :K8s 自动添加容忍度,
tolerationSeconds=300(5 分钟)。
节点故障,Pod 还可以继续运行 5 分钟,5 分钟之后驱逐。
- DaemonSet 的 Pod(calico‑node、kube‑proxy、node‑exporter)
自动容忍上面两个故障污点,没有 tolerationSeconds 。 节点失联、未就绪,永远不会驱逐 DaemonSet Pod,保障网络 / 监控组件运行。
自定义示例:Pod 希望网络断开后,多等待 10 分钟再驱逐
yaml
tolerations:
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 6000
5.4.7 cordon 和 uncordon
bash
# 封锁节点,禁止新Pod调度
kubectl cordon 节点名
# 解除封锁,恢复调度
kubectl uncordon 节点名
- 给节点==标记SchedulingDisabled ,不可调度,==将无法在node上创建新的pod。
- ✅禁止新 Pod 调度上来
- ❗**已经运行在节点上的 Pod 完全不受影响,不会驱逐删除**
注意:cordon 只是标记,不是打 taint 污点,机制不一样。
bash
root@master30:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master30.laoma.cloud Ready control-plane 9d v1.30.2
worker31.laoma.cloud Ready <none> 9d v1.30.2
worker32.laoma.cloud Ready <none> 9d v1.30.2
# 封锁节点,禁止新Pod调度
root@master30:~# kubectl cordon worker31.laoma.cloud
root@master30:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master30.laoma.cloud Ready control-plane 9d v1.30.2
worker31.laoma.cloud Ready,SchedulingDisabled <none> 9d v1.30.2
worker32.laoma.cloud Ready <none> 9d v1.30.2
# 创建一个deployment
root@master30:~# kubectl create deployment web --image=hub.laoma.cloud/library/nginx --replicas=2
root@master30:~# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-59b9bb7664-flhvx 1/1 Running 0 10s 10.224.225.69 worker32.laoma.cloud <none> <none>
web-59b9bb7664-ss586 1/1 Running 0 10s 10.224.225.68 worker32.laoma.cloud <none> <none>
# 解除封锁,恢复调度
root@master30:~# kubectl uncordon worker31.laoma.cloud
root@master30:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master.laoma.cloud Ready control-plane 36d v1.30.2
worker31.laoma.cloud Ready <none> 36d v1.30.2
worker32.laoma.cloud Ready <none> 36d v1.30.2
root@master30:~# kubectl scale deployment web --replicas=4
root@master30:~# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-59b9bb7664-flhvx 1/1 Running 0 2m6s 10.224.225.69 worker32.laoma.cloud <none> <none>
web-59b9bb7664-jkb5q 1/1 Running 0 5s 10.224.51.133 worker31.laoma.cloud <none> <none>
web-59b9bb7664-ss586 1/1 Running 0 2m6s 10.224.225.68 worker32.laoma.cloud <none> <none>
web-59b9bb7664-w6mbs 1/1 Running 0 5s 10.224.51.132 worker31.laoma.cloud <none> <none>
5.4.8 drain 节点排空
对节点执行维护(例如内核升级、硬件维护等 )之前, 可以使用 kubectl drain 从节点安全地逐出所有 Pod 。 安全的驱逐过程允许 Pod 的容器体面地终止, 并确保满足指定的 PodDisruptionBudgets。
drain = cordon(封锁节点) + 驱逐节点上普通业务 Pod;
被驱逐 Pod 会被控制器在其他健康节点重建。
- 默认会报错:不能驱逐 DaemonSet 管理 Pod(calico‑node/kube‑proxy)
- 必须加参数
--ignore‑daemonsets,跳过 DaemonSet Pod,它们会继续留在本节点运行。
kubectl drain 实践
kubectl drain 命令,驱逐 node 上所有 pod ,同时标记 node 为不可调度。默认情况下,该命令忽略节点上不能驱逐的 Pod
bash
# 默认不驱逐 DaemonSet 控制的pod
root@master30:~# kubectl drain worker31.laoma.cloud
node/worker31.laoma.cloud cordoned
error: unable to drain node "worker31.laoma.cloud", aborting command...
There are pending nodes to be drained:
worker31.laoma.cloud
error: cannot delete DaemonSet-managed Pods (use --ignore-daemonsets to ignore): kube-system/calico-node-jwdzx, kube-system/kube-proxy-98qkl
# 使用--ignore-daemonsets驱逐DaemonSet控制的pod
root@master30:~# kubectl drain worker31.laoma.cloud --ignore-daemonsets
node/worker31.laoma.cloud already cordoned
WARNING: ignoring DaemonSet-managed Pods: kube-system/calico-node-jwdzx, kube-system/kube-proxy-98qkl
evicting pod kube-system/kuboard-74c645f5df-ljk98
evicting pod laoma/web-59b9bb7664-jkb5q
evicting pod laoma/web-59b9bb7664-w6mbs
pod/web-59b9bb7664-w6mbs evicted
pod/web-59b9bb7664-jkb5q evicted
pod/kuboard-74c645f5df-ljk98 evicted
node/worker31.laoma.cloud evicted
# 还可以使用以下选项驱逐特定pod:
--pod-selector='': Label selector to filter pods on the node
-l, --selector='': Selector (label query) to filter on,supports '=', '==', and '!='.(e.g. -l CPU=L1,MEM=L2). Matching objects must satisfy all of the specified label constraints.
root@master30:~# kubectl get node
NAME STATUS ROLES AGE VERSION
master30.laoma.cloud Ready control-plane 36d v1.30.2
worker31.laoma.cloud Ready,SchedulingDisabled <none> 36d v1.30.2
worker32.laoma.cloud Ready <none> 36d v1.30.2
# 删除worker31上SchedulingDisabled标记
root@master30:~# kubectl uncordon worker31.laoma.cloud
- drain 执行驱逐会遵守PDB (PodDisruptionBudget) 干扰预算,保证业务最小可用副本,不会一次性全部干掉业务 Pod。
- 支持多个终端并行 drain 多个节点。
5.4.9 PriorityClass
-
集群级资源,非命名空间资源 ;
value数值越大优先级越高。 -
globalDefault:true:没有配置priorityClassName的 Pod 使用该优先级;集群只能有 1 个。
preemptionPolicy
PreemptLowerPriority(默认):高优先级 Pod 调度失败,可以抢占驱逐低优先级 Pod 拿资源。Never:非抢占模式;只排队,不去抢占别人 Pod 资源。
yaml 示例
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "高优先级业务"
Pod 引用优先级类:
spec:
priorityClassName: high-priority
5.4.10 抢占逻辑
- 高优先级 Pod(P)Pending,找不到合适节点;触发抢占逻辑。
- 寻找合适节点,驱逐节点上面低优先级 Pod;被驱逐 Pod 收到优雅终止信号。
- Pod P 会设置
nominatedNodeName记录抢占的目标节点,不等于 nodeName,不会立刻绑定运行。 - 如果来了更高优先级 Pod,会把
nominatedNodeName清空,抢占资格让出。
管理员可以用 ResourceQuota 限制普通用户创建高优先级 PriorityClass,防止恶意抢占。
环境清理
bash
root@master30:~# kubectl delete ns scheduler
📝高频背诵考点汇总
- nodeAffinity:Pod 选节点;Taint:节点筛选 Pod。
- NoSchedule:只管新 Pod 调度,不驱逐正在运行 Pod;NoExecute 会驱逐运行 Pod。
- tolerationSeconds 只对
NoExecute生效,控制故障后 Pod 滞留时间。 - 普通 Pod 故障污点默认容忍 300s;DaemonSet Pod 故障污点没有 tolerationSeconds,不会被驱逐。
- cordon 仅封锁新 Pod,不动老 Pod;drain=cordon + 驱逐普通业务 Pod。
- drain 必须加
--ignore‑daemonsets;驱逐会遵守 PDB。 - PriorityClass 是集群资源,value 越大优先级越高;PreemptionPolicy=Never 关闭抢占。
- 多个污点:Pod 容忍匹配掉部分污点,剩下的污点决定调度结果。
排错命令
kubectl describe pod xxx # 看事件,查看untolerated taint报错
kubectl get nodes node‑name -o yaml # 查看节点所