Skip to content

Kubernetes 节点独立域调度实战:Label + Taint + Toleration 四件套

在大规模 Kubernetes 集群中,所有节点默认对全部业务开放调度,带来的典型问题是:高资源消耗的应用挤占普通业务节点、批处理任务与在线服务争抢 CPU、某类业务高峰期把节点打满后波及其他业务。节点独立域调度(专用节点池)就是解决这类互相挤占问题的标准做法:按业务类型或资源规格把节点划分为若干独立的调度域,每个域通过「标签 + 污点」封闭起来,只有显式声明了「节点选择器 + 容忍」的工作负载才能进入,从而实现节点资源在业务之间的硬隔离。

本文基于生产环境的实际运维操作整理,覆盖独立域设计模式、六类典型域的落地清单、批量操作脚本、新节点入域标准流程、异常节点调度排除案例,以及常见故障的速查方法。

一、独立域调度的设计原理

独立域调度依赖 Kubernetes 调度器的四个机制配合使用,缺一不可:

机制配置位置作用
Label(节点标签)节点给节点打上归属标记,供 Pod 定向选择
Taint(节点污点)节点拒绝未容忍该污点的 Pod 调度上来,实现"排他"
nodeSelector(节点选择器)Pod/Deployment指定 Pod 只能调度到携带特定标签的节点
Toleration(污点容忍)Pod/Deployment声明 Pod 可以容忍节点上的特定污点,获得"准入"资格

四件套的配合逻辑:

  • 只打 Label 不打 Taint:Pod 能被引导过来,但其他业务也能随便调度上来,域不封闭。
  • 只打 Taint 不打 Label:普通业务被挡住了,但目标业务也无法精确定位这批节点,只能"飘"到任何容忍的节点上。
  • Label + Taint 封闭节点侧,nodeSelector + Toleration 打开应用侧,两边同时配置才构成一个完整的独立域。

污点的 effect 常用两种:

  • NoSchedule:硬约束,未容忍的 Pod 不会被调度到该节点,已在运行的 Pod 不受影响。独立域隔离统一使用该效果。
  • NoExecute:不仅拒绝新调度,还会驱逐节点上未容忍的存量 Pod。用于节点下线、故障驱逐等场景。
  • PreferNoSchedule:软约束,调度器尽量避免但不强制。适合上线前灰度验证,例如先给节点打软污点观察调度行为:
bash
node=192.168.10.103
kubectl taint node $node node.kubernetes.io/custom-memory-pressure=true:PreferNoSchedule

二、独立域规划清单

生产集群中按业务类型和资源规格划分的六类典型独立域如下,实际落地时可按自身业务命名:

域名称(标签值)标签键污点节点特征承载业务类型
bigmemnode-typenode-type=bigmem:NoSchedule大内存规格节点内存密集型的核心应用
jobnode-typenode-type=job:NoSchedule通用计算节点批处理、定时任务、数据计算类工作负载
exceptionnode-typenode-type=exception:NoSchedule独立物理机日志/异常处理等需要独占资源的消费类应用
datanode-typenode-type=data:NoSchedule独享节点池数据平台类客户应用
app-anode-typenode-type=app-a:NoSchedule独享节点池业务线 A 的核心应用
app-bnode-typenode-type=app-b:NoSchedule独享节点池业务线 B 的订单类应用

除按业务划分外,也可以按硬件规格划分资源型域,标签键使用 hardware-type,例如 high-mem-medium-cpu(高内存中 CPU)、high-cpu-medium-mem(高 CPU 中内存),使用方式与业务域完全一致。

规划原则:

  • 标签键全集群统一用 node-type(业务域)或 hardware-type(资源型域),避免每个域发明一个键,后期无法批量管理。
  • 污点键与标签键保持一致,污点值与标签值保持一致,排查时一眼能看出对应关系。
  • 每个域至少保留 2 个节点的冗余,单节点域一旦故障,域内所有业务会整体 Pending。

三、节点侧批量操作

3.1 打标签与打污点

节点入域时标签和污点必须同时配置。单个节点:

bash
nodeName=192.168.10.41
# 标记标签
kubectl label nodes $nodeName node-type=exception
# 标记污点
kubectl taint nodes $nodeName node-type=exception:NoSchedule

批量操作脚本:

