本文是Kubernetes调度系列的进阶篇,详细梳理污点与容忍、节点维护操作、亲和性/反亲和性三大核心调度机制,附带全部命令示例和补充知识点,适合作为博客学习笔记。
1. Taint(污点)和 Toleration(容忍)
1.1 基本概念
-
Taint(污点) 作用于节点,用于排斥一类Pod。
-
Toleration(容忍) 作用于Pod,表示Pod可以"容忍"某个污点,从而允许被调度到带有该污点的节点上。
类比:污点就像节点上的"刺",只有携带"防护手套"(容忍)的Pod才能靠近。
典型应用场景:
-
Master节点只运行系统组件(如kube-apiserver、etcd、Calico等),业务Pod不得调度。
-
新加入的节点需要经过测试后才能承接业务 → 先打污点,测试完成后再移除。
-
GPU专用节点只允许需要GPU的Pod部署。
-
节点维护时,驱逐现有Pod并阻止新Pod调度。
1.2 污点的操作命令
1.2.1 添加污点
bash
# 语法:kubectl taint nodes <节点名> <key>=<value>:<effect>
kubectl taint nodes k8s-node01 key1=value1:NoSchedule
1.2.2 查看节点的污点
bash
kubectl describe node k8s-node01 | grep Taints
# 或
kubectl get nodes -o json | jq '.items[].spec.taints'
1.2.3 移除污点
bash
# 在命令末尾加一个减号 -
kubectl taint nodes k8s-node01 key1=value1:NoSchedule-
1.3 三种effect详解
| effect | 含义 | 对已存在Pod | 对未调度Pod |
|---|---|---|---|
| NoSchedule | 不会将新Pod调度到此节点 | 无影响 | 除非Pod容忍,否则不调度 |
| PreferNoSchedule | 尽量不调度到此节点(软限制) | 无影响 | 尽量避开,但不强制 |
| NoExecute | 立即驱逐不匹配的Pod + 阻止新调度 | 不容忍的Pod被驱逐;容忍但未设置tolerationSeconds的Pod保留;容忍且设置了时间的,到达时间后驱逐 |
不容忍的不调度 |
补充知识点:
-
NoExecute常用于节点维护场景:先打污点驱逐Pod,待维护完成后再移除污点。 -
tolerationSeconds可给Pod一个"优雅退出"的时间窗口(例如60秒),默认值为一天(86400秒)。
1.4 容忍的定义与匹配规则
在Pod的spec中通过tolerations字段定义容忍:
yaml
tolerations:
- key: "check"
operator: "Equal"
value: "mycheck"
effect: "NoExecute"
tolerationSeconds: 60
匹配规则:
-
如果
operator为Exists,则无需指定value(只要key和effect匹配即可)。 -
如果
operator为Equal(默认),则key、value、effect必须完全匹配。
特殊容忍:
-
容忍所有污点:
operator: "Exists"(空key、空effect) -
容忍某个key的所有effect:指定
operator: "Exists"且不写effect
1.5 完整示例:NoExecute + tolerationSeconds
步骤1:给两个节点打污点
bash
kubectl taint node k8s-node01 check=mycheck:NoExecute
kubectl taint node k8s-node02 check=mycheck:NoExecute
步骤2:创建一个不容忍的Pod(会被驱逐)
yaml
# pod1.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp01
labels:
app: myapp01
spec:
containers:
- name: myapp
image: nginx
bash
kubectl apply -f pod1.yaml
# 该Pod会处于Pending状态,因为两个节点都有NoExecute污点且Pod没有容忍
步骤3:创建带容忍和tolerationSeconds的Pod
yaml
# pod2.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp02
spec:
tolerations:
- key: "check"
operator: "Equal"
value: "mycheck"
effect: "NoExecute"
tolerationSeconds: 60 # 60秒后被驱逐
containers:
- name: myapp
image: nginx
bash
kubectl apply -f pod2.yaml
kubectl get pods -o wide # 可以看到Pod被调度到某个节点
步骤4:实验完成后移除污点
bash
kubectl taint node k8s-node01 check=mycheck:NoExecute-
kubectl taint node k8s-node02 check=mycheck:NoExecute-
1.6 多污点与多容忍的处理逻辑
一个节点可以设置多个污点,一个Pod也可以定义多个容忍。调度器的处理逻辑:
-
过滤掉所有不容忍的污点(即污点的key/effect与Pod的容忍不匹配)。
-
剩下的污点中:
-
如果存在任意一个
effect=NoSchedule的污点 → 不调度。 -
如果只有
PreferNoSchedule→ 尽量不调度(软限制)。 -
如果有
NoExecute且Pod已经在该节点上运行 → 驱逐;如果未调度 → 不调度。
-
补充 :Kubernetes 1.18+ 引入了
TaintBasedEvictions特性门控,默认开启。NoExecute的驱逐是异步 的,容忍检查周期由--node-status-update-frequency和--taint-manager-recheck-period控制。
1.7 特殊容忍场景
1.7.1 Master节点允许临时调度
bash
# 给Master打PreferNoSchedule污点(尽量不调度,但资源紧张时可调度)
kubectl taint node k8s-master node-role.kubernetes.io/master=:PreferNoSchedule
1.7.2 节点维护前驱逐所有Pod
bash
kubectl taint node k8s-node01 maintenance=upgrade:NoExecute
# 待维护完成后移除
kubectl taint node k8s-node01 maintenance=upgrade:NoExecute-
1.7.3 容忍所有污点(慎用)
yaml
tolerations:
- operator: "Exists"
2. Cordon(警戒)和 Drain(转移)
这两个命令用于节点维护场景,安全地将节点上的Pod迁移走。
2.1 警戒(cordon)
cordon 将节点标记为不可调度 (SchedulingDisabled),但不会驱逐已有Pod。
bash
kubectl cordon k8s-node01
检查节点状态:
bash
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# k8s-node01 Ready,SchedulingDisabled <none> 10d v1.24.0
2.2 转移(drain)
drain 做了两件事:
-
将节点设置为不可调度(相当于自动执行了
cordon)。 -
驱逐节点上所有Pod(DaemonSet管理的Pod默认不会被驱逐,需要加
--ignore-daemonsets)。
bash
kubectl drain k8s-node01 \
--ignore-daemonsets \
--delete-local-data \
--force
参数详解:
| 参数 | 作用 |
|---|---|
--ignore-daemonsets |
忽略DaemonSet管理的Pod(它们会重建在当前节点,驱逐无意义) |
--delete-local-data |
强制删除使用emptyDir或hostPath的Pod(否则会因local data而阻塞) |
--force |
强制删除不是由控制器(ReplicaSet、StatefulSet等)管理的Pod,例如裸Pod |
补充 :
drain会检查节点上是否有非DaemonSet的不可容忍NoExecute的Pod,如果有且未加--force则会拒绝执行。
2.3 恢复调度(uncordon)
维护完成后,将节点重新设为可调度:
bash
kubectl uncordon k8s-node01
生产实践建议:
-
维护流程:
cordon→ 确认无新Pod调度 →drain→ 维护 →uncordon -
如需维护多个节点,应逐个进行,确保集群容量足够承载迁移的Pod。
3. 亲和性与非亲和性
亲和性提供比污点更灵活的调度选择,可以指定"希望"或"必须"满足的规则。
3.1 硬策略 vs 软策略
| 类型 | 关键词 | 含义 |
|---|---|---|
| 硬策略(Required) | requiredDuringSchedulingIgnoredDuringExecution |
必须满足,否则Pod不调度 |
| 软策略(Preferred) | preferredDuringSchedulingIgnoredDuringExecution |
尽量满足,不满足也可以调度到其他节点 |
IgnoredDuringExecution表示一旦Pod已经运行,即便节点标签发生变化,也不会驱逐Pod。
3.2 节点亲和性(NodeAffinity)
3.2.1 节点硬亲和示例
先给节点打标签:
bash
kubectl label nodes k8s-node01 type=node01
kubectl label nodes k8s-node02 type=node02
# 覆盖标签
kubectl label nodes k8s-node01 type=node01 --overwrite
# 删除标签
kubectl label nodes k8s-node01 type-
查看标签:
bash
kubectl get nodes --show-labels
创建Pod调度到type=node01的节点:
yaml
# test01.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod01
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: type
operator: In
values: ["node01"]
containers:
- name: pod01
image: nginx:1.7.9
bash
kubectl apply -f test01.yaml
kubectl get pod pod01 -o wide
# NODE 显示为 k8s-node01
3.2.2 节点软亲和示例
软亲和可以设置weight(1-100),权重越高越优先。
yaml
# test03.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod03
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: type
operator: In
values: ["node01"]
- weight: 20
preference:
matchExpressions:
- key: type
operator: In
values: ["node02"]
containers:
- name: pod03
image: nginx
注意 :节点软亲和中的权重顺序不直接影响调度结果,调度器会根据所有软规则综合打分。但Pod软亲和(后面会讲)的权重会明显影响结果。
3.3 Pod亲和性(PodAffinity)
Pod亲和性用于将Pod聚集 在一起(例如同一个机架、同一个节点),这需要通过topologyKey指定拓扑域。
topologyKey 是节点标签的键,例如:
-
kubernetes.io/hostname→ 同一节点 -
topology.kubernetes.io/zone→ 同一可用区 -
kubernetes.io/arch→ 同一架构
3.3.1 Pod硬亲和示例
首先创建两个基础Pod:pod04(在node01)、pod05(在node02)
yaml
# test04.yaml - pod04
apiVersion: v1
kind: Pod
metadata:
name: pod04
labels:
app: pod04
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: type, operator: In, values: ["node01"]}
containers:
- name: pod04
image: nginx
yaml
# test05.yaml - pod05
apiVersion: v1
kind: Pod
metadata:
name: pod05
labels:
app: pod05
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: type, operator: In, values: ["node02"]}
containers:
- name: pod05
image: nginx
部署后,pod04在node01,pod05在node02。
现在创建一个Pod亲和到pod05(即希望与pod05在同一个拓扑域):
yaml
# test06.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod06
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["pod05"]}
topologyKey: kubernetes.io/hostname
containers:
- name: pod06
image: nginx
bash
kubectl apply -f test06.yaml
kubectl get pods -o wide
# pod06 会运行在 node02(与 pod05 同节点)
3.3.2 Pod软亲和示例
Pod软亲和可以设置多个亲和项,每个带有weight。
yaml
# test07.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod07
spec:
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
podAffinityTerm:
labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["pod05"]}
topologyKey: kubernetes.io/hostname
- weight: 20
podAffinityTerm:
labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["pod04"]}
topologyKey: kubernetes.io/hostname
containers:
- name: pod07
image: nginx
因为权重80偏向pod05(位于node02),所以pod07大概率调度到node02。
如果将权重改为20/80(偏向pod04),则Pod会去node01。
yaml
# test08.yaml 中交换权重
- weight: 20 # 偏向pod05
- weight: 80 # 偏向pod04
3.4 Pod反亲和性(PodAntiAffinity)
反亲和性让Pod远离某些Pod,避免集中(例如避免两个数据库Pod在同一节点)。
yaml
# test09.yaml - 硬反亲和,不与 pod05 在同一节点
apiVersion: v1
kind: Pod
metadata:
name: pod09
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["pod05"]}
topologyKey: kubernetes.io/hostname
containers:
- name: pod09
image: nginx
由于pod05在node02,pod09会被调度到node01(或其他非node02的节点)。
4. Taint与亲和性的优先级
重要原则:污点的约束优先于亲和性的指导。
-
如果一个节点上有污点,而Pod没有对应的容忍,那么无论亲和性规则如何,Pod 都不会被调度到该节点。
-
即使Pod的亲和性明确"要求"调度到某个节点,只要该节点有不容忍的污点,调度依然失败。
因此,调度器的决策顺序为:
-
Taint/Toleration 过滤(硬性排除)
-
节点亲和性/反亲和性 过滤
-
Pod亲和性/反亲和性 过滤
-
打分排序(包括软亲和性的权重)
5. 补充知识点汇总
5.1 节点上的默认污点
Kubernetes 1.6+ 引入了节点条件污点,当节点出现某种状况(如网络不可用、磁盘压力)时,会自动添加对应污点:
| 内置污点 | 含义 |
|---|---|
node.kubernetes.io/not-ready |
节点未就绪 |
node.kubernetes.io/unreachable |
节点不可达 |
node.kubernetes.io/out-of-disk |
磁盘空间不足 |
node.kubernetes.io/memory-pressure |
内存压力 |
node.kubernetes.io/disk-pressure |
磁盘压力 |
node.kubernetes.io/network-unavailable |
网络不可用 |
这些污点默认都有NoExecute或NoSchedule效果,会触发Pod驱逐。
5.2 亲和性中的操作符(operator)
支持以下操作符:
-
In:标签值在列表中 -
NotIn:标签值不在列表中 -
Exists:存在该标签键 -
DoesNotExist:不存在该标签键 -
Gt(大于) /Lt(小于):仅用于数值比较
示例:
yaml
matchExpressions:
- key: disk-size
operator: Gt
values: ["100"] # 表示大于100
5.3 topologyKey 注意事项
-
Pod亲和性中必须 指定
topologyKey。 -
不同节点上的
topologyKey标签值相同表示同一个拓扑域。 -
使用
podAntiAffinity时,如果topologyKey没有覆盖到所有节点,可能会导致Pod被分散到某些域,但其他域仍有累积。
5.4 性能建议
-
大量的亲和性/反亲和性规则会增加调度器的计算开销,建议仅在必要时使用。
-
对于大规模集群(>1000节点),谨慎使用
podAffinity,因为调度器需要扫描所有节点上的Pod标签。
5.5 与nodeSelector的对比
| 特性 | nodeSelector | nodeAffinity |
|---|---|---|
| 表达能力 | 简单等式匹配 | 复杂表达式(In, NotIn, Exists, Gt, Lt) |
| 软策略 | 不支持 | 支持 preferredDuringScheduling |
| 联合条件 | 不支持 | 支持 matchExpressions 多条件 |
5.6 drain 的优雅驱逐
kubectl drain 实际上调用了Pod的/eviction API,遵循Pod的terminationGracePeriodSeconds(默认30秒)。可以使用--grace-period覆盖。
6. 本章总结
本文全面梳理了Kubernetes高级调度的三大支柱:
| 机制 | 作用对象 | 核心目的 |
|---|---|---|
| Taint & Toleration | 节点 / Pod | 排斥或容忍特定Pod,保护专用节点 |
| Cordon & Drain | 节点 | 安全维护节点,迁移Pod |
| NodeAffinity | Pod → 节点 | 控制Pod部署到满足标签条件的节点 |
| PodAffinity / AntiAffinity | Pod → Pod | 让Pod聚集或分散部署 |
掌握这些机制后,你可以:
-
隔离Master节点与业务Pod
-
实现GPU节点专用调度
-
优雅地完成节点升级维护
-
让高耦合的Pod就近部署(低延迟)
-
避免单点故障(反亲和性)
生产环境中,通常组合使用多种调度策略。例如:节点打上污点防止意外调度 + 为特定Pod设置容忍 + 配合亲和性指定拓扑域。
最后提醒:所有示例命令中的节点名、标签值请根据实际集群修改。实验结束后记得清理污点和标签,以免影响后续测试。