返回首页

3.3 亲和性调度与污点容忍

学到这里,会发现默认调度虽然已经很智能,但很多业务并不满足于“能跑就行”。

现实场景里,我们经常会提出更细的要求:

  • 这个 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 训练节点、大内存节点,通常不希望普通业务抢占。

常见做法:

  1. 给节点打标签:node-type=gpu
  2. 给节点加污点:dedicated=gpu:NoSchedule
  3. 给目标 Pod 同时配置:
    • nodeAffinity 指向 node-type=gpu
    • tolerations 容忍 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”的能力。


📝 版权声明:本文为原创技术博客,转载请注明出处。

如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!

上一篇

1.2 Kubernetes 架构概览:控制平面与数据平面

下一篇

3.4 PriorityClass 与 ResourceQuota