bash
nodes="
192.168.10.13
192.168.10.112
192.168.10.113
192.168.10.17
"
for node in $nodes; do
  kubectl label node $node node-type=bigmem
  kubectl taint node $node node-type=bigmem:NoSchedule
done

3.2 摘除标签与污点

标签和污点的摘除语法都是在末尾加 -

bash
nodeName=192.168.10.41
# 移除标签
kubectl label nodes $nodeName node-type-
# 移除污点(注意末尾的 -)
kubectl taint nodes $nodeName node-type=exception:NoSchedule-

批量摘除某个域的污点(例如域调整、节点退回公共池):

bash
nodes="
192.168.10.14
192.168.10.15
192.168.10.16
192.168.10.17
"
for node in $nodes; do
  kubectl taint nodes $node node-type=bigmem:NoSchedule-
done

注意:摘除污点只是放开了调度准入,已经调度到其他节点的 Pod 不会自动回来;如果目的是让节点重新承载业务,摘除污点即可,新扩容或重建的 Pod 会自然调度上来。

3.3 查看域分布

查看全部节点的污点:

bash
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

按域筛选节点(标签 + 污点一起看):

bash
kubectl get nodes -o=custom-columns=NAME:.metadata.name,Taints:.spec.taints,Labels:.metadata.labels | grep bigmem

按标签直接列出域内节点:

bash
kubectl get nodes -l node-type=bigmem

四、应用侧接入与退出独立域

4.1 应用接入:添加 nodeSelector + tolerations

节点封闭之后,目标业务需要同时声明节点选择器和污点容忍才能调度进域。单个 Deployment:

bash
ns=default
deploy=flow-platform-deploy
kubectl patch deployments $deploy -n $ns --patch '{"spec":{"template":{"spec":{"nodeSelector":{"node-type":"bigmem"}, "tolerations": [{"key":"node-type", "operator":"Equal","value":"bigmem", "effect":"NoSchedule"}]}}}}'

批量迁移一组应用到指定域:

bash
deploys="
order-loader-deploy
delivery-adapter-deploy
delivery-dashboard-deploy
waybill-sender-deploy
"
ns=default
for deploy in $deploys; do
  kubectl patch deployments $deploy -n $ns --patch '{"spec":{"template":{"spec":{"nodeSelector":{"node-type":"exception"}, "tolerations": [{"key":"node-type", "operator":"Equal","value":"exception", "effect":"NoSchedule"}]}}}}'
done

patch 之后 Deployment 会触发滚动更新,新副本直接调度到域内节点。如果域内节点资源不足,新副本会 Pending,而旧副本已被销毁,因此批量迁移前务必先确认域内容量。

声明式写法(写入 Deployment YAML 的版本):

yaml
spec:
  template:
    spec:
      nodeSelector:
        node-type: bigmem
      tolerations:
        - key: node-type
          operator: Equal
          value: bigmem
          effect: NoSchedule

4.2 应用退出:清除 nodeSelector 和 tolerations

应用迁出独立域时,需要把两处配置同时清掉,只清一处会导致 Pod 仍然受旧约束或失去容忍:

bash
ns=default
deploy=flow-platform-deploy
kubectl patch deployment $deploy -n $ns --type='json' -p='[{"op": "remove", "path": "/spec/template/spec/nodeSelector"}]'
kubectl patch deployment $deploy -n $ns --type='json' -p='[{"op": "remove", "path": "/spec/template/spec/tolerations"}]'

也可以用置 null 的方式一次 patch 完成:

bash
deploys="app1-deploy app2-deploy"
for deploy in $deploys; do
  echo $deploy
  kubectl patch deployment $deploy -n default --patch '{
    "spec": {
      "template": {
        "spec": {
          "nodeSelector": null,
          "tolerations": null
        }
      }
    }
  }'
done

批量清理异常状态下 Pod 的调度约束(例如大量 Pod Pending 时快速放开调度):

bash
for pod in $(kubectl get pod -n default | grep Pend | awk '{print $1}'); do
  deploy="${pod%-*-*}"
  echo $deploy
  kubectl patch deployment $deploy -n default --type='json' -p='[{"op": "remove", "path": "/spec/template/spec/nodeSelector"}]'
done
for pod in $(kubectl get pod -n default | grep Pend | awk '{print $1}'); do
  deploy="${pod%-*-*}"
  kubectl patch deployment $deploy -n default --type='json' -p='[{"op": "remove", "path": "/spec/template/spec/tolerations"}]'
