Kubernetes高级调度02:Taint/Toleration、Cordon/Drain、亲和性与反亲和性完全指南

本文是Kubernetes调度系列的进阶篇,详细梳理污点与容忍、节点维护操作、亲和性/反亲和性三大核心调度机制,附带全部命令示例和补充知识点,适合作为博客学习笔记。


1. Taint(污点)和 Toleration(容忍)

1.1 基本概念

  • Taint(污点) 作用于节点,用于排斥一类Pod。

  • Toleration(容忍) 作用于Pod,表示Pod可以"容忍"某个污点,从而允许被调度到带有该污点的节点上。

类比:污点就像节点上的"刺",只有携带"防护手套"(容忍)的Pod才能靠近。

典型应用场景:

  • Master节点只运行系统组件(如kube-apiserver、etcd、Calico等),业务Pod不得调度。

  • 新加入的节点需要经过测试后才能承接业务 → 先打污点,测试完成后再移除。

  • GPU专用节点只允许需要GPU的Pod部署。

  • 节点维护时,驱逐现有Pod并阻止新Pod调度。

1.2 污点的操作命令

1.2.1 添加污点

bash

复制代码
# 语法:kubectl taint nodes <节点名> <key>=<value>:<effect>
kubectl taint nodes k8s-node01 key1=value1:NoSchedule
1.2.2 查看节点的污点

bash

复制代码
kubectl describe node k8s-node01 | grep Taints
# 或
kubectl get nodes -o json | jq '.items[].spec.taints'
1.2.3 移除污点

bash

复制代码
# 在命令末尾加一个减号 -
kubectl taint nodes k8s-node01 key1=value1:NoSchedule-

1.3 三种effect详解

effect 含义 对已存在Pod 对未调度Pod
NoSchedule 不会将新Pod调度到此节点 无影响 除非Pod容忍,否则不调度
PreferNoSchedule 尽量不调度到此节点(软限制) 无影响 尽量避开,但不强制
NoExecute 立即驱逐不匹配的Pod + 阻止新调度 不容忍的Pod被驱逐;容忍但未设置tolerationSeconds的Pod保留;容忍且设置了时间的,到达时间后驱逐 不容忍的不调度

补充知识点

  • NoExecute 常用于节点维护场景:先打污点驱逐Pod,待维护完成后再移除污点。

  • tolerationSeconds 可给Pod一个"优雅退出"的时间窗口(例如60秒),默认值为一天(86400秒)。

1.4 容忍的定义与匹配规则

在Pod的spec中通过tolerations字段定义容忍:

yaml

复制代码
tolerations:
- key: "check"
  operator: "Equal"
  value: "mycheck"
  effect: "NoExecute"
  tolerationSeconds: 60

匹配规则

  • 如果operatorExists,则无需指定value(只要key和effect匹配即可)。

  • 如果operatorEqual(默认),则keyvalueeffect必须完全匹配。

特殊容忍

  • 容忍所有污点:operator: "Exists"(空key、空effect)

  • 容忍某个key的所有effect:指定operator: "Exists"且不写effect

1.5 完整示例:NoExecute + tolerationSeconds

步骤1:给两个节点打污点

bash

复制代码
kubectl taint node k8s-node01 check=mycheck:NoExecute
kubectl taint node k8s-node02 check=mycheck:NoExecute
步骤2:创建一个不容忍的Pod(会被驱逐)

yaml

复制代码
# pod1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp01
  labels:
    app: myapp01
spec:
  containers:
  - name: myapp
    image: nginx

bash

复制代码
kubectl apply -f pod1.yaml
# 该Pod会处于Pending状态,因为两个节点都有NoExecute污点且Pod没有容忍
步骤3:创建带容忍和tolerationSeconds的Pod

yaml

复制代码
# pod2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp02
spec:
  tolerations:
  - key: "check"
    operator: "Equal"
    value: "mycheck"
    effect: "NoExecute"
    tolerationSeconds: 60    # 60秒后被驱逐
  containers:
  - name: myapp
    image: nginx

bash

复制代码
kubectl apply -f pod2.yaml
kubectl get pods -o wide   # 可以看到Pod被调度到某个节点
步骤4:实验完成后移除污点

bash

复制代码
kubectl taint node k8s-node01 check=mycheck:NoExecute-
kubectl taint node k8s-node02 check=mycheck:NoExecute-

