本文通过一个真实的 K8s 集群操作案例,深入剖析 Taints 与 Tolerations 的底层原理,带你彻底搞懂 DaemonSet 的特殊调度行为和 operator: Exists 的匹配逻辑。

一、现象:系统组件为何无视污点?

最近在维护一个 K8s 集群时,我遇到了一个值得深入探究的现象:

  • 我给三个节点打了污点 node-role.cloud.kubeblocks.io/control-plane=true:NoSchedule
  • 原本以为新 Pod 不会再调度到这些节点上,但后续新增的 calico-node 和 kube-proxy Pod(注意:这些 Pod 是在打污点之后新建的)依然被调度到了这些节点上
  • 更值得思考的是:当我追加一个 NoExecute 污点后,删除 Pod 再重建,它们依然能调度回原节点

这背后的根本原因在于:calico-node 和 kube-proxy 并不是普通 Pod,而是以 DaemonSet 方式部署的系统组件。 DaemonSet 的设计目标就是在每个节点上运行一个 Pod,其调度依据是 DaemonSet 自身的 tolerations 配置,而非单个 Pod 的调度逻辑。只要 DaemonSet 中配置了对应的容忍规则,它就能在节点有污点的情况下持续运行。

带着这个发现,我梳理了整个排查过程。

二、基础概念回顾

2.1 污点(Taints)与容忍度(Tolerations)

概念作用配置位置
污点让节点"排斥"某些 PodNode 的 .spec.taints
容忍度让 Pod "容忍"节点的污点Pod 的 .spec.tolerations

2.2 污点的三种效果

Effect含义
NoSchedule新 Pod 不会调度到该节点(已有 Pod 不受影响)
PreferNoSchedule尽量不调度(软性限制)
NoExecute新 Pod 不会调度,且已有 Pod 若不匹配容忍度会被驱逐

三、实战操作全记录

3.1 查看节点初始污点

for node in iv-yet8omgwsgcva4fibmvz iv-yet8omgwsgcva4fie1zq iv-yet8omgwsgcva4ficnb8; do 
  echo -n "$node: "; kubectl get node $node -o jsonpath='{.spec.taints}' && echo
done

输出:

iv-yet8omgwsgcva4fibmvz: [{"effect":"NoSchedule","key":"node-role.cloud.kubeblocks.io/control-plane","value":"true"}]
iv-yet8omgwsgcva4fie1zq: [{"effect":"NoSchedule","key":"node-role.cloud.kubeblocks.io/control-plane","value":"true"}]
iv-yet8omgwsgcva4ficnb8: [{"effect":"NoSchedule","key":"node-role.cloud.kubeblocks.io/control-plane","value":"true"}]

此时每个节点只有 NoSchedule 污点。

3.2 查看系统 Pod 运行状态

kubectl get pod -n kube-system -owide | grep -E "fibmvz|fie1zq|ficnb8"
calico-node-zkrxt     1/1  Running  114s  172.31.53.193  iv-yet8omgwsgcva4fibmvz
kube-proxy-5d7c7      1/1  Running  3s    172.31.53.193  iv-yet8omgwsgcva4fibmvz

这两个 Pod 的 Age 显示它们是在打污点之后新建的,却依然调度到了有污点的节点上。

3.3 查看这些 Pod 的容忍度

kube-proxy 的容忍度:

kubectl get pod kube-proxy-5d7c7 -n kube-system -o yaml | grep -A 10 tolerations
tolerations:
- operator: Exists                                    # 关键:容忍所有污点!
- effect: NoExecute
  key: node.kubernetes.io/not-ready
  operator: Exists
- effect: NoExecute
  key: node.kubernetes.io/unreachable
  operator: Exists
- effect: NoSchedule
  key: node.kubernetes.io/disk-pressure
  operator: Exists

calico-node 的容忍度:

kubectl get pod calico-node-zkrxt -n kube-system -o yaml | grep -A 20 tolerations
tolerations:
- effect: NoSchedule
  operator: Exists                                     # 匹配所有 NoSchedule 污点
- key: CriticalAddonsOnly
  operator: Exists
- effect: NoExecute
  operator: Exists                                     # 匹配所有 NoExecute 污点
- effect: NoExecute
  key: node.kubernetes.io/not-ready
  operator: Exists
- effect: NoExecute
  key: node.kubernetes.io/unreachable
  operator: Exists
- effect: NoSchedule
  key: node.kubernetes.io/disk-pressure
  operator: Exists
- effect: NoSchedule
  key: node.kubernetes.io/memory-pressure
  operator: Exists
- effect: NoSchedule
  key: node.kubernetes.io/pid-pressure

3.4 追加 NoExecute 污点

