说明:本文由 Codex 根据作者提供的主题、素材与公开资料辅助生成,属于 AI 生成/整理内容,非作者原创。请读者自行甄别并交叉验证。
术语修订(2026-09-07,Agent:Codex):保留 Taint、Toleration 与 Affinity 等官方术语,并将调度与运行期关系改为 Mermaid 示意图。此次运行记录:模型
gpt-6-astra,reasoning effortultra,执行入口 Codex Desktop,提供方openai,CLI 版本0.153.4(不代表桌面 App 版本)。
先建立一张心智地图
Kubernetes 调度与节点管理,表面上有很多名词,实际上是在回答四个问题:
- Pod 能不能放到这个节点?
- Pod 更应该放到哪个节点?
- 节点发生维护或异常时,已经运行的 Pod 如何离开?
- 应用怎样在迁移、故障和资源压力下保持可用?
| 机制 | 作用方向 | 解决的问题 | 是否会影响已运行 Pod |
|---|---|---|---|
Label / nodeSelector | Pod 选择 Node | 只运行在带有指定标签的节点 | 通常不会 |
| Node Affinity | Pod 选择 Node | 用更灵活的硬、软规则选择节点 | IgnoredDuringExecution 通常不会 |
| Pod Affinity / Anti-Affinity | Pod 与 Pod 的关系 | 靠近依赖、避免副本共处 | 仅在调度新 Pod 时生效 |
| Taint | Node 排斥 Pod | 保护专用节点,拒绝不合适的 Pod | NoExecute 会影响 |
| Toleration | Pod 容忍 Taint | 允许 Pod 进入设置了匹配 Taint 的节点 | 可配合 NoExecute 延迟驱逐 |
| Eviction | 系统或管理员终止 Pod | 节点维护、资源压力、节点故障 | 会终止 Pod;控制器按需补建 |
Tolerations permit scheduling onto nodes with matching taints, but they do not guarantee node selection. Taints and Tolerations
可以这样理解各自的作用:
Affinity 是“我想去哪里”;
Taint 是“谁不该来”;
Toleration 是“我可以进去”;
驱逐是“已经在这里的 Pod 何时必须离开”。
三个常见误区也可以先排除:
- Toleration 不等于选择。Pod 容忍 GPU 节点,并不代表它一定会被调度到 GPU 节点。
NoSchedule不等于驱逐。它只阻止新的不匹配 Pod 调度进来。- PDB 不是绝对保护。它主要约束自愿性中断,不能阻止节点压力驱逐。
调度与运行时的边界
下面是一个便于理解的逻辑流程,不代表 kube-scheduler 内部插件的精确执行顺序:
调度阶段:
flowchart TB
A[未绑定的 Pod] --> B[过滤候选节点]
B --> C{有候选节点?}
C -->|有| D[打分<br/>绑定]
C -->|无| E[Pending<br/>等待重试]
运行期:
flowchart TB
A[运行中的 Pod] --> B{需要终止?}
B -->|否| C[继续运行]
B -->|是| D[终止 Pod]
D --> E[有控制器时<br/>按需补建]
过滤阶段检查资源、nodeSelector、required Node Affinity,以及未被容忍的 NoSchedule / NoExecute Taint;打分阶段考虑 preferred Affinity、PreferNoSchedule 等因素。运行期的节点维护、NoExecute 与资源压力则走各自的处理路径。
因此,调度规则大多决定“新 Pod 去哪里”,而驱逐规则处理“运行中的 Pod 是否还能留在原节点”。这两个阶段不要混为一谈。
Taint 与 Toleration:节点隔离的反向筛选
Taint 定义在 Node 上,格式为:
<key>=<value>:<effect>
例如,下面的命令把 gpu-node-01 标记为 GPU 专用节点:
kubectl label node gpu-node-01 workload=gpu
kubectl taint node gpu-node-01 dedicated=gpu:NoSchedule
此后,没有匹配的 Toleration 的 Pod 不能新调度到该节点。查看与移除 Taint:
kubectl describe node gpu-node-01
kubectl get node gpu-node-01 -o jsonpath='{.spec.taints}'
kubectl taint node gpu-node-01 dedicated=gpu:NoSchedule-
三种 effect
| effect | 对新 Pod 的影响 | 对已运行 Pod 的影响 | 适用场景 |
|---|---|---|---|
NoSchedule | 没有匹配的 Toleration 则不能调度 | 不驱逐 | 专用节点、GPU 节点、维护前封锁 |
PreferNoSchedule | 调度器尽量避开,但不保证 | 不驱逐 | 软隔离、临时倾向 |
NoExecute | 没有匹配的 Toleration 则不能调度 | 驱逐不匹配 Pod | 节点异常、强制隔离 |
同一 Node 可以有多个 Taint。Kubernetes 会先从全部 Taint 中移除被 Pod 容忍的部分,再根据剩余 Taint 处理:
- 剩余任意一个
NoSchedule,Pod 不能调度到该节点。 - 没有
NoSchedule,但有PreferNoSchedule,调度器会尽量避开该节点。 - 剩余任意一个
NoExecute,新 Pod 不能调度;已有 Pod 会被驱逐。
Toleration 字段与匹配规则
Toleration 写在 Pod 的 .spec.tolerations 中:
tolerations:
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"
| 字段 | 含义 |
|---|---|
key | 要匹配的 Taint 键 |
operator | Equal 或 Exists;省略时默认 Equal |
value | 仅 Equal 使用,必须与 Taint 值一致 |
effect | 要匹配的效果;为空时匹配同一个 key 的所有 effect |
tolerationSeconds | 仅用于 NoExecute,声明可继续留在节点上的秒数 |
Equal 是精确匹配:
tolerations:
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"
Exists 只关心 key 和 effect,不写 value:
tolerations:
- key: "dedicated"
operator: "Exists"
effect: "NoSchedule"
如果 key 为空,operator 必须是 Exists,它会匹配所有 key 和 value。这个配置范围很大,例如:
tolerations:
- operator: "Exists"
effect: "NoSchedule"
它会容忍任意 NoSchedule Taint。业务 Pod 一般不应这样写,否则专用节点隔离会失效。
NoExecute 与 tolerationSeconds
NoExecute 是唯一会直接影响已有 Pod 的 effect。下面的配置表示:如果节点出现匹配 Taint,Pod 最多可继续运行 300 秒;Taint 在这段时间内被移除,则不会被驱逐。
tolerations:
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 300
没有 tolerationSeconds 时,匹配的 Pod 会一直容忍该 NoExecute Taint;没有匹配的 Toleration 时,Pod 会被立即驱逐。因此,对 NoExecute 的容忍要明确业务对短暂故障和快速故障切换的取舍。
推荐组合:专用节点池
生产环境中,单独使用 Toleration 通常不够。以下组合能同时表达“普通 Pod 不能来”和“目标 Pod 必须来”:
- Node Label:描述节点能力,例如
workload=gpu。 - Taint:保护该节点,例如
dedicated=gpu:NoSchedule。 - Toleration:允许目标 Pod 进入。
- Node Affinity:要求目标 Pod 主动选择这一类节点。
先给节点打标:
kubectl label node gpu-node-01 workload=gpu
kubectl taint node gpu-node-01 dedicated=gpu:NoSchedule
再部署工作负载:
apiVersion: apps/v1
kind: Deployment
metadata:
name: gpu-worker
spec:
replicas: 1
selector:
matchLabels:
app: gpu-worker
template:
metadata:
labels:
app: gpu-worker
spec:
tolerations:
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: workload
operator: In
values:
- gpu
containers:
- name: worker
image: registry.example.com/training-worker:1.0
resources:
requests:
cpu: "1"
memory: "2Gi"
nvidia.com/gpu: 1
limits:
cpu: "2"
memory: "4Gi"
nvidia.com/gpu: 1
这四层的职责不同:
- 没有 Toleration 的普通 Pod 会被 Taint 挡在 GPU 节点外。
- 带 Toleration 但没有 Node Affinity 的 Pod 可以进入 GPU 节点,却不保证优先落在那里。
- 同时带 Toleration 和 required Node Affinity 的 GPU 任务,才会只在 GPU 节点池中寻找位置。
- GPU 资源请求仍是调度器判断该节点是否有可用 GPU 的依据。
DaemonSet 系统组件
CNI、kube-proxy、日志采集和节点监控等组件通常必须在每个节点运行。它们一般通过 DaemonSet 部署,并需要容忍控制平面、节点状态或维护相关的 Taint。
DaemonSet 控制器会为部分常见节点状态自动补充 Toleration;不同 Kubernetes 版本、控制器清单和节点角色的最终行为仍应以实际 Pod 规格为准。不要为了“跑遍所有节点”而给业务 Pod 添加宽泛的 Exists Toleration,也不要手工覆盖系统 DaemonSet 的 Toleration 策略却不验证其节点故障行为。
# 查看 DaemonSet 的实际 Pod 分布和容忍配置
kubectl -n kube-system get daemonset
kubectl -n kube-system get pod -o wide
kubectl -n kube-system get pod <pod-name> -o yaml
Affinity:从“允许”到“选择”
Node Affinity
Node Affinity 根据 Node Label 选择节点,比 nodeSelector 更灵活。它主要分为两类:
| 字段 | 语义 | 没有匹配节点时 |
|---|---|---|
requiredDuringSchedulingIgnoredDuringExecution | 硬约束 | Pod 保持 Pending |
preferredDuringSchedulingIgnoredDuringExecution | 软偏好 | 仍可调度到其他可用节点 |
一个 Node Affinity 示例:必须在北京或上海可用区,并尽量选择 SSD 节点。
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- cn-beijing-a
- cn-shanghai-a
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: disk-type
operator: In
values:
- ssd
逻辑关系值得注意:
- 多个
nodeSelectorTerms之间是 OR。 - 同一个
matchExpressions列表中的条件之间是 AND。 - Node Affinity 可用
In、NotIn、Exists、DoesNotExist;Gt和Lt只适用于 Node Affinity,且标签值必须是整数。
IgnoredDuringExecution 表示:Pod 已调度后,即使 Node Label 改变,Kubernetes 默认不会仅因这个原因把 Pod 驱逐。它不是持续迁移机制。
Pod Affinity 与 Pod Anti-Affinity
Pod Affinity 根据其他 Pod 的标签决定放置位置:
- Pod Affinity:尽量与某类 Pod 靠近,例如应用与本地缓存放在同一可用区。
- Pod Anti-Affinity:尽量或强制与同类 Pod 分开,避免一个节点故障带走多个副本。
下面的 Deployment 要求同一个应用的副本不能落在同一台主机上:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: api
topologyKey: kubernetes.io/hostname
containers:
- name: api
image: registry.example.com/api:1.0
该规则意味着 3 个副本至少需要 3 台带有 kubernetes.io/hostname 标签的可用节点;节点数不足时,剩余副本会 Pending。若业务更看重“尽量分散但不阻塞扩容”,应改为 preferredDuringSchedulingIgnoredDuringExecution。
topologyKey 定义“分开或靠近”的范围:
topologyKey | 常见含义 |
|---|---|
kubernetes.io/hostname | 不同节点 |
topology.kubernetes.io/zone | 不同可用区,或同一可用区 |
topology.kubernetes.io/region | 不同区域,通常粒度较大 |
Pod Affinity 和 Pod Anti-Affinity 需要根据集群中已有 Pod 与 Node Label 计算,在大规模集群上可能增加调度成本。高可用副本分布场景也可以评估 Pod Topology Spread Constraints,它通常更直接地表达“跨节点或跨可用区均匀分散”。
驱逐:哪些情况会让 Pod 离开节点
“驱逐”不是单一机制。不同来源对 PDB、终止宽限期和处理方式的影响不同。
| 场景 | 发起者 | PDB 是否生效 | 关键特点 |
|---|---|---|---|
kubectl drain 的 API 驱逐 | 管理员 / API Server | 通常生效 | 适合计划维护,可能因 PDB 被阻塞 |
NoExecute Taint | 节点生命周期控制器或管理员 | 不作为保护条件 | 不匹配 Pod 被驱逐,可用 tolerationSeconds 缓冲 |
| Node-pressure eviction | kubelet | 不生效 | 节点内存、磁盘、inode 或 PID 压力下自救 |
kubectl delete pod | 管理员 | 不生效 | 直接删除 Pod,不等同于 API 驱逐 |
| 抢占(preemption) | scheduler | 不是日常维护手段 | 为高优先级 Pod 腾出调度空间 |
计划维护:cordon、taint 与 drain
维护节点时,典型顺序如下:
# 1. 先确认节点上的工作负载与可用性预算
kubectl get pod -A --field-selector spec.nodeName=worker-01
kubectl get pdb -A
# 2. 停止接收新 Pod
kubectl cordon worker-01
kubectl taint node worker-01 maintenance=true:NoSchedule
# 3. 通过 Eviction API 迁移普通 Pod
kubectl drain worker-01 --ignore-daemonsets --delete-emptydir-data
# 4. 维护完成后恢复调度
kubectl uncordon worker-01
kubectl taint node worker-01 maintenance=true:NoSchedule-
这里的职责分别是:
cordon把节点标记为不可调度,阻止新的常规调度。taint ... NoSchedule是额外的显式隔离,便于表达维护意图或防止特定调度路径误入。drain通过 Eviction API 逐个处理可驱逐 Pod,默认会遵守 PDB 和terminationGracePeriodSeconds。--ignore-daemonsets保留 DaemonSet Pod;--delete-emptydir-data允许删除使用emptyDir的 Pod,其临时数据会丢失。
不要在常规维护中习惯性加 --force 或 --disable-eviction。前者可能绕过对裸 Pod 的保护,后者会绕过 PDB;使用前应明确知道会终止哪些工作负载。
节点压力驱逐
当节点内存、磁盘空间、inode 或 PID 资源达到 kubelet 配置的驱逐阈值时,kubelet 会先尝试回收节点级资源,例如清理无用镜像和已终止容器;仍无法恢复时才驱逐 Pod。
节点压力驱逐与 kubectl drain 不同:
- 不遵守 PodDisruptionBudget。
- 不遵守 Pod 的
terminationGracePeriodSeconds。 - 硬驱逐阈值触发时,终止宽限期为
0s;软阈值可受 kubelet 的eviction-max-pod-grace-period限制。 - kubelet 会综合 Pod 是否超出资源请求、Pod Priority 和相对资源使用量选择驱逐顺序。
所以,PDB 只能帮助计划内的迁移控制并发,不能替代资源请求、限制、容量规划和节点监控。
节点状态 Taint 与 NoExecute
节点不可达或状态异常时,控制平面可能根据 Node Condition 添加 Taint,例如 node.kubernetes.io/not-ready、node.kubernetes.io/unreachable、node.kubernetes.io/memory-pressure、node.kubernetes.io/disk-pressure、node.kubernetes.io/pid-pressure、node.kubernetes.io/network-unavailable 与 node.kubernetes.io/unschedulable。实际 Taint 及 effect 应以集群中的 Node 对象为准:
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
kubectl describe node worker-01
对于需要容忍短暂网络波动的工作负载,可显式配置两个常见的 NoExecute Toleration:
tolerations:
- key: "node.kubernetes.io/not-ready"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 300
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 300
时间不宜机械照搬:设置得短,故障转移更快但可能因短抖动导致频繁迁移;设置得长,抖动更稳定但真正故障时恢复更慢。
排查清单
Pod 一直 Pending,优先检查调度事件:
kubectl describe pod <pod-name> -n <namespace>
kubectl get events -n <namespace> --sort-by=.lastTimestamp
kubectl get nodes -L workload -L topology.kubernetes.io/zone
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
常见事件含义:
0/3 nodes are available: 1 node(s) had untolerated taint {dedicated: gpu}.
表示 Pod 缺少对应 Toleration,或 Toleration 中的 key、value、effect 没有完全匹配。
Pod 被驱逐或异常重建,检查:
kubectl describe pod <pod-name> -n <namespace>
kubectl get events -A --sort-by=.lastTimestamp
kubectl describe node <node-name>
kubectl top node <node-name>
kubectl get pdb -A
重点关注:
- Pod 事件中的
Evicted、Preempted、NodeNotReady、TaintManagerEviction等原因。 - Node Condition 中的
MemoryPressure、DiskPressure、PIDPressure、Ready。 - Pod 的 requests / limits、PriorityClass、QoS 与实际资源消耗。
- PDB 是否让计划维护中的
drain长时间等待。
选型速查
| 目标 | 推荐组合 |
|---|---|
| 保护 GPU、SSD、平台组件等专用节点 | Node Label + NoSchedule Taint + 目标 Pod Toleration + Required Node Affinity |
| 尽量优先使用某类节点 | Preferred Node Affinity;必要时加 PreferNoSchedule |
| 同一服务副本不落在同一节点 | Pod Anti-Affinity,或 Pod Topology Spread Constraints |
| 应用与缓存尽量同可用区 | Preferred Pod Affinity,topologyKey: topology.kubernetes.io/zone |
| 节点例行维护 | cordon + drain,确认 PDB 和 emptyDir 风险 |
| 节点短暂不可达时避免立即迁移 | NoExecute Toleration + 合理的 tolerationSeconds |
| 避免节点压力下关键服务被优先淘汰 | 合理 requests / limits、PriorityClass、容量余量与监控告警 |
使用建议
- Label 表示节点能力或属性,Taint 表示准入限制;不要只用其中一个来表达完整隔离。
- 尽量把“必须”与“最好”分开:必须使用
required...,偏好使用preferred...或PreferNoSchedule。 - 对业务 Pod 避免宽泛的
ExistsToleration,尤其是空 key 的 Toleration。 NoExecute要与业务恢复目标一起评估,不能只因“防抖”就无限容忍。- 维护节点先看 PDB、副本数、备用容量和本地临时数据,再执行
drain。 - 面对节点压力,优先治理资源请求、限制和节点容量;PDB 不能阻止 kubelet 自救驱逐。
- 不要通过
.spec.nodeName强行绑定节点来绕过调度约束。它会绕过 scheduler 对NoScheduleTaint 的检查,NoExecute仍可能让 kubelet 驱逐 Pod。