1.6 多污点与多容忍的处理逻辑

一个节点可以设置多个污点,一个Pod也可以定义多个容忍。调度器的处理逻辑:

  1. 过滤掉所有不容忍的污点(即污点的key/effect与Pod的容忍不匹配)。

  2. 剩下的污点中:

    • 如果存在任意一个 effect=NoSchedule 的污点 → 不调度。

    • 如果只有 PreferNoSchedule → 尽量不调度(软限制)。

    • 如果有 NoExecute 且Pod已经在该节点上运行 → 驱逐;如果未调度 → 不调度。

补充 :Kubernetes 1.18+ 引入了 TaintBasedEvictions 特性门控,默认开启。NoExecute的驱逐是异步 的,容忍检查周期由 --node-status-update-frequency--taint-manager-recheck-period 控制。

1.7 特殊容忍场景

1.7.1 Master节点允许临时调度

bash

复制代码
# 给Master打PreferNoSchedule污点(尽量不调度,但资源紧张时可调度)
kubectl taint node k8s-master node-role.kubernetes.io/master=:PreferNoSchedule
1.7.2 节点维护前驱逐所有Pod

bash

复制代码
kubectl taint node k8s-node01 maintenance=upgrade:NoExecute
# 待维护完成后移除
kubectl taint node k8s-node01 maintenance=upgrade:NoExecute-
1.7.3 容忍所有污点(慎用)

yaml

复制代码
tolerations:
- operator: "Exists"

2. Cordon(警戒)和 Drain(转移)

这两个命令用于节点维护场景,安全地将节点上的Pod迁移走。

2.1 警戒(cordon)

cordon 将节点标记为不可调度SchedulingDisabled),但不会驱逐已有Pod。

bash

复制代码
kubectl cordon k8s-node01

检查节点状态:

bash

复制代码
kubectl get nodes
# NAME          STATUS                     ROLES    AGE   VERSION
# k8s-node01    Ready,SchedulingDisabled   <none>   10d   v1.24.0

2.2 转移(drain)

drain 做了两件事:

  1. 将节点设置为不可调度(相当于自动执行了cordon)。

  2. 驱逐节点上所有Pod(DaemonSet管理的Pod默认不会被驱逐,需要加--ignore-daemonsets)。

bash

复制代码
kubectl drain k8s-node01 \
  --ignore-daemonsets \
  --delete-local-data \
  --force

参数详解

参数 作用
--ignore-daemonsets 忽略DaemonSet管理的Pod(它们会重建在当前节点,驱逐无意义)
--delete-local-data 强制删除使用emptyDir或hostPath的Pod(否则会因local data而阻塞)
--force 强制删除不是由控制器(ReplicaSet、StatefulSet等)管理的Pod,例如裸Pod

补充drain会检查节点上是否有非DaemonSet的不可容忍NoExecute的Pod,如果有且未加--force则会拒绝执行。

2.3 恢复调度(uncordon)

维护完成后,将节点重新设为可调度:

bash

复制代码
kubectl uncordon k8s-node01

生产实践建议

  • 维护流程:cordon → 确认无新Pod调度 → drain → 维护 → uncordon

  • 如需维护多个节点,应逐个进行,确保集群容量足够承载迁移的Pod。


3. 亲和性与非亲和性

亲和性提供比污点更灵活的调度选择,可以指定"希望"或"必须"满足的规则。

3.1 硬策略 vs 软策略

类型 关键词 含义
硬策略(Required) requiredDuringSchedulingIgnoredDuringExecution 必须满足,否则Pod不调度
软策略(Preferred) preferredDuringSchedulingIgnoredDuringExecution 尽量满足,不满足也可以调度到其他节点

IgnoredDuringExecution 表示一旦Pod已经运行,即便节点标签发生变化,也不会驱逐Pod。

3.2 节点亲和性(NodeAffinity)

3.2.1 节点硬亲和示例

先给节点打标签:

bash

复制代码
kubectl label nodes k8s-node01 type=node01
kubectl label nodes k8s-node02 type=node02

# 覆盖标签
kubectl label nodes k8s-node01 type=node01 --overwrite

# 删除标签
kubectl label nodes k8s-node01 type-

查看标签:

bash

复制代码
kubectl get nodes --show-labels

创建Pod调度到type=node01的节点:

yaml

复制代码
# test01.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod01
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: type
            operator: In
            values: ["node01"]
  containers:
  - name: pod01
    image: nginx:1.7.9

