实验环境:Kubernetes v1.35.3,1 Master + 2 Node,Flannel 网络
一、nodeName:手动指定调度节点
默认情况下 Pod 由调度器自动分配节点,而 nodeName 可以直接指定 Pod 运行在哪个节点上。
yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx
name: nginx
spec:
containers:
- image: nginx
name: nginx
不指定 nodeName 时,调度器自动选择节点:
bash
kubectl apply -f nginx.yml
kubectl get pods -o wide
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 56s 10.244.166.131 node1 <none> <none>
指定 nodeName: node2 后,Pod 固定调度到 node2:
yaml
spec:
nodeName: node2 # 指定调度节点
containers:
- image: nginx
name: nginx
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 7s 10.244.104.2 node2 <none> <none>
nodeName 是最强制的调度方式,绕过了调度器,直接由 kubelet 在目标节点创建 Pod。适用于特殊情况,一般不推荐用于日常调度。
二、nodeSelector:按节点标签选择
先给节点打标签,再用 nodeSelector 匹配标签调度。
yaml
spec:
nodeSelector:
app: timinglee
containers:
- image: nginx
name: nginx
没有节点带 app=timinglee 标签时,Pod 一直 Pending:
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 0/1 Pending 0 8s <none> <none> <none> <none>
给 node2 打上标签后,Pod 立即调度成功:
bash
kubectl label nodes node2 app=timinglee
kubectl get nodes --show-labels
# node2 的 LABELS 出现 app=timinglee
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 96s 10.244.104.3 node2 <none> <none>
删除标签:kubectl label nodes node2 app-,已运行的 Pod 不受影响(nodeSelector 只在调度时生效)。
三、节点亲和(nodeAffinity)
nodeSelector 只能做简单的等于匹配,nodeAffinity 支持更丰富的表达能力,有两种策略:
1. preferredDuringSchedulingIgnoredDuringExecution:倾向满足(软策略)
优先调度到满足条件的节点,没有满足条件的节点也会调度(只是权重偏好)。
yaml
spec:
containers:
- image: nginx
name: nginx
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: disk
operator: In
values:
- ssd
- iscsi
weight: 50
没有节点带 disk=ssd/iscsi 标签时,Pod 依然可以调度(落在 node1):
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 2m3s 10.244.166.132 node1 <none> <none>
给 node2 打上 disk=ssd 标签后,Pod 倾向调度到 node2:
bash
kubectl label nodes node2 disk=ssd
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 5s 10.244.104.4 node2 <none> <none>
2. requiredDuringSchedulingIgnoredDuringExecution:必须满足(硬策略)
条件不满足时 Pod 不会被调度(一直 Pending)。
yaml
spec:
containers:
- image: nginx
name: nginx
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disk
operator: In
values:
- ssd
- iscsi
条件满足(node2 有 disk=ssd),调度成功:
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 50s 10.244.104.5 node2 <none> <none>
配合 NotIn 反向选择 ------排除有 disk=ssd/iscsi 标签的节点,Pod 被调度到 node1:
yaml
- key: disk
operator: NotIn # 反向选择
values:
- ssd
- iscsi
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 8s 10.244.166.133 node1 <none> <none>
四、Pod 亲和(podAffinity)
Pod 亲和让新的 Pod 尽量/必须与满足条件的 Pod 调度到同一个拓扑域 (这里用 topologyKey: kubernetes.io/hostname,即同一节点)。
bash
kubectl create deployment webcluster --image nginx --replicas 2 --dry-run=client -o yaml > webcluster.yml
yaml
spec:
containers:
- image: nginx
name: nginx
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- webcluster
topologyKey: "kubernetes.io/hostname"
两个副本都调度到了同一节点 node1:
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
webcluster-65b86bcdbb-jhb2b 1/1 Running 0 35s 10.244.166.136 node1 <none> <none>
webcluster-65b86bcdbb-z4mt8 1/1 Running 0 35s 10.244.166.135 node1 <none> <none>
五、Pod 反亲和(podAntiAffinity)
Pod 反亲和与亲和相反:新的 Pod 必须与满足条件的 Pod 分布在不同的拓扑域(不同节点)。
yaml
spec:
replicas: 3
...
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- webcluster
topologyKey: "kubernetes.io/hostname"
3 个副本分布在 node1、node2 各一个,第三个因为反亲和条件找不到可用节点而 Pending:
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
webcluster-85f7868466-srhgf 1/1 Running 0 74s 10.244.104.7 node2 <none> <none>
webcluster-85f7868466-t8gk6 1/1 Running 0 74s 10.244.166.137 node1 <none> <none>
webcluster-85f7868466-x24wj 0/1 Pending 0 22s <none> <none> <none> <none>
Pending的 Pod 注释会提示 0/2 nodes are available: 2 node(s) didn't match pod anti-affinity rules。
六、节点的污点设定(Taint)
污点是打在节点上的排斥标记,默认 Pod 不会调度到带污点的节点。三种效果:
| 效果 | 行为 |
|---|---|
| NoSchedule | 新的 Pod 不调度到该节点(已运行的不受影响) |
| NoExecute | 新的不调度 + 驱逐 已运行的 Pod |
| PreferNoSchedule | 尽量不调度(软策略),没有其他节点时也会调度 |
NoExecute:立即驱逐
bash
kubectl taint node node1 nodetype=badnode:NoExecute
原本在 node1 上的 Pod 被立即驱逐,重新调度到 node2:
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
webcluster-77f8775f47-4bgj6 1/1 Running 0 8m52s 10.244.104.8 node2 <none> <none>
webcluster-77f8775f47-zrw2r 1/1 Running 0 5m6s 10.244.104.9 node2 <none> <none>
NoSchedule:拒绝新调度
bash
kubectl taint node node2 nodetype=badnode:NoSchedule
删除并重建 Deployment,两个 Pod 全部调度到没有污点的 node1:
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
webcluster-77f8775f47-6mkkr 1/1 Running 0 16s 10.244.166.140 node1 <none> <none>
webcluster-77f8775f47-c84z9 1/1 Running 0 16s 10.244.166.139 node1 <none> <none>
PreferNoSchedule:尽量避免
bash
kubectl taint node node2 nodetype=badnode:PreferNoSchedule
软策略下 Pod 仍倾向调度到 node1,但配合反亲和时可能容忍。
七、污点容忍(Toleration)
容忍是打在 Pod 上的标记,用来"忍受"节点的污点。先给两个节点设置污点:
bash
kubectl taint node node1 name=lee:NoSchedule
kubectl taint node node2 nodetype=badnode:NoSchedule
不带容忍的 Pod 全部 Pending:
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
webcluster-77f8775f47-fln2k 0/1 Pending 0 33s <none> <none> <none> <none>
webcluster-77f8775f47-k9bv8 0/1 Pending 0 33s <none> <none> <none> <none>
1. 精确容忍指定污点
operator: Equal 精确匹配 key=value 和 effect:
yaml
spec:
containers:
- image: nginx
name: nginx
tolerations: # 污点容忍
- operator: Equal
key: nodetype
value: badnode
effect: NoSchedule
Pod 精准调度到带 nodetype=badnode:NoSchedule 污点的 node2:
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
webcluster-597586678c-9qvp8 1/1 Running 0 12s 10.244.104.14 node2 <none> <none>
webcluster-597586678c-d2wvv 1/1 Running 0 12s 10.244.104.13 node2 <none> <none>
2. 容忍所有 key 的 NoSchedule 污点
operator: Exists + 指定 effect,容忍所有 NoSchedule 类型的污点(不管 key 是什么):
yaml
tolerations:
- operator: Exists
effect: NoSchedule
3 个副本分布在 node1、node2、master:
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
webcluster-549c6784f6-58zh9 1/1 Running 0 8s 10.244.104.15 node2 <none> <none>
webcluster-549c6784f6-d88tz 1/1 Running 0 8s 10.244.219.67 master <none> <none>
webcluster-549c6784f6-zv2pm 1/1 Running 0 8s 10.244.166.144 node1 <none> <none>
3. 容忍所有污点
只写 operator: Exists,不限定 effect,容忍一切污点(慎用,通常只在系统级 DaemonSet 使用):
yaml
tolerations:
- operator: Exists
3 个副本分布到全部节点:
text
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
webcluster-5df57d554b-6gdcc 1/1 Running 0 12s 10.244.219.68 master <none> <none>
webcluster-5df57d554b-cj8fd 1/1 Running 0 12s 10.244.166.145 node1 <none> <none>
webcluster-5df57d554b-mwcfx 1/1 Running 0 12s 10.244.104.16 node2 <none> <none>

图 1:污点与容忍实验------容忍后 Pod 分布到全部节点
总结
| 调度方式 | 特点 | 适用场景 |
|---|---|---|
| nodeName | 强制指定节点,绕过调度器 | 特殊单节点需求 |
| nodeSelector | 按标签简单匹配 | 标签调度入门 |
| nodeAffinity | 软/硬策略 + In/NotIn | 复杂节点选择 |
| podAffinity | 与指定 Pod 同拓扑域 | 就近调度、应用内聚 |
| podAntiAffinity | 与指定 Pod 异拓扑域 | 高可用分散部署 |
| Taint | 节点排斥 Pod | 节点隔离、专用节点 |
| Toleration | Pod 容忍污点 | 允许特定 Pod 调度到污点节点 |
调度是 Kubernetes 把 Pod 放到合适节点的核心机制。nodeSelector 管 "要什么节点",Taint/Toleration 管 "不要哪些 Pod 进来",亲和与反亲和则负责更精细的拓扑分布------理解这几者的组合,是设计高可用、高性能工作负载的基础。