说明:本文由 Codex 根据作者提供的主题、素材与公开资料辅助生成,属于 AI 生成/整理内容,非作者原创。请读者自行甄别并交叉验证。
先建立一张心智地图#
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 进入被污点保护的节点 | 可配合 NoExecute 延迟驱逐 |
| Eviction | 系统或管理员终止 Pod | 节点维护、资源压力、节点故障 | 会终止或迁移 Pod |
一句话记忆:
亲和性是“我想去哪里”;
污点是“谁不该来”;
容忍是“我可以进去”;
驱逐是“已经在这里的 Pod 何时必须离开”。三个常见误区也可以先排除:
- 容忍不等于选择。Pod 容忍 GPU 节点,并不代表它一定会被调度到 GPU 节点。
NoSchedule不等于驱逐。它只阻止新的不匹配 Pod 调度进来。- PDB 不是绝对保护。它主要约束自愿性中断,不能阻止节点压力驱逐。
调度与运行时的边界#
下面是一个便于理解的逻辑流程,不代表 kube-scheduler 内部插件的精确执行顺序:
创建未绑定 Node 的 Pod
|
v
过滤不满足硬条件的节点
资源请求、nodeSelector、required Node Affinity、
未被容忍的 NoSchedule / NoExecute 污点等
|
v
对可用节点打分
preferred Affinity、PreferNoSchedule、资源分布等
|
v
绑定 Pod 到某个 Node
|
v
运行时持续观察节点状态
节点维护、NoExecute 污点、节点不可达、资源压力等
|
v
必要时终止或驱逐 Pod,由控制器创建替代副本因此,调度规则大多决定“新 Pod 去哪里”,而驱逐规则处理“运行中的 Pod 是否还能留在原节点”。这两个阶段不要混为一谈。
污点与容忍:节点隔离的反向筛选#
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此后,没有匹配容忍的 Pod 不能新调度到该节点。查看与移除污点:
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 | 没有匹配容忍则不能调度 | 不驱逐 | 专用节点、GPU 节点、维护前封锁 |
PreferNoSchedule | 调度器尽量避开,但不保证 | 不驱逐 | 软隔离、临时倾向 |
NoExecute | 没有匹配容忍则不能调度 | 驱逐不匹配 Pod | 节点异常、强制隔离 |
同一 Node 可以有多个污点。Kubernetes 会先从全部污点中移除被 Pod 容忍的部分,再根据剩余污点处理:
- 剩余任意一个
NoSchedule,Pod 不能调度到该节点。 - 没有
NoSchedule,但有PreferNoSchedule,调度器会尽量避开该节点。 - 剩余任意一个
NoExecute,新 Pod 不能调度;已有 Pod 会被驱逐。
容忍字段与匹配规则#
容忍写在 Pod 的 .spec.tolerations 中:
tolerations:
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"| 字段 | 含义 |
|---|---|
key | 要匹配的污点键 |
operator | Equal 或 Exists;省略时默认 Equal |
value | 仅 Equal 使用,必须与污点值一致 |
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 污点。业务 Pod 一般不应这样写,否则专用节点隔离会失效。
NoExecute 与 tolerationSeconds#
NoExecute 是唯一会直接影响已有 Pod 的 effect。下面的配置表示:如果节点出现匹配污点,Pod 最多可继续运行 300 秒;污点在这段时间内被移除,则不会被驱逐。
tolerations:
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 300没有 tolerationSeconds 时,匹配的 Pod 会一直容忍该 NoExecute 污点;没有匹配容忍时,Pod 会被立即驱逐。因此,对 NoExecute 的容忍要明确业务对短暂故障和快速故障切换的取舍。
推荐组合:专用节点池#
生产环境中,单独使用容忍通常不够。以下组合能同时表达“普通 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这四层的职责不同:
- 没有容忍的普通 Pod 会被污点挡在 GPU 节点外。
- 带容忍但没有亲和性的 Pod 可以进入 GPU 节点,却不保证优先落在那里。
- 同时带容忍和必需亲和性的 GPU 任务,才会只在 GPU 节点池中寻找位置。
- GPU 资源请求仍是调度器判断该节点是否有可用 GPU 的依据。
DaemonSet 系统组件#
CNI、kube-proxy、日志采集和节点监控等组件通常必须在每个节点运行。它们一般通过 DaemonSet 部署,并需要容忍控制平面、节点状态或维护相关的污点。
DaemonSet 控制器会为部分常见节点状态自动补充容忍;不同 Kubernetes 版本、控制器清单和节点角色的最终行为仍应以实际 Pod 规格为准。不要为了“跑遍所有节点”而给业务 Pod 添加宽泛的 Exists 容忍,也不要手工覆盖系统 DaemonSet 的容忍策略却不验证其节点故障行为。
# 查看 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亲和性:从“允许”到“选择”#
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 亲和性和反亲和性需要根据集群中已有 Pod 与 Node Label 计算,在大规模集群上可能增加调度成本。高可用副本分布场景也可以评估 Pod Topology Spread Constraints,它通常更直接地表达“跨节点或跨可用区均匀分散”。
驱逐:哪些情况会让 Pod 离开节点#
“驱逐”不是单一机制。不同来源对 PDB、终止宽限期和处理方式的影响不同。
| 场景 | 发起者 | PDB 是否生效 | 关键特点 |
|---|---|---|---|
kubectl drain 的 API 驱逐 | 管理员 / API Server | 通常生效 | 适合计划维护,可能因 PDB 被阻塞 |
NoExecute 污点 | 节点生命周期控制器或管理员 | 不作为保护条件 | 不匹配 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 只能帮助计划内的迁移控制并发,不能替代资源请求、限制、容量规划和节点监控。
节点状态污点与 NoExecute#
节点不可达或状态异常时,控制平面可能根据 Node Condition 添加污点,例如 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。实际污点及 effect 应以集群中的 Node 对象为准:
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
kubectl describe node worker-01对于需要容忍短暂网络波动的工作负载,可显式配置两个常见的 NoExecute 容忍:
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 缺少对应容忍,或容忍中的 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 避免宽泛的
Exists容忍,尤其是空 key 的容忍。 NoExecute要与业务恢复目标一起评估,不能只因“防抖”就无限容忍。- 维护节点先看 PDB、副本数、备用容量和本地临时数据,再执行
drain。 - 面对节点压力,优先治理资源请求、限制和节点容量;PDB 不能阻止 kubelet 自救驱逐。
- 不要通过
.spec.nodeName强行绑定节点来绕过调度约束。它会绕过 scheduler 对NoSchedule污点的检查,NoExecute仍可能让 kubelet 驱逐 Pod。