【Kubernetes从入门到精通】第28篇:污点和容忍——K8s的“拒之门外“机制
上一篇【第27篇】节点亲和性和Pod亲和性——让Pod去它该去的地方下一篇【第29篇】资源请求和限制——CPU和内存的斤斤计较摘要上两篇咱们一直在聊怎么吸引Pod去特定的Node——亲和性、打分散、拓扑域。但K8s还有个相反的机制排斥。你想啊集群里有些Node是VIP包间——比如装有4块A100显卡的GPU服务器只有训练任务才能用普通Web Pod不准上来浪费资源再比如Master节点默认就拒绝所有普通Pod入住。这就是污点和容忍Taint Toleration的用武之地。污点是Node身上的油漆——“我有GPU但你得带通行证才能来”。容忍是Pod手里的通行证——“我知道你有GPU污点但我带证了容忍放我过去”。没有通行证的Pod会被Scheduler的Predicates过滤器一脚踢开。这篇文章从Taint三要素Key/Value/Effect讲起三种EffectNoSchedule/PreferNoSchedule/NoExecute的区别再到实战搭建一个GPU专用节点池——把昂贵GPU资源圈起来只有训练任务能进。一、Taint Toleration——“这把锁只有带钥匙的人能开”1.1 污点vs亲和性——本质是对立统一【亲和性 vs 污点——拉 vs 推】 亲和性Attract 污点Repel ┌─────────────────────┐ ┌─────────────────────┐ │ Pod: 我要去SSD节点│ │ Node: 闲杂Pod别来 │ │ ↕ │ │ ↕ │ │ Node: 我是SSD节点 │ │ Pod: 我有通行证 │ │ │ │ │ │ 主动拉近 Pod→Node │ │ 主动推开 Node→Pod │ │ 作用在Pod上 │ │ 作用在Node上 │ └─────────────────────┘ └─────────────────────┘ 对比表 ┌──────────────┬────────────────┬────────────────┐ │ 特性 │ Node Affinity │ Taint/Toleration │ ├──────────────┼────────────────┼────────────────┤ │ 施加者 │ Pod │ Node │ │ 方向 │ Pod选Node │ Node拒绝Pod │ │ 默认行为 │ 谁都可以来 │ 谁都不准来 │ │ 需要配合 │ 不需要 │ 需要Pod配Toleration │ │ 典型场景 │ 指定节点调度 │ 隔离专用节点 │ │ │ 分散部署 │ 故障节点驱逐 │ └──────────────┴────────────────┴────────────────┘1.2 Taint的三要素# Taint 的标准格式# kubectl taint nodes node-name keyvalue:effect# 三要素分解# ┌──────────────────────────────────────────────────┐# │ kubectl taint nodes gpu-node-1 │# │ gputrue:NoSchedule │# │ ───┬─── ─────┬───── │# │ Key Effect │# │ └─┬─┘ │# │ Value │# └──────────────────────────────────────────────────┘# KeyValue污点的身份证——就像锁的型号# Effect污点的强度等级——NoSchedule/PreferNoSchedule/NoExecute1.3 Effect三种等级的对比【Effect 三档——从建议别来到来了就滚】 NoSchedule一档——禁止新来 ┌──────────────────────────────────────────────────────┐ │ 新Pod不准调度到我身上 │ │ │ │ 已有Pod✅ 继续跑不受影响 │ │ 新Pod无容忍❌ 不调度 │ │ 新Pod有容忍✅ 正常调度 │ │ │ │ 类比公寓挂了个已满的牌子—— │ │ 住着的住户不受影响新租客没介绍信不让进 │ └──────────────────────────────────────────────────────┘ PreferNoSchedule二档——尽量别来 ┌──────────────────────────────────────────────────────┐ │ 最好不要调度到我身上但实在没办法了也能来 │ │ │ │ 已有Pod✅ 继续跑 │ │ 新Pod无容忍⚠️ 尽量不调但没有别的Node了也能调 │ │ 新Pod有容忍✅ 正常调度 │ │ │ │ 类比酒店门口的建议别住这层在装修—— │ │ 有空房的话尽量给你安排别的楼层但真没房了也能住 │ └──────────────────────────────────────────────────────┘ NoExecute三档——已经住着的也请滚 ┌──────────────────────────────────────────────────────┐ │ 不光新Pod不准来已经在跑的Pod也给我滚 │ │ │ │ 已有Pod无容忍❌ 立即驱逐Evict │ │ 已有Pod有容忍无tolerationSeconds✅ 继续跑 │ │ 已有Pod有容忍tolerationSeconds3600⏰ 1小时后滚 │ │ 新Pod无容忍❌ 不调度 │ │ │ │ 类比房东突然说整栋楼要拆了所有人一周内搬走—— │ │ 已住的不搬会被强制清退 │ └──────────────────────────────────────────────────────┘Effect影响新Pod调度影响已有Pod典型场景NoSchedule拒绝调度不影响Master节点隔离、GPU专用池PreferNoSchedule尽量不调度不影响软性专用池、临时减负NoExecute拒绝调度驱逐已有Pod节点故障自动驱逐、节点维护要点NoExecute是最危险的一档。如果你给一台跑了100个Pod的Node打了个NoExecute的Taint这100个Pod如果没有对应的Toleration会被Scheduler在几秒内全部驱逐。不是优雅关闭——是直接赶走。生产环境打NoExecute Taint前一定要确认所有Pod都有对应的Toleration或者认清楚哪些Pod会受影响。二、Toleration——“我带了通行证”2.1 Pod的Toleration怎么配apiVersion:v1kind:Podmetadata:name:gpu-training-jobspec:tolerations:# ← 容忍声明——我知道这些污点我愿意接受-key:gpu# 匹配污点的Keyoperator:Equal# 精确匹配默认value:true# 匹配 Valueeffect:NoSchedule# 匹配 Effect# tolerationSeconds: 3600 # 仅NoExecute生效容忍3600秒后驱逐-key:maintenanceoperator:Exists# 只要Key存在就匹配不管Value是什么effect:NoExecutetolerationSeconds:600# 容忍600秒10分钟到时间就被驱逐-key:node.kubernetes.io/not-readyoperator:Existseffect:NoExecutetolerationSeconds:300# Node NotReady后容忍300秒containers:-name:trainingimage:nvidia/cuda:11.8-base# 查看Pod的Tolerationkubectl get pod gpu-training-job-oyaml|grep-A20tolerations# 查看Node的Taintkubectl describenodemaster-node|grep-A5Taints# Taints: node-role.kubernetes.io/master:NoSchedule# node-role.kubernetes.io/control-plane:NoSchedule2.2 operator的两种模式【Equal vs Exists——精确匹配 vs 模糊匹配】 Equal精确匹配 ┌────────────────────────────────────────┐ │ Node Taint: gputrue:NoSchedule │ │ Pod Toleration: │ │ key: gpu │ │ operator: Equal │ │ value: true ← 完全匹配 │ │ effect: NoSchedule │ │ │ │ 要求Key、Value、Effect 全对上 │ └────────────────────────────────────────┘ Exists模糊匹配 ┌────────────────────────────────────────┐ │ Node Taint: gputrue:NoSchedule │ │ Pod Toleration: │ │ key: gpu │ │ operator: Exists ← 只要Key存在 │ │ effect: NoSchedule │ │ │ │ 要求Key 对上就行不管Value是什么 │ │ 适用不确定污点的Value会怎么变的场景 │ └────────────────────────────────────────┘ 如果不指定value默认operator就是Equalvalue匹配value为空的Taint要点如果你的Toleration里不指定effect字段那它会匹配所有Effect类型的该Key的Taint。这有时候是便利有时候是陷阱——你可能只想容忍NoSchedule结果把NoExecute也容忍了导致Node出现故障时Pod不自动迁移。三、Master节点的默认污点——“VIP区域闲人免进”3.1 为什么Master上默认没Pod【K8s集群的默认污点布局】 ┌─────────────────────────────────────────────────────────┐ │ K8s 集群 │ │ │ │ ┌───────────────────────┐ ┌───────────────────────┐ │ │ │ Master/Control-Plane │ │ Worker-1 │ │ │ │ │ │ │ │ │ │ Taints: │ │ Taints: │ │ │ │ node-role.kubernetes. │ │ (无) │ │ │ │ io/master: │ │ │ │ │ │ NoSchedule │ │ 所有Pod随便来 │ │ │ │ │ │ │ │ │ │ 默认普通Pod进不来 │ │ ┌────┐┌────┐┌────┐ │ │ │ └───────────────────────┘ │ │Pod ││Pod ││Pod │ │ │ │ │ │ A ││ B ││ C │ │ │ │ ┌───────────────────────┐ │ └────┘└────┘└────┘ │ │ │ │ Worker-2 │ └───────────────────────┘ │ │ │ │ │ │ │ Taints: │ ┌───────────────────────┐ │ │ │ (无) │ │ Worker-3 │ │ │ │ │ │ │ │ │ │ ┌────┐┌────┐┌────┐ │ │ Taints: │ │ │ │ │Pod ││Pod ││Pod │ │ │ (无) │ │ │ │ │ D ││ E ││ F │ │ │ │ │ │ │ └────┘└────┘└────┘ │ │ ┌────┐┌────┐ │ │ │ └───────────────────────┘ │ │Pod ││Pod │ │ │ │ │ │ G ││ H │ │ │ │ │ └────┘└────┘ │ │ │ └───────────────────────┘ │ └─────────────────────────────────────────────────────────┘# 查看Master节点的Taintkubectl describenodemaster-node|grepTaints# Taints: node-role.kubernetes.io/control-plane:NoSchedule# 如果非要在Master上跑Pod比如测试集群资源紧张kubectl taint nodes master-node node-role.kubernetes.io/control-plane:NoSchedule-# ↑# 末尾的 - 表示移除这个Taint# 恢复——不让Pod调上来kubectl taint nodes master-node node-role.kubernetes.io/control-plane:NoSchedule# 只允许特定Pod比如监控组件调到Master上apiVersion: v1 kind: Pod metadata: name: prometheus-node-exporter spec: tolerations: - key:node-role.kubernetes.io/control-planeoperator:Existseffect:NoSchedule# ← 带上通行证就可以进Master了containers: - name: node-exporter image: prom/node-exporter3.2 K8s自动添加的污点——保护节点的免疫系统K8s的Node Controller会自动给有问题Node打上几个内置污点【K8s内置污点——节点的生病自诊】 ┌──────────────────────────────────────────────────────────┐ │ 污点 Key 触发条件 │ │ ─────────────────────────────────────────────────────── │ │ node.kubernetes.io/not-ready Node状态变成NotReady │ │ kubelet超过40s没上报 │ │ │ │ node.kubernetes.io/unreachable Node与API Server失联 │ │ 网络断、机器挂了 │ │ │ │ node.kubernetes.io/disk-pressure 磁盘使用率超过阈值 │ │ (默认85%) │ │ │ │ node.kubernetes.io/memory- 内存使用率超过阈值 │ │ pressure (默认可用100Mi) │ │ │ │ node.kubernetes.io/pid-pressure 进程数超过阈值 │ │ │ │ node.kubernetes.io/network- 网络不可用部分CNI │ │ unavailable 需要特定网络插件支持 │ └──────────────────────────────────────────────────────────┘ 这些污点的Effect都是 NoExecute会驱逐不耐受的Pod。 大部分Pod包括Deployment管理的都有默认Toleration - not-ready/unreachable 容忍 300 秒5分钟宽限期 - 目的是等Node自动恢复避免Pod过早驱逐造成雪崩# K8s 自动给Pod添加的默认Toleration你不写也会有# 可以用 kubectl describe pod 看到tolerations:-effect:NoExecutekey:node.kubernetes.io/not-readyoperator:ExiststolerationSeconds:300# Node NotReady 后等300秒才驱逐-effect:NoExecutekey:node.kubernetes.io/unreachableoperator:ExiststolerationSeconds:300# Node失联后等300秒要点默认的300秒Toleration是保护Pod不被假故障误伤——比如网络抖动几秒Node暂时失联Pod不应该立刻被驱逐。但这也意味着真正的故障检测延迟最多300秒。如果你的应用对故障转移速度有极致要求可以自定义Toleration为0秒——但只要网络抖一下就可能导致Pod被驱逐重建做好心理准备。# 查看这些自动添加的Taint和Node状态kubectl describenodeworker-1|grep-A10Conditions:# Conditions:# MemoryPressure False ...# DiskPressure False ...# PIDPressure False ...# Ready True ...四、实战——GPU专用节点池4.1 场景分析【GPU专用节点池——VIP显卡包间】 痛点 ┌────────────────────────────────────────────────────────┐ │ 你的集群里有2台GPU节点每台4块A100每块价值10万 │ │ │ │ ❌ 问题普通Web Pod也可能被调度到GPU节点上 │ │ → Web Pod根本用不着GPU只是白白占着昂贵的资源 │ │ → GPU训练任务反而因为资源被占用而Pending │ │ │ │ ✅ 方案给GPU节点打Taint只有训练任务才带Toleration │ │ → GPU节点成了VIP包间Web服务根本进不来 │ └────────────────────────────────────────────────────────┘4.2 实现代码# 步骤1给GPU节点打Label方便识别和管理kubectl labelnodegpu-node-01acceleratornvidia-a100 kubectl labelnodegpu-node-02acceleratornvidia-a100 kubectl labelnodegpu-node-01 node-typegpu kubectl labelnodegpu-node-02 node-typegpu# 步骤2给GPU节点打Taint——普通Pod不许进来kubectl taint nodes gpu-node-01gputrue:NoSchedule kubectl taint nodes gpu-node-02gputrue:NoSchedule# 验证Taint已生效kubectl describenodegpu-node-01|grepTaints# Taints: gputrue:NoSchedule# 步骤3普通Web Pod——没有TolerationapiVersion:apps/v1kind:Deploymentmetadata:name:web-appspec:replicas:3selector:matchLabels:app:web-apptemplate:metadata:labels:app:web-appspec:# 没有 tolerations → 进不了GPU节点containers:-name:nginximage:nginx# 步骤4GPU训练任务——带Toleration NodeAffinity双重控制apiVersion:batch/v1kind:Jobmetadata:name:gpu-training-v2spec:parallelism:2template:spec:# 污点容忍——我知道这个节点有GPU Taint放我过去tolerations:-key:gpuoperator:Equalvalue:trueeffect:NoSchedule# 再用Node Affinity确保真正跑到GPU节点上# Toleration只是不排斥Node Affinity是我想去affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:-matchExpressions:-key:acceleratoroperator:Invalues:-nvidia-a100containers:-name:trainingimage:nvidia/cuda:11.8-devel-ubuntu22.04command:[python,train.py]resources:limits:nvidia.com/gpu:2# 请求2块GPUrequests:cpu:8memory:32GirestartPolicy:Never# 验证效果# 1. 部署Web应用和训练任务kubectl apply-fweb-app.yaml kubectl apply-fgpu-training.yaml# 2. 查看Pod分布kubectl get pods-owide# NAME READY STATUS NODE# web-app-xxx 1/1 Running worker-1 ← 普通节点# web-app-yyy 1/1 Running worker-2 ← 普通节点# web-app-zzz 1/1 Running worker-3 ← 普通节点# gpu-training-v2-aaa 1/1 Running gpu-node-01 ← GPU节点# gpu-training-v2-bbb 1/1 Running gpu-node-02 ← GPU节点# 3. 尝试把Web Pod手动调到GPU节点——会失败kubectl run test-web--imagenginx --dry-runclient-oyaml|\seds/nodeName: .*/nodeName: gpu-node-01/|kubectl apply-f-# Pod会一直PendingEvents显示node gpu-node-01 has taint gputrue:NoSchedule【Taint NodeAffinity 双保险的调度效果】 ┌─────────────────────────────────────────────────────────┐ │ K8s 集群 │ │ │ │ 普通Worker节点无Taint GPU节点Taint: gputrue│ │ ┌────────────────────┐ ┌────────────────────┐ │ │ │ ┌────┐ ┌────┐ ┌──┐ │ │ 闲人免进 │ │ │ │ │Web │ │Web │ │API│ │ │ │ │ │ │ │ 1 │ │ 2 │ │ 1 │ │ │ ┌────────────────┐ │ │ │ │ └────┘ └────┘ └──┘ │ │ │ GPU Training │ │ │ │ │ │ │ │ (带Toleration) │ │ │ │ │ 没有Toleration的Pod │ │ │ 4×A100 GPU │ │ │ │ │ 自动放到这里 │ │ └────────────────┘ │ │ │ └────────────────────┘ │ ┌────────────────┐ │ │ │ │ │ GPU Inference │ │ │ │ ┌────────────────────┐ │ │ (带Toleration) │ │ │ │ │ ┌────┐ ┌────┐ │ │ │ 2×A100 GPU │ │ │ │ │ │API │ │DB │ │ │ └────────────────┘ │ │ │ │ │ 2 │ │ 1 │ │ └────────────────────┘ │ │ │ └────┘ └────┘ │ │ │ └────────────────────┘ │ └─────────────────────────────────────────────────────────┘4.3 更复杂的专用节点方案——多级Taint# 场景集群里有SSD节点和GPU节点分两层专用# 第一层SSD加速节点——给数据库和缓存专用kubectl taint nodes ssd-node-01storagessd:NoSchedule kubectl taint nodes ssd-node-02storagessd:NoSchedule# 第二层GPU节点——给训练/推理任务专用kubectl taint nodes gpu-node-01gputrue:NoSchedule kubectl taint nodes gpu-node-02gputrue:NoSchedule# Redis Pod——只能进SSD节点apiVersion:v1kind:Podmetadata:name:redis-cachespec:tolerations:-key:storageoperator:Equalvalue:ssdeffect:NoScheduleaffinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:-matchExpressions:-key:storage-typeoperator:Invalues:-ssdcontainers:-name:redisimage:redis:7-alpine要点Taint NodeAffinity 是黄金搭档——Taint确保不该来的进不来NodeAffinity确保该来的确来了。只用Taint不用Affinity的问题是如果GPU任务调度时GPU节点正好满了它可能被调到普通节点普通节点没Taint不拦它而你希望的是必须跑在GPU上——加上NodeAffinity就能确保这一点。五、Taint/Toleration常用操作速查# 查看操作 # 查看所有Node的Taintkubectl get nodes-ocustom-columns\NAME:.metadata.name,\TAINTS:.spec.taints[*].key,\EFFECT:.spec.taints[*].effect# 查看单个Node的完整Taint信息kubectl describenodegpu-node-01|grep-A10Taints# 查看Pod的Tolerationkubectl get pod my-pod-ojsonpath{.spec.tolerations}|jq# 添加操作 # 添加一个NoSchedule Taintkubectl taint nodes worker-1dedicatedexperimental:NoSchedule# 添加一个NoExecute Taint已有Pod如果不容忍会被驱逐kubectl taint nodes worker-1 node-problemdisk-pressure:NoExecute# 移除操作 # 移除Key为dedicated的Taint不管Value和Effect是什么kubectl taint nodes worker-1 dedicated-# 移除KeyValue精确匹配的Taintkubectl taint nodes worker-1dedicatedexperimental:NoSchedule-# 高级操作 # 查看哪些Pod会被某个Taint影响kubectl get pods-A-ojson|jq-r .items[] | select(.spec.tolerations null or (.spec.tolerations | map(select(.key gpu)) | length 0)) | \(.metadata.namespace)/\(.metadata.name) \(.spec.nodeName) # 给一个Node加多个Taintkubectl taint nodes gpu-node-01gputrue:NoSchedule kubectl taint nodes gpu-node-01dedicatedgpu-pool:NoSchedule# 现在 gpu-node-01 有两个Taint# Pod需要同时容忍两个才能调度上来本篇小结污点和容忍是K8s调度的反亲和机制——不是拉近是推开Taint是锁在Node上Toleration是钥匙在Pod上——没有钥匙的Pod进不了带锁的Node三种Effect等级NoSchedule禁止新调、PreferNoSchedule建议别调、NoExecute强行驱赶Master节点默认有Taint——这就是为什么你的业务Pod不会跑到Master上K8s自动给故障Node打NoExecute Taint——防止Pod继续被调度到坏掉的节点上Taint NodeAffinity 双保险——Taint负责排他别人别来NodeAffinity负责指定我一定要去这里污点解决的是拒之门外亲和性解决的是请君入瓮。但调度只是去哪的问题到了之后还得解决给多少资源的问题——下一篇咱们聊资源请求和限制CPU和内存的斤斤计较。上一篇【第27篇】节点亲和性和Pod亲和性——让Pod去它该去的地方下一篇【第29篇】资源请求和限制——CPU和内存的斤斤计较