主题
第 4 章:内置污点与自动容忍机制
本章是"精通"的分水岭:理解 K8s 控制面如何利用污点机制实现节点故障处理、优雅驱逐和系统组件调度。
4.1 K8s 内置污点清单
| 污点 | Effect | 由谁添加 | 含义 |
|---|---|---|---|
node-role.kubernetes.io/control-plane | NoSchedule | kubeadm / 安装工具 | 控制面节点,业务 Pod 禁止调度 |
node-role.kubernetes.io/master | NoSchedule | 老版本 kubeadm(≤1.23) | 同上(已废弃,被 control-plane 取代) |
node.kubernetes.io/not-ready | NoExecute | node-controller | 节点 NotReady(kubelet 失联/启动中) |
node.kubernetes.io/unreachable | NoExecute | node-controller | 节点网络不可达 |
node.kubernetes.io/memory-pressure | NoSchedule | kubelet | 节点内存压力 |
node.kubernetes.io/disk-pressure | NoSchedule | kubelet | 节点磁盘压力 |
node.kubernetes.io/pid-pressure | NoSchedule | kubelet | 节点 PID 资源耗尽 |
node.kubernetes.io/network-unavailable | NoSchedule | kubelet | 节点网络未就绪(CNI 未配置好) |
node.kubernetes.io/unschedulable | NoSchedule | 系统(cordon 时) | 节点被标记为不可调度 |
node.cloudprovider.kubernetes.io/uninitialized | NoSchedule | kubelet | 云厂商节点未初始化(外部 CCM 接管) |
node.cloudprovider.kubernetes.io/shutdown | NoSchedule+NoExecute | 云控制器 | 云主机正在关机 |
查看节点当前污点:
bash
kubectl describe node <node> | grep -A10 Taints4.2 Taint-Based Evictions(基于污点的驱逐)
自 1.6 引入、1.13 GA 的机制:节点生命周期控制器(node-controller)和 kubelet 通过自动打污点来表达节点状态问题,取代了过去硬编码的驱逐逻辑。
工作流程:
节点发生故障(如 kubelet 停止上报心跳)
│
▼
node-controller 检测到 NodeReady 条件异常
│
├── Ready=False → 打污点 node.kubernetes.io/not-ready:NoExecute
└── Ready=Unknown→ 打污点 node.kubernetes.io/unreachable:NoExecute
│
▼
taint-eviction-controller 扫描该节点上的 Pod
│
├── 无对应容忍的 Pod → 立即驱逐
└── 容忍带 tolerationSeconds → 倒计时结束后驱逐
│
▼
节点恢复 → 污点被移除 → 驱逐停止kubelet 侧同理:磁盘/内存/PID 压力时打对应 NoSchedule 污点(阻止新 Pod),压力解除后自动清除。
实战:调整节点故障时的驱逐时间
默认 300 秒太长?在 Pod 里显式覆盖(自动注入的默认值会被你的显式配置取代):
yaml
spec:
tolerations:
- key: "node.kubernetes.io/not-ready"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 30 # 节点故障 30 秒后就迁移
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 30反过来,对有状态、迁移成本高的服务,可以调大到 600 甚至更高,避免网络抖动引发不必要的迁移。
⚠️ 注意:
unreachable对应 Ready=Unknown(心跳完全丢失),not-ready对应 Ready=False(kubelet 上报了自己不健康)。两者最好成对配置。
4.3 DaemonSet 的自动容忍
DaemonSet 的使命是"每个节点都跑一个",所以系统会给它自动注入一批容忍。创建任意 DaemonSet 后查看:
bash
kubectl get ds <name> -n <ns> -o yaml | grep -A20 tolerations自动注入的典型容忍包括:
yaml
tolerations:
- key: node.kubernetes.io/not-ready # 节点没就绪也要尝试跑(如 CNI 插件本身)
operator: Exists
effect: NoExecute
- key: node.kubernetes.io/unreachable
operator: Exists
effect: NoExecute
- 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
operator: Exists
effect: NoSchedule
- key: node.kubernetes.io/unschedulable
operator: Exists
effect: NoSchedule
- key: node.kubernetes.io/network-unavailable
operator: Exists
effect: NoSchedule注意:node-role.kubernetes.io/control-plane 污点不在自动注入列表里。如果你想让 DaemonSet 也跑在 Master 节点(如日志/监控 Agent),需要手动加:
yaml
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule观察一下集群里的 kube-proxy、Calico/Flannel 等系统 DaemonSet,就能看到它们都显式配了这个容忍。
4.4 cordon/drain 与污点的关系
bash
kubectl cordon node1 # 标记不可调度
kubectl drain node1 # 驱逐 Pod 并标记不可调度
kubectl uncordon node1 # 恢复调度kubectl cordon node1本质等价于:kubectl taint nodes node1 node.kubernetes.io/unschedulable:NoSchedulekubectl uncordon等价于删除该污点。drain= cordon + 驱逐节点上所有非 DaemonSet Pod(走 Eviction API,尊重 PodDisruptionBudget)。
bash
# 验证
kubectl cordon node1
kubectl describe node node1 | grep Taints
# Taints: node.kubernetes.io/unschedulable:NoSchedule4.5 系统组件 Pod 为什么能跑在 Master 上
kubectl get pods -n kube-system -o wide 会发现 apiserver、etcd 等都跑在控制面节点。它们是静态 Pod(Static Pod),由 kubelet 直接管理(不经过调度器),manifest 中自带对 control-plane 污点的容忍:
bash
kubectl get pod kube-apiserver-master1 -n kube-system -o yaml | grep -A5 tolerations
# tolerations:
# - effect: NoExecute
# operator: Exists而 CoreDNS 等普通 Deployment 靠 controller-manager 注入的容忍 + 调度器特殊处理运行。
4.6 节点压力污点的实际效果
当 kubelet 报告资源压力时:
| 条件 | 自动污点 | 效果 |
|---|---|---|
| memory.available 低于阈值 | node.kubernetes.io/memory-pressure:NoSchedule | 新 Pod(无容忍)不再调度上来;同时 kubelet 按 QoS 驱逐 Pod(BestEffort 先死) |
| nodefs/imagefs 空间不足 | node.kubernetes.io/disk-pressure:NoSchedule | 同上 |
| PID 耗尽 | node.kubernetes.io/pid-pressure:NoSchedule | 同上 |
| CNI 未就绪 | node.kubernetes.io/network-unavailable:NoSchedule | 节点初始化阶段临时存在 |
这些压力类污点是 NoSchedule 而非 NoExecute——已在运行的 Pod 不会被污点机制驱逐(但内存/磁盘压力会触发 kubelet 自己的 eviction-manager 按优先级驱逐,那是另一套机制)。
本章小结
- 节点故障 = 自动打
not-ready/unreachableNoExecute 污点 → 默认 300 秒后驱逐 Pod,可在 Pod 中显式覆盖时长。 - 资源压力 = 自动打 NoSchedule 污点,只挡新 Pod。
- DaemonSet 自动获得一批内置污点的容忍,但 control-plane 污点需手动加。
- cordon = 打
unschedulable:NoSchedule污点;drain = cordon + 优雅驱逐。 - 理解内置机制是排查"Pod 为什么被驱逐/为什么 Pending"的关键。
动手练习
kubectl cordon一个工作节点,观察污点变化,再创建 Pod 验证其不会被调度上来,最后uncordon恢复。- 查看集群中 kube-proxy DaemonSet 的 tolerations,对照 4.3 节表格验证。
- 查看任意业务 Pod 自动注入的默认容忍,并尝试显式覆盖
tolerationSeconds: 60。