done

说明:${pod%-*-*} 是从 Pod 名反推 Deployment 名的简化写法,依赖命名规律(Deployment 名 + ReplicaSet 哈希 + Pod 哈希),命名不规范的业务需改用 ownerReferences 反查。

五、新节点加入独立域的标准流程

新节点加入集群后,默认对所有业务开放。在被普通业务"污染"之前完成入域,是最干净的操作路径。标准流程如下:

  1. 节点加入集群后立即封锁调度,防止普通业务抢先落上来:
bash
kubectl taint nodes $nodeName node.kubernetes.io/unschedulable=true:NoSchedule
  1. 打域标签和域污点:
bash
kubectl label nodes $nodeName node-type=bigmem
kubectl taint nodes $nodeName node-type=bigmem:NoSchedule
  1. 摘除临时封锁污点(此时节点已被域污点保护,只有容忍该域的 Pod 能进来):
bash
kubectl taint nodes $nodeName node.kubernetes.io/unschedulable=true:NoSchedule-
  1. 验证节点配置和调度准入:
bash
kubectl get node $nodeName -o=custom-columns=NAME:.metadata.name,Taints:.spec.taints,Labels:.metadata.labels
  1. 迁移或扩容目标业务进域(参考第四章的 patch 命令),观察 Pod 落点:
bash
kubectl get pods -n default -o wide | grep $nodeName

如果节点已经在运行其他业务的 Pod,需要先执行存量迁移:给节点加 NoExecute 污点驱逐(见第六章),或用 descheduler 做平滑再平衡(见第八章)。

六、节点驱逐与副本迁移

6.1 驱逐节点上所有副本

节点维护、下线或需要从公共池收回划给独立域时,用 NoExecute 污点驱逐全部存量 Pod:

bash
# 驱逐所有不容忍的存量 Pod
kubectl taint nodes $nodeName node.kubernetes.io/unschedulable=true:NoExecute

只禁止新调度、不动存量(等同 cordon 的效果):

bash
kubectl taint nodes $nodeName node.kubernetes.io/unschedulable=true:NoSchedule

节点彻底卸载(确认驱逐完成后):

bash
node=192.168.10.16
kubectl drain $node --ignore-daemonsets --delete-local-data
kubectl delete node $node

6.2 驱逐固定数量副本

批量释放节点压力但不需要清空节点时,可以按数量驱逐:

bash
nodename=192.168.10.36
count=100
kubectl get pods --field-selector spec.nodeName=$nodename -o name | head -n $count | xargs kubectl delete --force --grace-period=0

注意:--force --grace-period=0 是强制删除,Pod 不做优雅退出,有状态或长事务业务慎用;常规场景去掉这两个参数走正常删除流程。

七、实战案例:nodeAffinity NotIn 排除异常节点

场景:某核心业务服务(biz-core-service-deploy)在个别节点上反复出现运行异常,节点本身未达到 NotReady 驱逐条件,短期内也无法下线维修。此时不能给节点打 NoExecute 污点(会误伤节点上其他正常业务),需要让这一个服务单方面绕开异常节点。

方案:给该 Deployment 添加节点亲和性排除规则,用 NotIn 操作符把异常节点从可调度列表中剔除:

bash
kubectl patch deployment biz-core-service-deploy -p '{
  "spec": {
    "template": {
      "spec": {
        "affinity": {
          "nodeAffinity": {
            "requiredDuringSchedulingIgnoredDuringExecution": {
              "nodeSelectorTerms": [{
                "matchExpressions": [{
                  "key": "kubernetes.io/hostname",
                  "operator": "NotIn",
                  "values": ["worker-055", "worker-061", "worker-019"]
                }]
              }]
            }
          }
        }
      }
    }
  }
}'

要点说明:

  • 使用 requiredDuringSchedulingIgnoredDuringExecution:调度期强制排除,但已在异常节点上运行的存量 Pod 不会被驱逐,patch 后需要手动删除旧副本完成迁移。
  • kubernetes.io/hostname 是节点自带标签,values 填节点名(kubectl get nodes 第一列),不是 IP。
  • 这是临时止血手段。节点修复后应及时回滚该亲和性配置,避免运维债务:
bash
kubectl patch deployment biz-core-service-deploy --type='json' -p='[{"op": "remove", "path": "/spec/template/spec/affinity"}]'

