Kubernetes 基础体系 · 第 23/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes 调度约束:NodeSelector、Affinity、Taint、Topology 和 Spread
Kubernetes 调度约束解决的是一个核心问题:当 Pod 进入调度队列后,调度器如何判断某个节点是否“允许”承载它,以及在多个可用节点之间如何选择更合适的节点。
这些机制作用层次不同:
nodeSelector:按节点标签做简单的硬匹配。nodeAffinity:按节点标签做更丰富的硬约束或软偏好。podAffinity/podAntiAffinity:根据其他 Pod 的位置决定当前 Pod 应该靠近或远离哪些 Pod。taint/toleration:节点声明“通常不接纳哪些 Pod”,Pod 通过容忍来获得进入资格。topologyKey:定义“节点属于哪个拓扑域”,例如可用区、机架或自定义故障域。topologySpreadConstraints:限制一组 Pod 在多个拓扑域之间的不均衡程度。
理解这些字段时,必须区分两类行为:
- 硬约束:不满足就不能调度。
- 软偏好:满足条件的节点会被优先选择,但没有合适节点时仍可能调度。
调度器通常先执行可行性筛选,再进行打分,最后绑定:
Pod 创建
│
▼
调度队列 Queue
│
▼
Filter:排除违反硬约束、资源不足或不满足其他条件的节点
│
▼
Score:根据软偏好和资源策略给可行节点打分
│
▼
选择最高分节点
│
▼
Bind:写入 Pod.spec.nodeName
这不是“所有字段简单相加”。不同约束由不同调度插件参与,最终分数还会经过插件权重和归一化处理。因此,不能仅凭 YAML 推导出调度器一定选择某个节点。
一、调度约束的共同基础:节点、标签与拓扑域
1. 节点标签是匹配的输入
节点标签是键值对:
metadata:
labels:
kubernetes.io/os: linux
kubernetes.io/arch: amd64
node-pool: compute
topology.kubernetes.io/zone: zone-a
Pod 通过这些标签表达要求。例如:
spec:
nodeSelector:
node-pool: compute
表示 Pod 只能调度到包含:
node-pool=compute
的节点。
查看节点标签:
kubectl get nodes --show-labels
kubectl get nodes -L node-pool,topology.kubernetes.io/zone
为测试节点添加标签:
kubectl label node worker-1 node-pool=compute
删除标签:
kubectl label node worker-1 node-pool-
节点标签是调度输入,不是天然可信的安全边界。若普通用户能够修改节点标签,就可能把 Pod 引导到不应运行的位置。生产环境应限制节点标签修改权限,并关注 Node authorizer、NodeRestriction admission plugin 等安全机制。云厂商提供的节点池和区域标签也可能因发行版、云平台或自建集群而不同,不能只根据名称假定一定存在。
2. Topology 不是 Kubernetes 对象
topology 在这里表示节点之间的层次或故障域关系。常见拓扑标签包括:
topology.kubernetes.io/zone
topology.kubernetes.io/region
kubernetes.io/hostname
如果节点标签如下:
| 节点 | topology.kubernetes.io/zone |
kubernetes.io/hostname |
|---|---|---|
| node-a1 | zone-a | node-a1 |
| node-a2 | zone-a | node-a2 |
| node-b1 | zone-b | node-b1 |
那么:
- 以
topology.kubernetes.io/zone为topologyKey时,有两个域:zone-a、zone-b; - 以
kubernetes.io/hostname为topologyKey时,每个节点通常都是一个独立域。
拓扑域是否真实代表独立故障域,取决于底层基础设施。一个自建集群中的 zone-a 标签,如果实际上只是人为命名,并不自动意味着它们位于不同机房或电源域。
二、NodeSelector:最简单的节点硬约束
nodeSelector 是 PodSpec 中的一个键值映射:
apiVersion: v1
kind: Pod
metadata:
name: selector-demo
spec:
nodeSelector:
node-pool: compute
containers:
- name: app
image: registry.k8s.io/pause:3.10
它的逻辑是:
节点可行 ⇔ 对每个 key,都存在完全相等的 node.label[key] == value
例如:
Pod 要求:node-pool=compute,kubernetes.io/arch=amd64
节点 node-1:
node-pool=compute
kubernetes.io/arch=arm64
结果:不可行
节点 node-2:
node-pool=compute
kubernetes.io/arch=amd64
结果:可行
多个键之间是逻辑 AND,没有 OR、存在性匹配、数值比较或否定表达式。
典型失败表现
如果集群中没有匹配节点,Pod 会保持 Pending。事件通常可以看到类似信息:
kubectl describe pod selector-demo
可能包含:
0/3 nodes are available: 3 node(s) didn't match Pod's node affinity/selector.
事件文本由 Kubernetes 版本和调度插件实现决定,不能把具体英文文本当成稳定 API;稳定的事实是调度器会记录未调度原因。
NodeSelector 与资源条件是 AND 关系
匹配标签并不代表一定能运行。调度器仍需检查:
- CPU、内存和扩展资源请求;
- 节点是否不可调度;
- 污点和容忍;
- Pod 的硬亲和性和反亲和性;
- 拓扑分布约束;
- 卷的拓扑限制;
- 其他调度插件条件。
因此:
可调度节点
= 满足 nodeSelector
AND 资源足够
AND 满足 taint/toleration
AND 满足其他硬约束
三、Node Affinity:节点硬约束与软偏好
nodeAffinity 是比 nodeSelector 更丰富的节点标签约束。它位于:
spec:
affinity:
nodeAffinity:
支持两种主要模式:
requiredDuringSchedulingIgnoredDuringExecution:调度时必须满足;preferredDuringSchedulingIgnoredDuringExecution:调度时尽量满足。
这里的 IgnoredDuringExecution 很重要:Pod 调度后,如果节点标签后来发生变化,Kubernetes 通常不会因为该变化自动驱逐已经运行的 Pod。它只影响后续调度。当前稳定 API 中并没有一个通用的 requiredDuringSchedulingRequiredDuringExecution 字段可直接使用。
1. 硬节点亲和性
apiVersion: v1
kind: Pod
metadata:
name: required-affinity-demo
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-pool
operator: In
values:
- compute
- memory
- key: kubernetes.io/arch
operator: In
values:
- amd64
containers:
- name: app
image: registry.k8s.io/pause:3.10
其逻辑需要特别准确地理解:
nodeSelectorTerms之间是 OR;- 同一个
nodeSelectorTerm内的matchExpressions之间是 AND; - 同一个表达式中的
values,对于In操作符表示 OR。
形式化表示为:
term1 OR term2 OR ...
其中:
termi = expressioni,1 AND expressioni,2 AND ...
上例表示:
(node-pool=compute OR node-pool=memory)
AND
(kubernetes.io/arch=amd64)
不是:
(node-pool=compute AND arch=amd64)
OR
(node-pool=memory)
常见操作符包括:
In:标签值属于给定集合;NotIn:标签值不属于给定集合,缺少该标签也会影响匹配语义,使用时应验证目标版本行为;Exists:存在该标签;DoesNotExist:不存在该标签;Gt、Lt:对标签值进行整数比较,适用范围和解析规则比普通字符串匹配更严格。
Exists 和 DoesNotExist 不应填写 values。例如:
- key: accelerator
operator: Exists
表示节点只要存在 accelerator 标签即可,不关心值。
2. 软节点亲和性
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: node-pool
operator: In
values:
- compute
- weight: 20
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- zone-a
每个偏好项的 weight 必须在 1 到 100 之间。一个节点满足某个偏好项,就获得该项对应的偏好分值;调度器会结合多个插件的评分选择节点。
但以下推导是不成立的:
weight=80 的节点一定比 weight=20 的节点优先
原因是:
- 不同节点可能分别满足多个偏好项;
- 调度器会对插件分数进行归一化;
- 插件有调度权重;
- 资源均衡、Pod 拓扑、镜像本地性等其他插件也会参与评分;
- 并列时还可能使用实现相关的决策逻辑。
软亲和性适合表达“优先使用某类节点”,不适合表达“绝不能使用某类节点”。
3. nodeSelector 与 nodeAffinity 同时存在
如果两者同时设置,必须同时满足:
spec:
nodeSelector:
node-pool: compute
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values: [amd64]
等价于:
node-pool=compute
AND
kubernetes.io/arch=amd64
它们不是“二选一”,也不是后者覆盖前者。
四、Pod Affinity 与 Pod Anti-Affinity:根据其他 Pod 的位置调度
节点亲和性看的是:
目标节点自己的标签
Pod 亲和性和反亲和性看的是:
其他 Pod 的标签 + 其他 Pod 所在节点的拓扑标签
例如,一个 Web Pod 可以要求自己与带有 app=cache 的 Pod 位于同一可用区。
apiVersion: v1
kind: Pod
metadata:
name: web
labels:
app: web
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: cache
topologyKey: topology.kubernetes.io/zone
containers:
- name: app
image: registry.k8s.io/pause:3.10
它的实际含义不是“节点必须有 app=cache 标签”,而是:
存在某个带 app=cache 的 Pod,
并且该 Pod 所在节点的 zone
等于候选节点的 zone
1. 亲和性匹配的基本条件
对每一个候选节点,调度器大致需要判断:
- 找出符合
labelSelector的已有 Pod; - 限制搜索的命名空间;
- 读取这些 Pod 所在节点的
topologyKey标签值; - 判断候选节点是否位于相同拓扑域;
- 对硬亲和性,不满足则过滤;对软亲和性,则参与打分。
默认情况下,Pod 亲和性规则匹配同一命名空间中的 Pod。可以用 namespaces 或 namespaceSelector 改变范围:
- labelSelector:
matchLabels:
app: cache
namespaceSelector:
matchLabels:
team: platform
topologyKey: topology.kubernetes.io/zone
namespaceSelector 本身选择命名空间,而不是 Pod;随后还要在这些命名空间中按 labelSelector 选择 Pod。
2. Pod Anti-Affinity
反亲和性表示尽量或必须远离匹配的 Pod:
apiVersion: v1
kind: Pod
metadata:
name: replica
labels:
app: api
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: api
topologyKey: kubernetes.io/hostname
containers:
- name: app
image: registry.k8s.io/pause:3.10
topologyKey: kubernetes.io/hostname 通常表示:
同一节点内不能出现匹配的 api Pod
如果改成:
topologyKey: topology.kubernetes.io/zone
则表示:
同一可用区内不能出现匹配的 api Pod
后者通常非常严格。假设只有三个可用区,却部署四个副本,第四个副本无法满足“每个 zone 至多一个”的硬反亲和性,因此会保持 Pending。
Pod 反亲和性还具有明显的计算成本:调度器需要检查现有 Pod 与候选节点的拓扑关系。高规模集群中,大量复杂的硬 Pod 亲和性规则可能增加调度延迟。它也不像拓扑分布约束那样直接表达“尽量均匀分布”,而更适合表达“不要共置”这一类关系。
五、Taint 与 Toleration:节点的拒绝条件
亲和性是 Pod 主动选择节点;污点是节点主动拒绝 Pod。
给节点添加污点:
kubectl taint nodes worker-gpu dedicated=gpu:NoSchedule
它表示:
节点存在 dedicated=gpu:NoSchedule 污点;
没有匹配容忍的 Pod 不应被调度到该节点。
对应的 Pod 容忍如下:
spec:
tolerations:
- key: dedicated
operator: Equal
value: gpu
effect: NoSchedule
operator: Exists 可以忽略具体值:
spec:
tolerations:
- key: dedicated
operator: Exists
effect: NoSchedule
当 key 为空时,operator 必须是 Exists,表示匹配指定 effect 的所有污点。若 effect 为空,则可匹配该 key 的所有 effect。生产配置中应尽量写出明确的 key、value 和 effect,避免容忍范围过大。
1. 三种主要污点效果
NoSchedule
没有匹配容忍的 Pod 不能新调度到节点。
它主要影响调度阶段,不会因为添加污点就自动删除已经运行的普通 Pod。
PreferNoSchedule
调度器尽量避开该节点,但这不是绝对禁止。若其他节点不合适,Pod 仍可能被调度到这里。
NoExecute
除了影响新调度,还会影响已经运行的 Pod:
- 没有匹配容忍的 Pod 会被驱逐;
- 有匹配容忍但设置了
tolerationSeconds的 Pod,在容忍时间到期后可能被驱逐; - 永久容忍的 Pod 可以继续运行。
示例:
spec:
tolerations:
- key: node.kubernetes.io/not-ready
operator: Exists
effect: NoExecute
tolerationSeconds: 300
这表示 Pod 可以在节点 NotReady 污点下继续运行 300 秒。真正执行已运行 Pod 驱逐的主要是节点侧组件和控制器路径,而不是简单理解为“调度器删除 Pod”。
2. 容忍不等于吸引
这是最常见的误解:
Pod tolerates dedicated=gpu
只表示:
它不会因为该污点被拒绝
并不表示:
它会优先去 GPU 节点
如果希望 Pod 只使用 GPU 节点,通常需要同时配置:
spec:
nodeSelector:
accelerator: nvidia
tolerations:
- key: dedicated
operator: Equal
value: gpu
effect: NoSchedule
二者分别表达:
- 节点选择:我想去哪里;
- 污点容忍:我能否进入那里。
3. 多个污点的逻辑
假设节点有:
dedicated=gpu:NoSchedule
maintenance=true:NoExecute
Pod 必须分别有匹配的容忍。只容忍第一个污点,仍会受到第二个污点的影响。
形式化地说:
Pod 可通过污点检查
⇔ 对每一个影响它的污点,都存在匹配 toleration
匹配通常要求:
- key 相同,除非使用合法的空 key +
Exists; operator: Equal时 value 相同;- effect 相同,除非容忍 effect 为空;
operator: Exists时不比较 value。
4. 节点状态产生的污点
节点控制器可能添加诸如:
node.kubernetes.io/not-ready
node.kubernetes.io/unreachable
node.kubernetes.io/memory-pressure
node.kubernetes.io/disk-pressure
node.kubernetes.io/network-unavailable
集群中的 DaemonSet、系统 Pod 或云厂商组件也可能配置自动容忍。这些自动行为依赖控制器和发行版配置,排查 Pod 为什么能留在异常节点上时,应直接查看 Pod 的完整 tolerations:
kubectl get pod <pod> -o yaml
kubectl describe node <node>
六、Topology Spread Constraints:控制 Pod 的不均衡程度
拓扑分布约束用于表达:
一组匹配 Pod 在多个拓扑域之间最多允许有多大数量差异
示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 6
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: api
containers:
- name: app
image: registry.k8s.io/pause:3.10
字段含义:
maxSkew:允许的最大不均衡程度;topologyKey:用于划分拓扑域的节点标签;whenUnsatisfiable:DoNotSchedule:无法满足时过滤节点;ScheduleAnyway:仍然调度,但尽量改善分布;
labelSelector:统计哪些已有 Pod;minDomains:在支持该字段的集群版本中,可声明期望的最少拓扑域数量;版本和特性状态应以目标集群文档为准。
1. maxSkew 的计算
设:
count(d):拓扑域d中匹配 Pod 的数量;globalMinimum:参与计算的拓扑域中最小的 Pod 数量;- 将候选 Pod 放入域
d后,该域数量变成count(d)+1。
则候选放置的偏斜度可理解为:
skew(d) = count(d) + 1 - globalMinimum
当:
skew(d) > maxSkew
且 whenUnsatisfiable=DoNotSchedule 时,该节点不能作为候选。
完整算例:
| 拓扑域 | 当前匹配 Pod 数 |
|---|---|
| zone-a | 3 |
| zone-b | 1 |
| zone-c | 0 |
因此:
globalMinimum = 0
maxSkew = 2
尝试把下一个 Pod 放入不同域:
| 目标域 | 放置后数量 | 偏斜度 | 结果 |
|---|---|---|---|
| zone-a | 4 | 4 - 0 = 4 | 拒绝 |
| zone-b | 2 | 2 - 0 = 2 | 允许 |
| zone-c | 1 | 1 - 0 = 1 | 允许 |
这里容易产生错误直觉:有人会比较 zone-a=3 和 zone-b=1 的差值 2,认为再放一个到 zone-a 只是差 3。实际上 globalMinimum 仍包括空的 zone-c,所以候选放入 zone-a 后的 skew 是 4。
如果只有两个参与计算的域:
| 拓扑域 | 当前匹配 Pod 数 |
|---|---|
| zone-a | 3 |
| zone-b | 1 |
则:
globalMinimum = 1
把 Pod 放入 zone-a 后:
skew = 4 - 1 = 3
即使同样是 maxSkew=2,结果也不同。可见参与计算的拓扑域集合非常重要。
2. DoNotSchedule 与 ScheduleAnyway
whenUnsatisfiable: DoNotSchedule
是硬约束。违反时,调度器在 Filter 阶段排除节点。
whenUnsatisfiable: ScheduleAnyway
是软约束。调度器会通过评分优先选择能改善分布的节点,但如果资源、污点或其他约束使其不合适,仍可能选择导致暂时不均衡的节点。
对于跨可用区高可用服务,DoNotSchedule 不能脱离容量设计使用。如果某个可用区没有可用节点、节点资源不足或卷不能跨区挂载,严格分布会直接导致 Pod Pending,而不是自动牺牲约束来恢复服务。
3. minDomains 的边界
minDomains 用于表达“至少应有多少个拓扑域参与分布”。它适合防止只有一个可用域时,调度器把所有副本都集中到该域却仍被认为满足分布。
不过,minDomains 的 API 特性状态、默认行为和对 globalMinimum 的影响具有版本敏感性。使用前应确认目标集群支持的 Kubernetes 版本;不能把新版本文档中的字段直接复制到旧集群。
4. 统计对象不等于 Deployment 副本数
labelSelector 决定统计哪些已有 Pod。若标签写得过宽:
labelSelector:
matchLabels:
app: api
可能把多个工作负载都算入同一分布集合;若标签写得过窄,则可能完全没有统计到目标 Pod。
因此,Deployment 的 selector、Pod template labels 和 spread constraint 的 selector 应共同设计。尤其要避免修改标签后造成新旧 Pod 不再属于同一分布集合。
七、Topology Spread 与 Pod Anti-Affinity 的区别
两者都能影响 Pod 的位置,但表达能力不同。
1. Pod Anti-Affinity 更像“禁止共置”
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: api
topologyKey: kubernetes.io/hostname
含义是:同一节点不能出现匹配 Pod。
它适合:
一个节点最多一个副本
但它不直接表达:
zone-a 有 3 个,zone-b 有 2 个,zone-c 有 1 个时,下一步去哪
2. Topology Spread 更像“限制数量差”
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
它适合表达:
不同可用区之间的副本数尽量均匀
这允许一个域拥有多个副本,只要数量差不超过约束。
3. 两者同时使用时是 AND
如果同时配置:
- zone 级别的 spread;
- hostname 级别的硬反亲和性;
那么 Pod 必须同时满足:
zone 分布约束
AND
同节点不能共置
这可能是合理的高可用设计,也可能过于严格。部署副本数、节点数、可用区数量和故障恢复后的容量必须一起计算。
八、多个约束组合时如何推导结果
考虑下面的 Pod:
apiVersion: v1
kind: Pod
metadata:
name: combined-demo
labels:
app: api
spec:
nodeSelector:
node-pool: compute
tolerations:
- key: dedicated
operator: Equal
value: api
effect: NoSchedule
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: [zone-a]
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: api
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: api
containers:
- name: app
image: registry.k8s.io/pause:3.10
调度一个新副本时,可以按以下顺序理解:
第一步:NodeSelector 筛选
节点必须满足:
node-pool=compute
否则直接不可行。
第二步:污点筛选
节点若有:
dedicated=api:NoSchedule
则该 Pod 可以通过,因为容忍匹配。
节点若有:
dedicated=gpu:NoSchedule
则仍然不能通过,因为没有对应容忍。
第三步:节点硬约束和资源检查
本例没有硬 nodeAffinity,但调度器仍检查 CPU、内存、卷、节点状态等条件。
第四步:Pod 反亲和性检查
候选节点的 hostname 域中不能已经有带:
app=api
的 Pod。
第五步:拓扑分布检查
统计所有相关 app=api Pod 在各 zone 的数量,检查放入候选 zone 后是否满足:
skew <= 1
由于本例使用 DoNotSchedule,不满足就过滤。
第六步:软偏好评分
对剩余节点,优先选择位于 zone-a 的节点,但这只是偏好,不会推翻前面任何硬条件。
最终可以概括为:
候选节点
= NodeSelector
AND Taint/Toleration
AND 资源与系统条件
AND PodAntiAffinity
AND TopologySpread
然后在候选节点中依据软 nodeAffinity 和其他评分插件进行选择。
九、调度失败、抢占与约束的关系
当所有节点都不可行时,调度器可能考虑抢占低优先级 Pod。抢占的作用是释放资源,而不是绕过硬约束。
例如:
节点有足够 CPU,但违反 required nodeAffinity
驱逐其他 Pod 也不能让该节点变成可行节点。
同样:
节点带 NoSchedule 污点,目标 Pod 没有容忍
抢占普通 Pod 不会自动授予目标 Pod 容忍资格。
因此,抢占能够解决的主要是:
资源不足导致的不可行
而通常不能解决:
标签不匹配
硬亲和性不满足
硬反亲和性冲突
污点没有容忍
拓扑硬约束冲突
卷拓扑不兼容
一个优先级很高的 Pod,如果自身硬约束过严,仍然可能长期 Pending。这也是优先级、抢占和调度约束之间的边界:优先级改变竞争顺序,不能把错误的约束变成正确的约束。
十、完整验证示例
创建一个测试节点标签:
kubectl label node worker-1 node-pool=compute topology.kubernetes.io/zone=zone-a
kubectl label node worker-2 node-pool=general topology.kubernetes.io/zone=zone-b
创建一个只允许 compute 节点的 Pod:
apiVersion: v1
kind: Pod
metadata:
name: scheduling-test
spec:
nodeSelector:
node-pool: compute
containers:
- name: pause
image: registry.k8s.io/pause:3.10
应用并查看:
kubectl apply -f scheduling-test.yaml
kubectl get pod scheduling-test -o wide
预期结果是 Pod 被绑定到标签为 node-pool=compute 的节点。如果 Pod 为 Pending:
kubectl describe pod scheduling-test
kubectl get events --sort-by=.lastTimestamp
重点查看:
- 是否没有节点匹配标签;
- 是否 CPU 或内存不足;
- 是否有未容忍污点;
- 是否存在亲和性或拓扑约束冲突;
- 是否存在卷拓扑限制。
查看实际绑定结果:
kubectl get pod scheduling-test \
-o jsonpath='{.spec.nodeName}{"\n"}'
查看调度器写入的事件:
kubectl get events \
--field-selector involvedObject.name=scheduling-test \
--sort-by=.lastTimestamp
删除测试对象:
kubectl delete pod scheduling-test
验证污点:
kubectl taint nodes worker-1 dedicated=api:NoSchedule
kubectl describe node worker-1 | grep -A5 Taints
此时,原本只满足 nodeSelector 的 Pod 如果没有 dedicated=api 容忍,就可能无法调度到 worker-1。添加容忍后,只是恢复资格;若还希望它主动优先去该节点,仍需节点标签和亲和性偏好。
十一、常见误区与生产边界
1. 把 preferred 当成强制条件
preferredDuringSchedulingIgnoredDuringExecution 失败不会让 Pod Pending。如果业务绝不能运行在某类节点上,必须使用 requiredDuringSchedulingIgnoredDuringExecution、nodeSelector 或其他硬约束。
2. 认为 toleration 会把 Pod 引导到目标节点
容忍只解除污点造成的拒绝。需要“定向进入”时,应同时使用节点标签和节点亲和性。
3. 把 NoExecute 当成只影响新 Pod
NoExecute 还可能驱逐已经运行的 Pod。修改节点污点前,应确认工作负载的容忍时间、控制器重建行为和业务对短暂迁移的承受能力。
4. 使用不存在或不一致的拓扑标签
如果约束使用:
topologyKey: topology.kubernetes.io/zone
但节点没有这个标签,拓扑域计算就可能与预期完全不同。应先检查:
kubectl get nodes \
-L topology.kubernetes.io/zone,topology.kubernetes.io/region,kubernetes.io/hostname
云平台的 zone 标签通常由云控制器或节点初始化组件提供;自建集群必须自行保证标签来源、命名和持续性。
5. 过度使用硬 Pod 反亲和性
“每个 zone 不能有两个副本”和“每个节点不能有两个副本”是不同约束。副本数、节点数不足或节点故障时,硬反亲和性会直接造成 Pending。若目标只是改善分布而不是绝对禁止,可以考虑 ScheduleAnyway 的拓扑分布约束或软反亲和性。
6. 忽略标签变更后的执行期语义
带有 IgnoredDuringExecution 的节点亲和性主要在调度时生效。节点标签被修改后,已经运行的 Pod 通常不会自动因为该规则被驱逐。若标签代表安全边界、硬件能力或合规属性,不能仅依赖调度时的一次判断,还要控制标签写权限并设计额外的运行期校验。
7. 只看 Pod YAML,不看节点和事件
调度问题至少需要同时检查:
kubectl get pod <pod> -o yaml
kubectl describe pod <pod>
kubectl describe node <node>
kubectl get nodes --show-labels
kubectl get events --sort-by=.lastTimestamp
Pod YAML 告诉你声明了什么,节点信息告诉你实际提供了什么,事件告诉你调度器在哪个阶段拒绝了候选节点。只有三者结合,才能区分“没有匹配标签”“资源不足”“污点未容忍”和“拓扑约束冲突”。
十二、如何选择这些机制
可以把常见需求映射为以下语义:
| 需求 | 更合适的机制 |
|---|---|
只运行在 node-pool=compute |
nodeSelector |
| 节点标签需要 OR、Exists、NotIn 或软偏好 | nodeAffinity |
| 与某类 Pod 同 zone | podAffinity |
| 同一节点或同一 zone 不共置 | podAntiAffinity |
| 节点只接纳专用工作负载 | taint + Pod toleration |
| 容灾副本跨 zone 均匀分布 | topologySpreadConstraints |
| 节点异常后允许 Pod 暂留一段时间 | NoExecute + tolerationSeconds |
| 高优先级 Pod 资源不足时清理低优先级 Pod | PriorityClass + 抢占,但不能绕过硬约束 |
最重要的设计原则不是“字段越多越可靠”,而是让每个字段只表达一种明确关系:
nodeSelector / required nodeAffinity:我必须去哪里
preferred nodeAffinity:我更想去哪里
toleration:我可以进入哪里
taint:这里通常不接纳谁
podAffinity:我要靠近谁
podAntiAffinity:我要远离谁
topologySpread:数量差最多允许多大
当多个硬约束交集为空时,调度器不会替应用猜测哪条规则更重要,而是让 Pod 保持 Pending。因此,部署前应按节点标签、污点、资源、拓扑域数量和副本数逐项计算可行集合;运行中则通过 Pod 事件、节点状态和实际标签验证约束是否仍然成立。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:ConfigMap 与 Secret:注入、更新、不可变、轮换和安全边界
- 下一篇:Kubernetes Priority 与 Preemption:优先级、抢占、公平和关键服务保护
- 延伸:Kubernetes 调度器:Queue、Filter、Score、Bind、插件和扩展
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论