for node in iv-yet8omgwsgcva4fibmvz iv-yet8omgwsgcva4fie1zq iv-yet8omgwsgcva4ficnb8; do 
  kubectl taint nodes $node node-role.cloud.kubeblocks.io/control-plane=true:NoExecute --overwrite
done

再次查看节点污点:

iv-yet8omgwsgcva4fibmvz: [{"effect":"NoExecute","key":"node-role.cloud.kubeblocks.io/control-plane","value":"true"},{"effect":"NoSchedule","key":"node-role.cloud.kubeblocks.io/control-plane","value":"true"}]

现在每个节点同时拥有 NoScheduleNoExecute 两个污点。

3.5 删除 Pod 验证重新调度

kubectl delete pod calico-node-zkrxt -n kube-system
kubectl delete pod kube-proxy-5d7c7 -n kube-system

查看新 Pod:

kubectl get pod -n kube-system -owide | grep fibmvz
calico-node-m9dfs     1/1  Running  15s  172.31.53.193  iv-yet8omgwsgcva4fibmvz
kube-proxy-56jzk      1/1  Running  2m32s 172.31.53.193  iv-yet8omgwsgcva4fibmvz

关键发现:即使增加了 NoExecute 污点,删除 Pod 后重建,它们依然调度回原节点!

3.6 查看 DaemonSet 级别的容忍度

calico-node DaemonSet:

kubectl get daemonset calico-node -n kube-system -o yaml | grep -A 20 tolerations
tolerations:
- effect: NoSchedule
  operator: Exists
- key: CriticalAddonsOnly
  operator: Exists
- effect: NoExecute          # ⭐ 明确存在
  operator: Exists

kube-proxy DaemonSet:

kubectl get daemonset kube-proxy -n kube-system -o yaml | grep -A 20 tolerations
tolerations:
- operator: Exists            # 容忍所有污点(NoSchedule + NoExecute + PreferNoSchedule)

四、核心知识点解析

4.1 operator: Exists 的匹配逻辑

配置匹配范围
operator: Exists(无 effect)匹配所有污点(任何 key、value、effect)
effect: NoSchedule, operator: Exists匹配所有 NoSchedule 污点
effect: NoExecute, operator: Exists匹配所有 NoExecute 污点

关键:operator: Exists 时,调度器只检查 effect,完全忽略 key 和 value。

4.2 为什么 calico 和 kube-proxy 能无视 NoExecute 污点?

节点污点kube-proxy 匹配规则calico 匹配规则
NoScheduleoperator: Exists(所有污点)✅effect: NoSchedule, operator: Exists
NoExecuteoperator: Exists(所有污点)✅effect: NoExecute, operator: Exists

两个组件都能容忍 NoExecute,因此追加 NoExecute 污点后它们依然可以调度。

4.3 DaemonSet 的特殊性:与普通 Pod 的本质区别

对比项普通 Pod(Deployment)DaemonSet Pod
调度依据Pod 自身的 tolerationsDaemonSet 的 tolerations
调度目标满足调度条件的任意节点每个节点
污点影响受污点限制可配置容忍度绕过污点

核心结论:DaemonSet 的设计目标就是在每个节点上运行 Pod,因此它的调度逻辑比普通 Pod 更"强势",可以通过 tolerations 配置绕过节点污点。

五、排查方法论总结

# 1. 查看节点污点
kubectl describe node <node-name> | grep Taints

# 2. 查看 Pod 容忍度
kubectl get pod <pod-name> -n <namespace> -o yaml | grep -A 10 tolerations

# 3. ⭐ 查看 DaemonSet 容忍度(关键!调度决策的真正依据)
kubectl get daemonset <ds-name> -n <namespace> -o yaml | grep -A 10 tolerations

重要提示: 排查 DaemonSet 的调度问题时,一定要查看 DaemonSet 本身的 tolerations,而不是只看某个 Pod 的配置。Pod 的 YAML 可能不完整显示所有继承的容忍度,而 DaemonSet 的配置才是调度器决策的真正依据。

六、总结

  1. DaemonSet 不等于普通 Pod:它以"每个节点一个 Pod"为目标,天生具备绕过污点的能力
  2. 调度决策看 DaemonSet,不是看 Pod:DaemonSet 控制器的调度依据是自身的 tolerations 配置
  3. operator: Exists 是理解容忍度匹配的核心:它只检查 effect,忽略 key/value
  4. NoExecute 污点可以被 DaemonSet 的容忍度绕过:只要配置了 effect: NoExecute, operator: Exists,Pod 就不会被驱逐
  5. 系统组件(calico、kube-proxy)天生配置了宽泛的容忍度,确保集群网络和代理功能覆盖所有节点

如果觉得有用,欢迎点赞、收藏、转发! 有任何问题,欢迎在评论区交流讨论。

更多推荐