如果需要批量清理集群中历史遗留的亲和性配置,可以:

bash
# 删除 default 命名空间所有 Deployment 的 affinity 配置
kubectl patch deployment -n default --all -p '{"spec":{"template":{"spec":{"affinity":null}}}}'

八、进阶:与 descheduler 配合的域内再平衡

独立域运行一段时间后,域内节点之间会出现负载倾斜:有的节点 Pod 密集,有的节点很空。由于污点只约束"调度时刻"的落点,存量 Pod 不会自动迁移,需要引入 descheduler 做再平衡。

常用策略:

  • RemoveDuplicates:剔除同一节点上同一 Deployment 的多个副本,配合反亲和让副本散开。
  • LowNodeUtilization:按节点真实利用率把高负载节点的 Pod 驱逐到空闲节点,是域内均衡的主力策略。
  • RemovePodsViolatingNodeAffinity / RemovePodsViolatingNodeTaints:清理配置变更后仍滞留在错误节点上的 Pod,例如应用退出独立域后,域内残留的旧副本。

配合要点:

  • descheduler 只做驱逐(delete Pod),重新调度仍走正常链路,因此独立域的污点约束天然生效,被驱逐的 Pod 只会回到域内节点,不会飘出域外。
  • 对核心业务建议开启 dry-run 先观察,并配置 PDB 限制同时被驱逐的副本数。
  • 调度成功率还受资源 requests 影响。域内节点碎片化严重时,可适当下调低优先级应用的 requests 提升装箱率:
bash
kubectl get deployment -n default -o name | xargs -I {} kubectl set resources {} -n default --requests=cpu=200m

九、故障速查表

故障现象可能原因排查与处理
Pod 一直 Pending,事件提示 0/N nodes are available: node(s) had untolerated taint应用未配置 toleration,或 toleration 的 key/value/effect 与节点污点对不上kubectl describe pod <pod> -n <ns> 看污点详情;比对节点污点(kubectl describe node)后补齐 toleration
Pod 一直 Pending,事件提示 didn't match Pod's node affinity/selectornodeSelector 指定的标签值写错,或域内节点未打标签kubectl get nodes -l node-type=<域名> 确认标签存在;检查 nodeSelector 拼写
Pod Pending,无污点/亲和性报错域内节点资源耗尽kubectl describe pod 看 Insufficient cpu/memory;扩容域内节点或下调 requests
加污点后整个业务大面积 Pending污点误加到了不该加的节点,或 effect 误用 NoExecute 驱逐了存量kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints 全局检查;误加的用 kubectl taint nodes <node> <key>=<value>:<effect>- 立即摘除
摘除污点后业务仍调度不上来应用侧还残留旧的 nodeSelector/toleration 指向已撤销的域按 4.2 节清除应用调度约束,触发滚动更新
patch nodeSelector/tolerations 报 The request is invalid: patch: Invalid valuepatch 体 JSON 结构错误,或路径不存在时用了 removenodeSelector/tolerations 存在与否决定用 remove 还是 replace;先用 kubectl get deploy <name> -o jsonpath='{.spec.template.spec.nodeSelector}' 确认
应用迁出独立域后,Pod 仍停在域内节点toleration 只影响调度,存量 Pod 不会自动迁移删除存量 Pod 触发重建,或配置 descheduler 的 RemovePodsViolatingNodeTaints 策略
新节点入域后被普通业务 Pod 占满加入集群后未及时封锁,普通业务抢先调度按第五章流程,新节点第一时间打 unschedulable 污点,入域完成后再摘除
kubectl patch deployment --all 清 affinity 误伤正常配置批量操作未区分业务批量清理前先导出全量配置备份:kubectl get deploy -n <ns> -o yaml > deploy-backup.yaml

十、操作建议

  • 任何批量打污点、批量 patch 应用的操作,先在 1 个节点、1 个 Deployment 上验证,再推广到全量。
  • 污点操作属于即时生效的调度面变更,操作窗口避开业务高峰;NoExecute 类驱逐操作务必确认目标业务的副本数和 PDB。
  • 域规划、节点归属、应用接入情况应维护成台账(域清单表 + 节点映射),避免"谁能进哪个域"只存在于某次命令历史里。
  • 临时性配置(异常节点排除、软污点灰度)要登记回收时间,防止长期遗留。

延伸阅读