bash

复制代码
kubectl apply -f test01.yaml
kubectl get pod pod01 -o wide
# NODE 显示为 k8s-node01
3.2.2 节点软亲和示例

软亲和可以设置weight(1-100),权重越高越优先。

yaml

复制代码
# test03.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod03
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 80
        preference:
          matchExpressions:
          - key: type
            operator: In
            values: ["node01"]
      - weight: 20
        preference:
          matchExpressions:
          - key: type
            operator: In
            values: ["node02"]
  containers:
  - name: pod03
    image: nginx

注意 :节点软亲和中的权重顺序不直接影响调度结果,调度器会根据所有软规则综合打分。但Pod软亲和(后面会讲)的权重会明显影响结果。

3.3 Pod亲和性(PodAffinity)

Pod亲和性用于将Pod聚集 在一起(例如同一个机架、同一个节点),这需要通过topologyKey指定拓扑域。

topologyKey 是节点标签的键,例如:

  • kubernetes.io/hostname → 同一节点

  • topology.kubernetes.io/zone → 同一可用区

  • kubernetes.io/arch → 同一架构

3.3.1 Pod硬亲和示例

首先创建两个基础Pod:pod04(在node01)、pod05(在node02)

yaml

复制代码
# test04.yaml - pod04
apiVersion: v1
kind: Pod
metadata:
  name: pod04
  labels:
    app: pod04
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - {key: type, operator: In, values: ["node01"]}
  containers:
  - name: pod04
    image: nginx

yaml

复制代码
# test05.yaml - pod05
apiVersion: v1
kind: Pod
metadata:
  name: pod05
  labels:
    app: pod05
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - {key: type, operator: In, values: ["node02"]}
  containers:
  - name: pod05
    image: nginx

部署后,pod04在node01,pod05在node02。

现在创建一个Pod亲和到pod05(即希望与pod05在同一个拓扑域):

yaml

复制代码
# test06.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod06
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - {key: app, operator: In, values: ["pod05"]}
        topologyKey: kubernetes.io/hostname
  containers:
  - name: pod06
    image: nginx

bash

复制代码
kubectl apply -f test06.yaml
kubectl get pods -o wide
# pod06 会运行在 node02(与 pod05 同节点)
3.3.2 Pod软亲和示例

Pod软亲和可以设置多个亲和项,每个带有weight

yaml

复制代码
# test07.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod07
spec:
  affinity:
    podAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 80
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - {key: app, operator: In, values: ["pod05"]}
          topologyKey: kubernetes.io/hostname
      - weight: 20
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - {key: app, operator: In, values: ["pod04"]}
          topologyKey: kubernetes.io/hostname
  containers:
  - name: pod07
    image: nginx

因为权重80偏向pod05(位于node02),所以pod07大概率调度到node02。

如果将权重改为20/80(偏向pod04),则Pod会去node01。

yaml

复制代码
# test08.yaml 中交换权重
- weight: 20   # 偏向pod05
- weight: 80   # 偏向pod04

3.4 Pod反亲和性(PodAntiAffinity)

反亲和性让Pod远离某些Pod,避免集中(例如避免两个数据库Pod在同一节点)。

yaml

复制代码
# test09.yaml - 硬反亲和,不与 pod05 在同一节点
apiVersion: v1
kind: Pod
metadata:
  name: pod09
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - {key: app, operator: In, values: ["pod05"]}
        topologyKey: kubernetes.io/hostname
  containers:
  - name: pod09
    image: nginx

由于pod05在node02,pod09会被调度到node01(或其他非node02的节点)。


4. Taint与亲和性的优先级

重要原则:污点的约束优先于亲和性的指导。

  • 如果一个节点上有污点,而Pod没有对应的容忍,那么无论亲和性规则如何,Pod 都不会被调度到该节点。

  • 即使Pod的亲和性明确"要求"调度到某个节点,只要该节点有不容忍的污点,调度依然失败。

因此,调度器的决策顺序为:

  1. Taint/Toleration 过滤(硬性排除)

  2. 节点亲和性/反亲和性 过滤

  3. Pod亲和性/反亲和性 过滤

  4. 打分排序(包括软亲和性的权重)


5. 补充知识点汇总

5.1 节点上的默认污点

Kubernetes 1.6+ 引入了节点条件污点,当节点出现某种状况(如网络不可用、磁盘压力)时,会自动添加对应污点:

