学到这里,会发现默认调度虽然已经很智能,但很多业务并不满足于“能跑就行”。
现实场景里,我们经常会提出更细的要求:
- 这个 Pod 必须跑在 SSD 节点上;
- 这个服务最好和同类 Pod 分散到不同节点;
- 这批 GPU 节点只允许特定业务使用;
- 某些核心服务最好和某个缓存组件部署得更近。
这些需求,靠的就是亲和性(Affinity)和污点容忍(Taint / Toleration)。
官方文档说明,Kubernetes 提供了多种方式来约束 Pod 运行在哪些节点上,推荐方式通常都围绕标签选择器展开;在很多情况下,你也可以不设置这些约束,让默认调度器自动做合理放置。::cite[229]
1. NodeSelector 与 Node Affinity
这两者解决的是同一类问题:Pod 应该去哪些节点。
区别在于:
nodeSelector:简单、直接、硬匹配;nodeAffinity:表达能力更强,支持硬约束和软偏好。
1.1 NodeSelector:最简单的节点选择方式
先给节点打标签:
kubectl label node worker-1 disktype=ssd zone=hangzhou-a
然后创建 Pod:
apiVersion: v1
kind: Pod
metadata:
name: node-selector-demo
spec:
containers:
- name: app
image: nginx:1.27
nodeSelector:
disktype: ssd
这个 Pod 只能落到带 disktype=ssd 的节点。
优点是简单,缺点是能力有限:
- 只能做精确匹配;
- 不能表达“最好去某类节点”;
- 不能写复杂组合条件。
1.2 Node Affinity:更灵活的节点亲和性
官方节点分配文档说明,你既可以限制 Pod 只能运行在特定节点上,也可以表达“更倾向于”运行在某些节点上。::cite[229]
Node Affinity 分两类:
requiredDuringSchedulingIgnoredDuringExecution:硬约束preferredDuringSchedulingIgnoredDuringExecution:软偏好
下面是一个完整示例:
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-demo
spec:
containers:
- name: app
image: nginx:1.27
resources:
requests:
cpu: "100m"
memory: "128Mi"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: zone
operator: In
values:
- hangzhou-a
这段配置的意思是:
- 必须在
disktype=ssd的节点上; - 如果有多个候选节点,优先选择
zone=hangzhou-a的节点。
1.3 NodeSelector 与 Node Affinity 怎么选
| 方式 | 表达能力 | 适合场景 |
|---|---|---|
| nodeSelector | 简单精确匹配 | 条件很少、快速上手 |
| nodeAffinity | 强,支持硬约束和偏好 | 生产环境常用 |
如果你只是做入门实验,nodeSelector 足够;如果要做正式策略控制,通常建议优先考虑 nodeAffinity。
2. Pod Affinity 与 Pod Anti-Affinity
上面解决的是“Pod 和 Node 的关系”。
接下来是另一类更高级的需求:Pod 和 Pod 之间的关系。
2.1 Pod Affinity:希望靠近某类 Pod
适合场景:
- Web Pod 希望和缓存 Pod 放得近一点;
- 业务服务希望与同可用区内的依赖组件就近部署;
- 降低跨节点、跨拓扑域通信开销。
示例:
apiVersion: v1
kind: Pod
metadata:
name: frontend-with-cache-affinity
labels:
app: frontend
spec:
containers:
- name: app
image: nginx:1.27
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cache
topologyKey: kubernetes.io/hostname
含义:这个 Pod 只会被调度到同一节点上已经有 app=cache Pod 的地方。
2.2 Pod Anti-Affinity:希望远离某类 Pod
这是生产中更常见的能力,尤其适合高可用部署。
比如一个 Deployment 有 3 个副本,我们往往希望它们不要全堆到同一台节点,否则单节点宕机会一下子打掉所有副本。
示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-ha-demo
spec:
replicas: 3
selector:
matchLabels:
app: web-ha
template:
metadata:
labels:
app: web-ha
spec:
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 80
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-ha
topologyKey: kubernetes.io/hostname
这表示:同一个节点上不能同时放多个 app=web-ha 的 Pod。
2.3 为什么反亲和性这么重要
因为它直接影响高可用性:
- 节点宕机时,不会一下损失全部副本;
- 容量更均匀;
- 故障域更分散。
所以很多线上系统里,反亲和性几乎是高可用部署的基础配置。
3. Taint 与 Toleration 的工作机制
如果说 Affinity 更像“吸引”,那 Taint 就更像“排斥”。
官方文档描述得很清楚:污点加在节点上,用来拒绝某些 Pod;容忍加在 Pod 上,用来声明自己可以接受哪些污点。 如果节点带有 NoExecute 污点且 Pod 不容忍,kubelet 还可能把已经在该节点上的 Pod 驱逐出去。::cite[228]
3.1 Taint 的核心效果
常见 effect:
NoSchedule:不允许新的、不容忍该污点的 Pod 调度上来;PreferNoSchedule:尽量别调度,但不是绝对禁止;NoExecute:不仅影响新调度,还可能驱逐已运行但不容忍的 Pod。::cite[228]
3.2 给节点打污点
kubectl taint nodes worker-2 dedicated=ml:NoSchedule
这条命令的意思是:
worker-2是一个专用节点;- 不带对应 toleration 的 Pod,不允许调度到这台机器。
3.3 带容忍的 Pod
apiVersion: v1
kind: Pod
metadata:
name: toleration-demo
spec:
containers:
- name: app
image: nginx:1.27
tolerations:
- key: "dedicated"
operator: "Equal"
value: "ml"
effect: "NoSchedule"
这并不代表 Pod 一定会去 worker-2,它只表示:
- 这个 Pod 有资格上带该污点的节点;
- 最终是否真的调度过去,还要看资源、亲和性、其他约束。
这一点很重要:
- Taint/Toleration 解决“允不允许”;
- Affinity 解决“想不想去”。
4. 典型场景:专用节点、隔离节点与高可用部署
4.1 场景一:专用节点
比如 GPU 节点、AI 训练节点、大内存节点,通常不希望普通业务抢占。
常见做法:
- 给节点打标签:
node-type=gpu - 给节点加污点:
dedicated=gpu:NoSchedule - 给目标 Pod 同时配置:
nodeAffinity指向node-type=gputolerations容忍dedicated=gpu
完整 YAML:
apiVersion: v1
kind: Pod
metadata:
name: gpu-job-demo
spec:
containers:
- name: trainer
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "2"
memory: "4Gi"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-type
operator: In
values:
- gpu
tolerations:
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"
这套组合很经典:
- 亲和性负责“只去 GPU 节点”;
- 容忍负责“允许进入被隔离的专用节点池”。
4.2 场景二:隔离节点
有些节点可能只允许:
- 安全组件
- 存储组件
- 监控组件
- 系统守护进程
这时常见策略就是给这些节点统一打 NoSchedule 污点,不让普通业务误入。
4.3 场景三:高可用部署
高可用最常见的是:
- 用
podAntiAffinity让同类副本分散到不同节点; - 再结合拓扑键,把副本分散到不同可用区;
- 必要时配合
topologySpreadConstraints做更稳定的分布。
即便不写复杂规则,官方文档也提到默认调度器通常会做合理放置;但在关键业务里,显式写出反亲和性会更可控。::cite[229]
5. 实操:一步步验证调度行为
5.1 查看节点标签
kubectl get nodes --show-labels
5.2 查看节点污点
kubectl describe node worker-2
在输出中关注 Taints 字段。
5.3 应用反亲和性 Deployment
kubectl apply -f web-ha-demo.yaml
5.4 查看副本分布
kubectl get pod -l app=web-ha -o wide
如果节点数足够,你应该能看到多个副本分布在不同节点上。
6. 学习这一章最容易踩的坑
6.1 把 toleration 当成“强制调度”
不是。
tolerations 只是让 Pod 可以接受某个污点,不代表一定会调度过去。
6.2 亲和性条件写得过死
如果你把 requiredDuringSchedulingIgnoredDuringExecution 条件写得过于严格,而集群节点又不足,Pod 很容易一直 Pending。
6.3 反亲和性和副本数不匹配
如果你只有 2 台节点,却要求 3 个副本“每个都不能和同类在同一节点”,那第三个副本大概率调度不上。
6.4 只做隔离,不做标签引导
给节点加污点,只能实现“挡住别人”;如果你想让目标 Pod 更稳定地进入这批节点,通常还要配合标签与亲和性来做“引导”。
7. 本章小结
这一章你可以记住下面这组对照:
nodeSelector:最简单的节点选择;nodeAffinity:更灵活的节点选择;podAffinity:希望和谁靠近;podAntiAffinity:希望和谁分开;taint:节点主动拒绝;toleration:Pod 表示我能接受。
如果把它们组合起来,你就能实现非常实用的调度策略:
- 专用节点:标签 + 亲和性 + 污点容忍
- 隔离节点:污点优先
- 高可用部署:反亲和性优先
理解完这一章,你基本就具备了“按业务意图调度 Pod”的能力。
📝 版权声明:本文为原创技术博客,转载请注明出处。
如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!