资讯详情

K8s调度核心机制:节点亲和、Pod反亲和与污点容忍实践

📅 2026/10/21 12:22:26 | 华诺云谱 👁 阅读
K8s调度核心机制:节点亲和、Pod反亲和与污点容忍实践
1. K8s调度入门先把“把Pod放到哪台机器”这件事想清楚做Kubernetes的同学早晚都会撞上调度这个话题。刚接触时你可能觉得调度不过就是“把Pod放到一个Node上跑起来”但真上了生产环境你会发现事情远没那么简单——节点标签怎么打、Pod该不该往某类机器上放、如何避免两个副本挤在同一台物理机、某个节点要下线维护怎么办这些全是调度要管的事。我最早踩坑是在一次压测环境里。当时业务方要求把核心服务均匀打散到三台高配机器上我没做任何亲和性配置只靠ReplicaSet默认调度。结果Kubernetes调度器把三个副本全部塞进了一台机器另外两台空转那台机器CPU直接打满服务延迟飙到不可接受。从那以后我就明白调度这事不能靠运气必须把节点亲和、Pod亲和、污点容忍这些机制真正吃透并且清楚它们各自管什么、怎么组合用。这个标题里提到的内容其实是三条完全不同的调度控制路径节点亲和和反亲和解决的是“Pod和Node之间”的选择关系Pod亲和和反亲和解决的是“Pod和Pod之间”的部署关系而Taints污点和Tolerations容忍解决的是“Node主动拒绝哪些Pod”的问题。它们三个层面互相独立但又经常需要配合使用。这篇文章就围绕这三条路径把原理、配置、实操和排障一次讲透。适合谁来读如果你已经在用Kubernetes部署应用但总觉得调度不可控、Pod落在哪全靠运气或者你准备设计多可用区、多机型混部的集群想把Pod的分布规则表达清楚又或者你只是被“亲和”“污点”这些概念绕得头晕——这篇文章就是给你写的。2. 先建立一个整体认知调度的几种手段分别管什么2.1 调度器到底在做什么Kubernetes默认的调度器是kube-scheduler它干的事情可以简化成一句话为每个待调度的Pod找到一个最合适的Node。这个过程分两步先过滤Filtering后打分Scoring。过滤阶段调度器会把完全不能满足Pod要求的Node剔除掉。比如Pod声明了要2个CPU那CPU不足的Node直接被淘汰Pod挂了一个节点亲和规则指定必须跑在带有diskssd标签的节点上那不带这个标签的Node全部出局。这个阶段是“硬约束”不满足就拉倒。打分阶段调度器对剩下的Node逐一打分分数高的优先。默认打分规则很多包括资源余量、Pod分布情况、节点上已有的Pod数量等等。你配置的亲和规则如果是preferred类型软性偏好就是在打分阶段加分而不是在过滤阶段强制筛选。明白这两个阶段非常重要。很多人配置亲和性时搞不清楚为什么有的规则没生效原因往往就是他把“软规则”当成了“硬规则”来用。硬规则是required不满足就不调度软规则是preferred不满足最多是分数低Pod照样能调度上去。2.2 三种调度控制路径的关系NodeSelector、节点亲和、Pod亲和/反亲和、污点容忍这几种机制不是替代关系而是互补关系控制路径作用对象表达的问题硬/软nodeSelectorPod → Node我只想去带某标签的机器硬节点亲和nodeAffinityPod → Node我倾向于去某类机器也可以限定必须去硬/软都支持Pod亲和podAffinityPod → Pod我要和某类Pod待在同一个拓扑域硬/软都支持Pod反亲和podAntiAffinityPod → Pod我不要和某类Pod待在同一个拓扑域硬/软都支持Taints TolerationsNode 主动 → Pod某些Pod不准上来除非你有“通行证”硬默认举一个实际场景来串起这几种手段你有一套机器学习训练集群gpu节点打上了gputesla的标签普通业务节点没打。训练任务Pod用节点亲和强制要求跑到gputesla的节点上为了保证训练稳定性再用Pod反亲和让同一个训练任务的不同副本不要挤在同一台GPU机器上同时给GPU节点打一个污点防止那些没声明容忍的普通Pod“蹭机”被调度上来。三者配合才算把调度规则表达完整。这个例子你应该能看出来节点亲和和污点做的事情并不重复。节点亲和是Pod主动选择Node污点是Node主动拒绝Pod。即便你给Pod写了required节点亲和只要Node上有污点而Pod没容忍Pod依然进不去。两者相遇时污点的优先级更高。3. 节点亲和与反亲和把Pod精准导航到目标机器3.1 从nodeSelector说起为什么还要有nodeAffinityKubernetes最早提供的节点选择能力是nodeSelector。它非常简单——Pod里声明一个map比如gpu: tesla调度器就只把这个Pod放到带gputesla标签的Node上。简单、直观但也非常死板表达能力极其有限。它有几个硬伤。第一只支持精确匹配keyvalue想表达“这个标签存在就行”或者“这个标签的值不等于某个值”就不行。第二只能写多个条件的AND没法表达“满足A或B任一即可”。第三只有硬约束没有软约束——没法表达“我优先想跑在SSD机器上但如果没有普通机器也行”。节点亲和nodeAffinity就是来补这些短板的。它是nodeSelector的进阶版表达能力强大得多并且在设计上完全兼容了原有场景。为了平滑升级Kubernetes后来在Node上增加了nodeAffinity字段之后nodeSelector依然被保留。在现在的API里你可以仍然用nodeSelector也可以用nodeAffinity或者两个一起用。如果两个都写了那调度时会同时满足两者的条件。3.2 nodeAffinity的两种类型和四种匹配操作符节点亲和配置里最关键的是schedulingIgnoredDuringExecution这个概念。你可以把它理解成Kubernetes对亲和规则的一种“软硬分离”设计。字段全称很长拆开来看requiredDuringSchedulingIgnoredDuringExecution调度时必须满足不满足就调度失败。但Pod已经运行之后即使Node标签发生变化导致规则不再满足已经运行的Pod不会被驱逐。preferredDuringSchedulingIgnoredDuringExecution调度时尽量满足不满足也能调度只是优先级低。运行期的行为同上。后面那个IgnoredDuringExecution其实是执行期忽略的意思也就是Kubernetes不会因为标签变化去主动把Pod挪走。目前还有requiredDuringExecution之类的计划特性但到这篇文章写作时你只需要关注上面两种。表达匹配条件时nodeAffinity支持的匹配符有四个In值必须在给定列表中等效等于nodeSelector的精确匹配NotIn值不能在给定列表中Exists标签键存在即可不关心值DoesNotExist标签键不存在另外还有一个Gt和Lt用于数值型标签的比较大小。这两个操作符适用于标签值是整数形式的场景比如memory64这种。下面是一个完整的示例。假设集群里有三种节点SSD高IO节点、GPU训练节点、普通节点对应的标签分别是disk-typessd、gputrue、disk-typehdd。我们要部署一个数据库Pod它必须跑在SSD节点上同时希望尽量跑在CPU主频更高的节点上假设打了cpu-fasttrue标签apiVersion: v1 kind: Pod metadata: name: db-pod spec: containers: - name: db image: mysql:8.0 affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: disk-type operator: In values: - ssd preferredDuringSchedulingIgnoredDuringExecution: - weight: 80 preference: matchExpressions: - key: cpu-fast operator: Exists注意nodeSelectorTerms是个列表列表里的多个term之间是OR关系。也就是说只要满足其中一个term就算通过过滤。而单个term内部的多个matchExpressions是AND关系必须全部满足。这点非常容易写错。你如果有多个条件需要“同时满足”一定要把它们放在同一个matchExpressions里。如果你图省事拆成了两个term那调度器理解的是“满足其中一个就行”结果可能完全不是你要的。3.3 weighted权重到底怎么影响调度结果preferredDuringSchedulingIgnoredDuringExecution里的weight字段很多人不理解它的作用。简单说调度器打分时每满足一条preferred规则就给节点加上相应的权重分。多个preferred规则可以各设权重最终调度器会把所有加分累加到节点总分里。但需要注意weight值本身没有绝对意义只有相对意义。你设了80和60跟设了8和6相比对最终排序的影响是完全一样的——因为其他打分项的默认权重可能高达几十甚至上百你的120分只是整体的一个加分项不能决定一切。所以有个实操经验权重值的数量级要根据场景选择。如果想让某条偏好规则真正对调度结果有显著影响建议权重设在50以上甚至100如果只是“聊胜于无”的偏好可以设10左右。但别指望权重能产生“硬约束”的效果它永远只是加分项。3.4 节点反亲和到底存在吗NotIn和DoesNotExist就是反亲和严格来说Kubernetes并没有一个独立叫“节点反亲和”的字段。反亲和的表达能力是通过NotIn和DoesNotExist这两个操作符实现的。你只需要把匹配条件从“我要去”改成“我不要去”就行。举个例子某类节点打上了rolespot的标签表示这些是竞价实例随时可能被回收。生产环境的Web服务不希望跑在上面spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: role operator: NotIn values: - spot这一段表达的意思是role标签的值不能是spot。注意一个细节如果某个节点压根没有role这个标签那么NotIn的判断会直接通过——因为NotIn只关心标签存在且值匹配的情况。这跟DoesNotExist是完全不同的语义前者允许标签存在但值不匹配也允许标签不存在后者严格要求标签键不能存在。这在某些场景下会埋坑。比如你的节点上如果有些机器根本没有打role标签那NotIn: spot是拦不住这些无标签节点的。如果你希望“只要机器打上了这个标签且值是spot就不能去”那没问题如果你希望“没打标签的也不许去”那你得再加一条DoesNotExist或单独控制标签的覆盖范围。4. Pod亲和与反亲和让Pod和Pod之间“抱团”还是“散开”4.1 为什么要关心Pod和Pod之间的位置关系节点亲和已经把Pod和Node之间的关系管住了但很多时候你要表达的需求是“Pod与Pod之间”的位置。最典型的两个场景第一是抱团。比如你有一个Redis缓存和一个业务服务业务服务对缓存的访问延迟极其敏感你希望业务Pod和Redis Pod尽量落在同一台机器上走本机回环网络而不是跨节点走一遍物理网络。这时候需要Pod亲和。第二是散开。比如一个无状态服务的多个副本如果全部堆在同一台机器上这台机器一挂整个服务的所有副本全部不可用。为了保证高可用你需要让这些副本尽量分布到不同的机器上。这时候需要Pod反亲和。Kubernetes的Pod亲和和反亲和就是专门处理这两种需求的。它跟节点亲和的本质区别在于Pod亲和的条件不是“Node具备什么标签”而是“某个Node上已经运行了哪些Pod”。4.2 topologyKeyPod亲和里必须弄懂的核心概念配置Pod亲和时topologyKey是一个绕不开的字段。它决定的是“同一个拓扑域”到底按什么尺度来划分。这个字段的值是Node上的一个标签键。Kubernetes用这个标签键来给Node分组。比如kubernetes.io/hostname以机器维度分组每个Node是一个独立拓扑域这是最常用的粒度topology.kubernetes.io/zone以可用区为维度分组同一个可用区内的Node属于同一个拓扑域topology.kubernetes.io/region以地域为维度分组举个例子如果配置了Pod亲和要求“和某类Pod处于同一个拓扑域”topologyKey用的是kubernetes.io/hostname那调度器会把目标Pod所在的Node作为参照然后只把新Pod调度到同一台Node上。如果topologyKey换成topology.kubernetes.io/zone那只要在同一个可用区内哪怕不同机器也行。这个字段是必填的。你不写配置直接校验失败。为什么强制要求因为Kubernetes需要一个明确的尺度来判定亲和与反亲和没有尺度语义就无法成立。特别注意Pod反亲和里topologyKey还不能是空的而且通常不能用kubernetes.io/hostname以外的超大粒度——当然这不是强制的只是从高可用角度建议大家认真想清楚。如果你把反亲和的topologyKey设成topology.kubernetes.io/zone那同一个可用区内就可以同时跑多个副本这在高可用上的保护力度会弱很多。4.3 Pod亲和/反亲和的配置实例下面看一个实际配置。假设你有两个服务一个是cache已经通过Deployment部署好了Pod带appcache标签另一个是api服务希望跟cache的Pod跑到同一台机器上减少访问延迟apiVersion: apps/v1 kind: Deployment metadata: name: api spec: replicas: 3 selector: matchLabels: app: api template: metadata: labels: app: api spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - cache topologyKey: kubernetes.io/hostname containers: - name: api image: myapp/api:v1注意这里有个隐含条件api和cache必须部署在同一个命名空间内。如果不在同一个命名空间需要额外加namespaces字段指明要匹配的命名空间列表。再看反亲和的例子。同一个服务有5个副本希望它们不要落在同一台机器上spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - api topologyKey: kubernetes.io/hostname这段配置的语义是在调度每个副本时调度器会检查目标Node上是否已经存在appapi的Pod。如果存在就拒绝把新的Pod调度上去。这样5个副本就会被尽量分散到5台不同的机器上。4.4 踩坑实录Pod反亲和的“自己人”问题上面这段配置有一个非常经典的坑反亲和的labelSelector匹配到了自己。因为Deployment的Pod模板本身带有appapi这个标签所以每次新调度一个Pod时它都算作“已经存在的目标Pod”的同类。这就意味着第一个Pod调度成功后第二个Pod不能跟第一个在同一台机器第三个不能跟第一个、第二个在同一台机器。如果集群里机器数量少于副本数后面的Pod会一直Pending。这不是配置错误而是语义使然。很多人第一次写Pod反亲和时都会遇到“怎么有副本一直Pending不调度”的问题原因就是这个。解决办法有两个如果你就是想严格保证每个副本分散在不同机器上那这个行为是合理的你需要保证集群有足够多的Node。如果你只是希望避免和别的服务混部那labelSelector里要选一个能区分“自己副本”和“对方副本”的标签不要把自身标签圈进去。另外一个常见做法是软反亲和。为了高可用你可能并不需要“绝对不许同机”只是希望调度器“尽量别让我和副本们挤在一起”。这时用preferredDuringSchedulingIgnoredDuringExecution更合适。spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - api topologyKey: kubernetes.io/hostname软反亲和不会导致调度失败只是降低分数。集群机器不足时多个副本也能挤在一台机器上跑起来只是调度器会优先尝试分散。这个在实际生产里更常用因为硬反亲和太容易把调度卡死。5. Taints污点与Tolerations容忍给Node装上“门禁”5.1 污点的本质Node主动把Pod拒之门外节点亲和、Pod亲和都是从Pod视角出发的Pod说我想到哪去、想跟谁在一起。但实际运维里Node自己往往也有诉求。比如某台机器要做内核升级暂时不想接收新Pod某台机器是GPU专用机器不希望普通业务Pod跑上来某个节点磁盘异常希望上面的Pod尽快搬走。这些诉求靠亲和规则是表达不了的。这时候就要用污点Taints。污点是打在Node上的一个标记。一个污点由三部分组成key、value和effect。调度器在调度Pod时会检查Node上的所有污点然后看Pod是否有对应的容忍Tolerations。如果Pod不容忍某个污点同时该污点的effect是NoSchedule级别的这个Pod就不会被调度到该Node上。一个很生活化的理解Node是一间VIP休息室污点就是门口挂的牌子写着“非请勿入”。Pod想进去必须有门禁卡也就是对应的容忍。亲和规则是Pod主动选择Node污点则是Node定义准入规则。5.2 三种污点effect分别是什么意思effect字段决定污点的严格程度一共有三种NoSchedule新的Pod不调度到该节点上但已经运行在该节点上的Pod不受影响可以继续跑。PreferNoSchedule软性的NoSchedule调度器会尽量避免把Pod调度到这个节点上但实在没地方去时也可以调度过来。NoExecute最严格。不仅新Pod不能调度上来连已经运行在这个节点上的Pod如果没有对应的容忍也会被驱逐。前两种主要影响“未来”的调度决策NoExecute会影响“现在”正在运行的Pod。这个区别非常重要因为做节点维护时如果你希望把节点上的Pod全部赶走用NoExecute而不是手动一个个删除Pod。5.3 污点管理的实操命令污点的管理主要通过kubectl命令行完成。常见的操作有三个给节点打污点kubectl taint nodes node1 key1value1:NoSchedule这里的key1value1是污点的键值对NoSchedule是effect。如果只关心键不关心值也可以写成key1:NoSchedule。去掉污点kubectl taint nodes node1 key1value1:NoSchedule-注意末尾的减号没有它这条命令就是在打污点而不是去掉污点。这是很多人会犯的低级错误。去掉污点时不要求value完全一样只要key和effect匹配就能移除。查看节点的污点kubectl describe node node1输出信息中会有Taints字段列出了这个节点上的所有污点。排查调度问题时第一步就是先看这个字段。5.4 Pod容忍配置的写法与细节Pod要容忍某个污点需要在Pod的spec里加上tolerations字段。最基本的写法tolerations: - key: key1 operator: Equal value: value1 effect: NoSchedule这段的意思是这个Pod容忍key1value1且effect为NoSchedule的污点。当Node上有这个污点时Pod可以被调度上去。还有两种更灵活的写法。第一种是只关心key不关心valuetolerations: - key: key1 operator: Exists effect: NoScheduleoperator: Exists表示只要污点的key存在、effect匹配不管value是什么都能容忍。第二种是容忍所有污点tolerations: - operator: Exists这种写法没有指定key和effect表示无条件容忍任意污点。一般只在特殊场景下用比如系统组件DaemonSet的Pod或者你确定这个Pod放到任何节点上都不会有问题。5.5 NoExecute里还有一个时间参数在NoExecute污点的容忍配置里tolerationSeconds是一个非常有用的字段。它表示“我能忍受这个污点多长时间”超时之后如果污点还在Pod就会被驱逐。tolerations: - key: key1 operator: Equal value: value1 effect: NoExecute tolerationSeconds: 60这个配置的意思是Node上有key1value1且effect为NoExecute的污点时Pod会在这个节点上继续运行最多60秒之后被驱逐。这个字段的用途很多。比如节点要关机维护你先给节点打一个NoExecute污点但允许某些关键Pod多留几分钟做优雅退出。时间一到Pod自然被驱离比你手动数秒去删Pod优雅多了。5.6 Kubernetes自动添加的系统级污点生产环境中很多节点上的污点并不是人打的而是Kubernetes节点控制器自动添加的。常见的有node.kubernetes.io/not-ready节点未就绪effect为NoExecutenode.kubernetes.io/unreachable节点不可达effect为NoExecutenode.kubernetes.io/disk-pressure节点磁盘压力过大node.kubernetes.io/memory-pressure节点内存压力过大node.kubernetes.io/network-unavailable节点网络不可用默认情况下Pod对这些系统级污点是没有任何容忍的。所以当一个节点突然变成NotReady状态时Kubernetes会触发Pod驱逐流程把Pod从该节点上移走重新调度到其他健康节点。但有一个细节在节点宕机或不可达的情况下Pod并不是立即被驱逐而是等待一个默认的容忍时间通常是300秒。这个时间由Pod的tolerationSeconds控制默认值由集群控制器设置。要通过这个机制快速感知Pod失联并把工作负载迁走可以给核心Pod配置更短的tolerationSeconds增加故障响应速度。比如tolerations: - key: node.kubernetes.io/unreachable operator: Exists effect: NoExecute tolerationSeconds: 30这样节点不可达后这个Pod只会等待30秒就会被调度到别的节点上服务恢复速度会快很多。6. 综合实战把亲和、反亲和、污点组合起来用6.1 一个完整的调度方案长什么样前面分别介绍了三种机制但真实生产环境里它们往往是组合出场的。我拿一个模拟项目场景来演示完整配置这个项目是一个典型的多层Web服务包含前端Nginx、后端API、数据库MySQL部署在三类节点上node-groupweb普通Web节点数量多配置均衡node-groupdb数据库专用节点带SSD和较高内存打了污点dbdedicated:NoSchedulenode-groupgpuGPU节点给了机器学习任务专用部署需求前端Nginx副本要分散在不同机器上高可用优先。后端API要跟Redis跑到同一台机器降低延迟。数据库MySQL只跑到数据库专用节点并且不能让别的Pod蹭上来。GPU节点不允许普通业务Pod进入。完整配置思路如下。数据库节点的污点提前打好kubectl taint nodes db-node-1 dbdedicated:NoScheduleMySQL的Deployment配置apiVersion: apps/v1 kind: Deployment metadata: name: mysql spec: replicas: 1 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: tolerations: - key: db operator: Equal value: dedicated effect: NoSchedule affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: node-group operator: In values: - db containers: - name: mysql image: mysql:8.0这个配置的两层含义第一层MySQL有dbdedicated污点的容忍能进入数据库专用节点第二层节点亲和强制要求node-groupdb确保它只落在数据库节点上。两个条件叠加别的节点它不去专用节点上它进得去。再给数据库节点打一个反向保护不允许非数据库Pod跑上来。这个可以用污点实现但要注意如果只是打了污点那些带有db容忍的Pod都能进来。如果还要限制得更死需要配合Pod反亲和或准入策略。生产上通常会结合命名空间隔离来管理。API服务的配置apiVersion: apps/v1 kind: Deployment metadata: name: api spec: replicas: 3 selector: matchLabels: app: api template: metadata: labels: app: api spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - redis topologyKey: kubernetes.io/hostname podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - api topologyKey: kubernetes.io/hostname containers: - name: api image: myapp/api:v1这里有一个值得注意的问题API Pod要求跟Redis同机但同时有3个副本如果集群里Redis只部署了1个副本那3个API Pod会全部挤到Redis所在的那台机器上。虽然反亲和是preferred只能尽量分散但同机要求是required实际上3个API Pod依然会被塞到同一台机器。所以部署之前要评估清楚如果API是多副本的Redis也应该做成多副本或者把Pod亲和改成preferred而不是required。否则亲和规则会跟副本数打架反而制造热点。6.2 多种机制叠加时的调度判断顺序当节点亲和、Pod亲和、污点同时存在时调度器是怎么判断的在过滤阶段调度器会校验所有硬性条件先看Node上的污点Pod有没有对应的容忍没有容忍且effect是NoSchedule或NoExecute直接淘汰。再看节点亲和里的requiredDuringSchedulingNode标签是否匹配不匹配直接淘汰。再看Pod亲和/反亲和里的requiredDuringScheduling目标Node上的Pod是否满足条件不满足直接淘汰。过滤完之后进入打分阶段。此时preferredDuringSchedulingIgnoredDuringExecution的各项规则开始发挥作用加上权重分最终选出一个得分最高的Node。这个顺序是Kubernetes调度器内置的逻辑你不需要去改它但理解它很重要。因为排查调度问题时你得知道哪个环节卡住了是污点不让进还是亲和条件不满足还是只是分数不够高、被别的Node比下去了判断方法就是挨个看先看Taints再看亲和条件最后看调度结果里有没有Pending的Pod以及事件信息。6.3 Operator的Equal和Exists到底怎么选配置容忍时operator字段有两种取值Equal和Exists。它们的使用场景不同Equal需要同时匹配key、value和effect精确控制。适用于只有一个特定污点要被容忍的场景。Exists只匹配key和effect不关心value。适用于污点的value会变化或者你根本不关心value的场景。一个常见的组合是系统组件DaemonSet比如日志收集器、监控Agent。它们要跑遍集群所有节点包括那些打了各种污点的节点。这个时候用operator: Exists加不指定effect就可以容忍所有污点tolerations: - operator: Exists我见过不少同学在这个地方纠结半天总觉得“不写全是不是不严谨”实际上对于需要全节点覆盖的系统组件这种写法恰恰是官方推荐的方式。容忍本来就是“放行”的意思表达“我不在乎这个Node上有什么限制”是最直接的理解。但如果你的Pod要部署到生产环境且对安全性有要求我建议还是尽量把容忍范围收紧只容忍确切的key和effect。因为容忍了所有污点意味着将来如果有人给某台有故障隐患的节点打了NoExecute污点想驱逐Pod你的Pod也会无条件留下或者被驱逐失去了一层保护。7. 调度排障Pod一直Pending时怎么定位是哪一环节卡住了7.1 排障第一步看事件Pod一直Pending不调度是实际运维中最常见的问题。定位的第一步永远是看Pod的事件kubectl describe pod pod-name如果调度器拒绝了调度事件区域会给出非常明确的提示。常见的错误消息无非这几种0/5 nodes are available: 5 node(s) didnt match Pods node affinity——节点亲和条件不满足0/5 nodes are available: 1 node(s) had taint {db: dedicated}, that the pod didnt tolerate——节点上有污点Pod没容忍0/5 nodes are available: 2 node(s) didnt match pod anti-affinity rules——Pod反亲和不满足这些消息基本上把方向指得很清楚了。你只需要根据提示去检查对应的配置。有一个技巧如果你不确定到底是哪台节点上有什么样的污点可以一次性看所有节点的污点标签kubectl get nodes -o custom-columnsNAME:.metadata.name,TAINTS:.spec.taints[*].key,TAINTS_VALUE:.spec.taints[*].value,TAINTS_EFFECT:.spec.taints[*].effect这个命令会以表格形式把所有节点的污点列出来比一个个describe方便得多。7.2 排障第二步核对标签和拓扑域如果事件提示亲和条件不满足你得核对几个信息查看节点标签kubectl get nodes --show-labels检查某个特定节点上的标签kubectl describe node node-name | grep Labels然后跟Pod里的nodeSelectorTerms逐条对照。最容易踩的坑是标签名称里带了空格、大小写不一致、或者namespace没对上导致Pod亲和匹配不到目标Pod。还有一点Pod亲和匹配的是“目标Pod所在的节点”而不是“目标Pod本身”。如果你的目标Pod因为某种原因还没调度成功那等它的Pod亲和根节点也会一直Pending——一个等一个形成死锁。排查时要看看被依赖的Pod是不是已经正常运行了。7.3 排查调度时一个容易忽略的问题节点资源不足有些时候事件里没有任何亲和或污点相关的错误Pod还是Pending这时候百分之九十九是资源不足。调度器给出的错误一般是Insufficient cpuInsufficient memoryInsufficient ephemeral-storage这个问题经常跟亲和规则一起出现。比如你配置了节点亲和到某个特定标签的节点而这些节点的资源已经被占满了那Pod就会一直Pending。不要以为是亲和配置写错了先看看目标节点的资源余量。kubectl describe node node-name | grep -A5 Allocated resources这里能看到已分配资源量和实际可分配量。如果发现节点上CPU或内存已经满了要么扩容要么调整亲和规则让Pod能去别的节点。7.4 一些我自己记住的实用排查路径我平时排查调度问题时习惯按这个顺序走先看Pod事件区分是调度失败还是镜像拉取失败。如果是调度失败看错误消息是资源、污点还是亲和。如果是污点列出所有节点的污点情况对照Pod的容忍配置。如果是亲和核对节点标签、Pod标签、topologyKey、namespace。如果一切配置都正确但仍然一直Pending把Pod的YAML拉出来重新读一遍经常能发现是nodeSelectorTerms的OR/AND逻辑理解错了。最后看一眼节点的Allocated resources排除资源问题。这套流程基本能解决九成以上的调度排障问题。8. 关于调度的几个理念和我的体会做Kubernetes调度配置做得多了我有一个很深的感受调度规则的设计本质上是把“部署意图”从“人的大脑”里翻译成“机器能理解的配置”。而这个过程最大的困难不是学会某个字段怎么填而是想清楚你到底要什么。比如“让API服务和Redis待在一起”这句话听起来很明确但细想问题很多是所有API副本都要跟Redis在一起吗如果Redis有多副本是任意一个副本同机就行还是必须跟某个特定副本同机如果Redis所在的机器满了API是愿意等还是愿意跑远一点这些问题没有想清楚配置写出来一定会有隐藏问题。我还有一个经验能用软规则的时候尽量别用硬规则。硬规则写起来很爽但一旦集群环境发生变化比如某类节点下线、某些机器标签变更硬规则会让Pod找不到去处直接Pending。而软规则虽然“有时候不按你想的来”但至少不会把调度彻底卡死。在高可用优先的设计里我倾向于用软亲和、软反亲和把硬规则留给那些真正需要强保证的场景。最后说一个小技巧。调试亲和配置时不一定要动用真实的业务Pod可以用一个一次性测试Pod快速验证调度规则kubectl run test-pod --imagebusybox --restartNever --command -- sleep 3600然后kubectl edit pod test-pod临时加上亲和配置保存观察它是否被调度到你预期的节点确认后再把配置复制到正式的Deployment里。这个方法试错成本极低比反复修改Deployment再重发要高效得多。Kubernetes的调度体系内容很深这篇文章覆盖的是最核心的三个维度节点亲和/反亲和、Pod亲和/反亲和、污点与容忍。把这三块吃透再去看调度器的高级特性——优先级、抢占、拓扑分布约束——就会顺畅很多。因为它们本质上遵循的是同一套思想明确意图用标签描述世界用规则约束行为然后让调度器去执行。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