内置污点 含义
node.kubernetes.io/not-ready 节点未就绪
node.kubernetes.io/unreachable 节点不可达
node.kubernetes.io/out-of-disk 磁盘空间不足
node.kubernetes.io/memory-pressure 内存压力
node.kubernetes.io/disk-pressure 磁盘压力
node.kubernetes.io/network-unavailable 网络不可用

这些污点默认都有NoExecuteNoSchedule效果,会触发Pod驱逐。

5.2 亲和性中的操作符(operator)

支持以下操作符:

  • In:标签值在列表中

  • NotIn:标签值不在列表中

  • Exists:存在该标签键

  • DoesNotExist:不存在该标签键

  • Gt(大于) / Lt(小于):仅用于数值比较

示例:

yaml

复制代码
matchExpressions:
- key: disk-size
  operator: Gt
  values: ["100"]   # 表示大于100

5.3 topologyKey 注意事项

  • Pod亲和性中必须 指定topologyKey

  • 不同节点上的topologyKey标签值相同表示同一个拓扑域。

  • 使用podAntiAffinity时,如果topologyKey没有覆盖到所有节点,可能会导致Pod被分散到某些域,但其他域仍有累积。

5.4 性能建议

  • 大量的亲和性/反亲和性规则会增加调度器的计算开销,建议仅在必要时使用。

  • 对于大规模集群(>1000节点),谨慎使用podAffinity,因为调度器需要扫描所有节点上的Pod标签。

5.5 与nodeSelector的对比

特性 nodeSelector nodeAffinity
表达能力 简单等式匹配 复杂表达式(In, NotIn, Exists, Gt, Lt)
软策略 不支持 支持 preferredDuringScheduling
联合条件 不支持 支持 matchExpressions 多条件

5.6 drain 的优雅驱逐

kubectl drain 实际上调用了Pod的/eviction API,遵循Pod的terminationGracePeriodSeconds(默认30秒)。可以使用--grace-period覆盖。


6. 本章总结

本文全面梳理了Kubernetes高级调度的三大支柱:

机制 作用对象 核心目的
Taint & Toleration 节点 / Pod 排斥或容忍特定Pod,保护专用节点
Cordon & Drain 节点 安全维护节点,迁移Pod
NodeAffinity Pod → 节点 控制Pod部署到满足标签条件的节点
PodAffinity / AntiAffinity Pod → Pod 让Pod聚集或分散部署

掌握这些机制后,你可以:

  • 隔离Master节点与业务Pod

  • 实现GPU节点专用调度

  • 优雅地完成节点升级维护

  • 让高耦合的Pod就近部署(低延迟)

  • 避免单点故障(反亲和性)

生产环境中,通常组合使用多种调度策略。例如:节点打上污点防止意外调度 + 为特定Pod设置容忍 + 配合亲和性指定拓扑域。

最后提醒:所有示例命令中的节点名、标签值请根据实际集群修改。实验结束后记得清理污点和标签,以免影响后续测试。

相关推荐
专业白嫖怪5 小时前
什么是docker
运维·docker·容器
Plastic garden10 小时前
Docker(1)
运维·docker·容器
gs8014011 小时前
网络隐形杀手:从 Could not connect to SMTP host 报错深度剖析 Docker MTU 黑洞理论与实战
网络·docker·容器
程序猿阿伟12 小时前
《一套完整方法论:搞定图形应用的Docker镜像优化》
数据库·docker·容器
java_logo12 小时前
2026 Docker 国内镜像加速配置教程
运维·docker·容器·docker镜像·docker镜像源·docker镜像加速·docker镜像国内库
IT策士13 小时前
Docker从0到1再到 Kubernetes 实战:第15篇Compose 中的服务依赖、健康检查与启动顺序
docker·容器·kubernetes
Waay13 小时前
K8s Deployment 滚动更新与回滚深度详解(含踩坑实录+生产选型原理)
云原生·容器·kubernetes
顾默@14 小时前
双系统Ubuntu18.04升级22.04,安装docker进行openclaw安装
运维·docker·容器
蜀道山老天师15 小时前
Docker Compose 多容器编排实战:LNMP、Tomcat 集群、云桌面、Portainer、Zabbix 一键部署
运维·docker·容器·tomcat·zabbix
见牛羊16 小时前
docker理解
java·docker·容器