资讯详情

Kubernetes调度实操:用亲和性、污点与容忍控制Pod位置

📅 2026/10/21 4:33:09 | 华诺云谱 👁 阅读
Kubernetes调度实操:用亲和性、污点与容忍控制Pod位置
1. 为什么需要手动指定Pod的“住处”做运维的多多少少都会被后端同学问过这样一个问题为什么我的Pod跑到了那台机器上明明我想让它去另一台。默认情况下kube-scheduler会根据节点的资源水位、污点、亲和性等条件自动为Pod选择节点。大多数时候这套自动调度足够好用。但总有一些场景你必须主动介入Pod需要跑在挂了GPU的节点上、两个服务需要尽量放在同一台机器减少网络延迟、关键业务不希望和别的业务挤在一起、新买的节点要上线但还不允许调度Pod上去。这些需求靠默认调度策略是没法满足的。这篇是kubernetes调度实操系列的第11篇就把nodeName、nodeSelector、节点亲和性、Pod亲和性、污点、容忍、节点删除这几个手段一次性讲透。每个方案我都会按“是什么、怎么用、什么时候选它、有什么坑”来拆尽量把底层逻辑讲明白附带可以直接抄作业的YAML和命令。需要说明的是后面所有示例都以kubernetes 1.28左右版本为基准更早或更新的版本在字段细节上略有差异但整体思路完全一致。1.1 默认调度器做了什么在介入之前先快速过一遍调度器的工作流程。kube-scheduler本质上是个“筛选打分”的过程。筛选阶段它会过滤掉不满足硬性条件的节点比如资源不足、端口冲突、节点不Ready、不满足nodeSelector等。打分阶段它会对通过筛选的节点按一系列优先级策略打分比如资源最紧张的节点分数更低、尽量把同一副本集的Pod分散到不同节点等。最终分数最高的节点会被选中由scheduler通知API Server把Pod绑定到那个节点上。整个过程是异步的所以你在创建Pod之后会看到Pending状态那是调度器还没做完筛选和打分。理解这个流程对你后续排错非常关键。比如一个Pod一直Pending你要先判断是筛选阶段把所有节点都过滤掉了还是打分阶段出了问题。前者常见于nodeSelector、污点、资源不足后者则比较少见通常是调度器的策略配置异常。1.2 什么时候需要干预调度我根据自己的经验整理了下面这些需要手动干预的场景硬件差异明显部分节点有GPU、有SSD、有更好的CPU型号业务必须跑在这些特定节点上。数据本地化某些应用依赖宿主机上的缓存文件或本地磁盘Pod必须固定在特定节点比如使用local PV的场景。故障隔离和韧性希望关键服务的多个副本尽量分布在不同节点避免一台机器故障导致整个服务一起挂掉。独占资源某些节点是给数据库专用、给大数据任务专用的普通业务不允许调度上去。节点运维维护要升级内核、换硬件、裁撤机器需要先把节点上的Pod全部赶走再把节点从集群里摘除。这五类场景正是这篇文章要讲的六种手段各自擅长解决的。下面先从最简单的两种说起。2. 最直接的两种指定方式2.1 nodeName一句话把Pod钉死到某台节点nodeName是调度控制里最简单粗暴的手段。直接在Pod的spec里写上节点名调度器看到这个字段后直接跳过调度算法把Pod绑定到你写的那台节点上。apiVersion: v1 kind: Pod metadata: name: nginx-node-name spec: nodeName: node-01 containers: - name: nginx image: nginx:1.25写成这样Pod会被直接调度到node-01。哪怕node-01资源已经快满了、哪怕node-01有污点也不影响它被绑定。因为调度器在看到nodeName时就直接走人了根本不参与决策。这个字段通常只在测试环境或紧急排障时用。比如某个节点上有个容器网络异常你想直接在机器上起一个调试Pod不经过调度器折腾nodeName就很好用。再比如说你明确知道某台机器上有一块特定的本地SSD想跑个临时任务读那块盘的数据也可以这样固定。但如果你在正式环境滥用nodeName坑是非常明显的节点宕机了Pod不会自动迁移到其他节点。节点名写错了Pod会一直Pending错误信息是0/1 nodes are available很容易误导排查方向。如果节点上有污点而你又加了nodeName虽然调度器不检查污点但kubelet在启动Pod时会检查容忍。没有对应容忍的话Pod会一直卡在Pending或ContainerCreating状态。这一点很多人踩过后面污点部分会详细讲。总结一句话nodeName适合临时调试、不适合生产调度。生产环境要固定Pod到某节点更推荐用nodeSelector或节点亲和性。2.2 nodeSelector按标签挑选节点nodeSelector是比nodeName“柔性”得多的方案。原理非常简单先给节点打标签然后在Pod里声明需要哪些标签调度器筛选阶段就会只看带这些标签的节点。先给节点打上标签kubectl label nodes node-02 disktypessd kubectl label nodes node-03 disktypessd然后在Pod或Deployment里指定nodeSelectorapiVersion: apps/v1 kind: Deployment metadata: name: nginx-selector spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: nodeSelector: disktype: ssd containers: - name: nginx image: nginx:1.25这样三个副本只会调度到打了disktypessd标签的节点上。如果集群里没有节点满足这个标签Pod就Pending如果新加了一个也带这个标签的节点新Pod也会自动调度过去不需要改动任何配置。你还可以给节点打多个标签Pod的nodeSelector里写多个键值对这就相当于“与”逻辑所有条件都得满足spec: nodeSelector: disktype: ssd gpu: present这种情况下调度器只会在同时满足这两个标签的节点里选。nodeSelector的限制也比较明显它只能做“等值匹配”不能表达“不在某某节点上”“只要group A或group B的节点”这类复杂逻辑。而且它属于硬性条件一旦没有节点满足Pod就永远Pending不会退而求其次。需要更灵活的表达就要用到亲和性了。2.3 两者的对比与选型建议简单列个表方便你直观对比对比项nodeNamenodeSelector指定方式直接写节点名通过节点标签匹配调度器是否参与不参与直接绑定参与筛选指定标签节点节点不可用时行为Pod不会迁移卡死不满足标签则Pending节点恢复后自动调度适用场景临时调试、紧急排查按硬件类型、环境类型分组调度被污点拦截的可能性会kubelet层仍会校验容忍会调度器层就拦截表达灵活性极低低只能等值匹配我的建议是能用nodeSelector解决的问题不要用nodeName。等值匹配已经能覆盖大部分“按硬件类型分机器”的场景。只有当你需要“更灵活的逻辑”或者“软性偏好”时才值得继续往下看亲和性。3. 亲和性让调度变得更有弹性亲和性是对nodeSelector的升级分为节点亲和性和Pod亲和性两类。节点亲和性解决的是“Pod想往哪些节点上跑”的问题。Pod亲和性解决的是“Pod想和哪些Pod待在一起”的问题反亲和性则表达“不想和哪些Pod待在一起”。3.1 nodeAffinity比nodeSelector灵活得多节点亲和性的核心语法是spec.affinity.nodeAffinity里面有两大策略块requiredDuringSchedulingIgnoredDuringExecution硬性要求筛选阶段不满足就直接不调度。preferredDuringSchedulingIgnoredDuringExecution软性偏好调度器会优先考虑但没有满足也没关系会选其他节点。后面的IgnoredDuringExecution意思是节点标签在Pod运行期间发生变化不影响已经运行在节点上的Pod。这个设计很关键因为它把调度时的检查和执行时的检查解耦了避免运行中的Pod因为节点标签变动而被动驱逐。先看一个硬性节点亲和性的例子spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: disktype operator: In values: - ssd - nvme这段表达的意思是Pod必须调度到disktype标签值为ssd或nvme的节点上。注意这时nodeSelectorTerms是一个数组多个term之间是“或”的关系只要满足任意一个term就行。而每个term里的matchExpressions之间是“与”的关系必须全部满足。再看软性偏好的例子spec: affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 80 preference: matchExpressions: - key: region operator: In values: - east - weight: 20 preference: matchExpressions: - key: burst operator: Exists这里每个preference都配了一个weight取值1到100。调度器会给满足条件的节点加相应权重分最终把Pod放到得分最高的节点上。注意多个preferred项是叠加起来的你可以在“尽量在east区域”的同时“尽量在有burst标签的节点上”。还有个非常有用的operatorDoesNotExist可以表达“某个标签不存在的节点”matchExpressions: - key: dedicated operator: DoesNotExist这样Pod就会避开所有打了dedicated标签的节点常用于避免跑到别人独占的机器上。operator支持值一览In、NotIn、Exists、DoesNotExist、Gt、Lt。其中Gt和Lt只适用于整数类型的标签值比如cpu-count大于8的节点。3.2 podAffinity与podAntiAffinity让Pod之间“抱团”或“散开”Pod亲和性解决的是Pod和Pod之间的位置关系。它的语法块和节点亲和性不同因为除了用标签选择Pod之外还多了一个topologyKey用来定义“在什么范围内算在一起”。先看Pod亲和性的简单例子。假设你有一个前端Deployment想让它的副本和后端Deployment的副本尽量落在同一台节点上降低跨节点访问延迟spec: affinity: podAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 80 podAffinityTerm: labelSelector: matchLabels: app: backend topologyKey: kubernetes.io/hostname这段的意思是如果集群里有别名叫appbackend的Pod调度器会给这些Pod所在的节点加权重分让当前Pod优先往那边放。topologyKey取kubernetes.io/hostname表示“同一台节点才算在一起”。反亲和性的经典案例是让同一个Deployment的副本尽量分散避免复制到一起一台机器挂掉服务就全挂spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: app: backend topologyKey: kubernetes.io/hostname注意如果这个Deployment的副本数大于集群节点数硬性反亲和会直接导致多余的副本Pending因为没有更多节点可以放了。所以生产上如果副本数比较多我更推荐用软性反亲和让调度器尽量分散但不阻断调度spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 50 podAffinityTerm: labelSelector: matchLabels: app: backend topologyKey: kubernetes.io/hostnametopologyKey最常用的是kubernetes.io/hostname和topology.kubernetes.io/zone前者控制同一节点维度后者控制同一可用区维度。按可用区反亲和可以保证服务跨机房分布容灾能力更强。3.3 实操案例让特定业务“抱团”我来写一个比较贴近生产的组合案例。假设有这样一个需求Web服务需要和Redis服务放在同一个可用区以减少跨可用区网络延迟同时Web服务的多个副本要尽量分散到不同节点。apiVersion: apps/v1 kind: Deployment metadata: name: web spec: replicas: 3 selector: matchLabels: app: web template: metadata: labels: app: web spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - redis topologyKey: topology.kubernetes.io/zone podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 80 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - web topologyKey: kubernetes.io/hostname containers: - name: web image: nginx:1.25这样Web副本必须和有appredis标签的Pod处于同一个可用区同时尽力分散到不同节点上。硬性亲和用在“必须和Redis同区域”这个业务约束上软性反亲和用在“尽量分散”这个韧性要求上。一个YAML把两种语义结合起来这就是实战里最常用的写法。有个容易忽略的点Pod亲和性的labelSelector默认只匹配同命名空间下的Pod。跨命名空间的亲和选择需要在labelSelector里额外指定namespaces字段。但跨命名空间Pod亲和会让调度器的过滤范围变大性能有所下降生产中建议慎用优先考虑用节点亲和性节点标签间接实现。4. 污点与容忍谁能上谁不能上如果说亲和性解决的是“Pod想往哪跑”那污点解决的就是“节点不想让谁上来”。这是一个更主动的隔离机制。节点可以打上一个或多个污点。Pod如果对这个污点没有对应的容忍调度器就不会把Pod放到这个节点上。已经运行在节点上的Pod如果节点新增了NoExecute污点还会被驱逐。4.1 污点的三种效果给节点打污点的命令是kubectl taint nodes node-gpu dedicatedgpu:NoSchedule污点的格式是keyvalue:effect。value部分可以省略比如kubectl taint nodes node-gpu dedicated:NoScheduleeffect有三种取值效果行为典型场景NoSchedule不允许调度已运行的Pod不受影响新节点上线先不接业务PreferNoSchedule尽量不调度但没有其他节点时也会调度优先避免但不强制的软隔离NoExecute不允许调度 驱逐已有Pod节点要下线维护立刻清空PodNoExecute还支持一个可选的tolerationSeconds参数用于给被驱逐Pod一个宽限期。比如节点上打了一个maintenanceinprogress:NoExecute的污点Pod的容忍里设置了tolerationSeconds: 60那Pod最多在节点上待60秒就会被驱逐。这个机制在大规模节点维护时非常有用可以优雅地分批迁移Pod。4.2 容忍的配置与匹配规则在Pod的spec里配置容忍语法如下spec: tolerations: - key: dedicated operator: Equal value: gpu effect: NoScheduleoperator有两种取值Equal和Exists。Equal表示key、value、effect都要匹配Exists表示只要污点的key存在即可不关心value。比如tolerations: - key: dedicated operator: Exists effect: NoSchedule这段表示“只要节点有dedicated这个key的NoSchedule污点不管value是什么我都容忍”。还有一种更粗暴的容忍容忍所有污点tolerations: - operator: Exists写这样一段Pod可以容忍节点上的任何污点包括key、value、effect都忽略。这个配置在调试阶段很有用但在生产环境建议少用等于把自己暴露在所有节点上很容易把Pod调度到不该去的地方。容忍的匹配有几点要特别注意容忍和污点需要同时满足key、value、effect才算匹配。effect不匹配就算key和value一样Pod也容忍不了。如果节点上有多个污点Pod必须对每个污点都有对应的容忍才能调度上去。有一个不匹配就不允许调度。只给Pod加容忍、节点上没有污点调度完全不受影响容忍没有副作用。即使Pod容忍了污点调度器还会结合其他条件综合判断不意味着一定能调度到这个节点。额外补充一下集群初始化时控制平面节点会自动带上一个node-role.kubernetes.io/control-plane:NoSchedule污点普通业务Pod默认不会调度到控制平面节点上。这不是什么都没配而是默认就有这层隔离。如果你看到业务Pod跑到了master节点上就要查一下是不是有谁加了全局容忍或者手动删掉了这个污点。4.3 大场景实战给GPU节点加污点我来还原一个真实场景。某公司购买了一批带GPU的节点希望这些节点的算力只给AI推理服务使用普通Web服务不能“蹭”上去。完整操作分三步第一步给GPU节点打上标签和污点kubectl label nodes node-gpu-01 acceleratornvidia kubectl taint nodes node-gpu-01 dedicatedgpu:NoSchedule第二步给AI推理服务加nodeSelector和容忍spec: nodeSelector: accelerator: nvidia tolerations: - key: dedicated operator: Equal value: gpu effect: NoSchedule containers: - name: inference image: inference-image:latest resources: limits: nvidia.com/gpu: 1第三步验证效果。普通Deployment创建出来的Pod不会调度到GPU节点上因为GPU节点有污点且普通Pod没有容忍。只有带容忍的AI推理Pod能上去。这个组合拳应该是最常见的高优资源隔离方案。关键点在于污点用来“拦住普通人”nodeSelector用来“把目标用户导进来”二者缺一不可。我还试过只用污点、不用nodeSelector的方案结果AI推理Pod确实能调度上去但同时任何加了全局容忍的Pod也能上去隔离效果大打折扣。反过来只用nodeSelector、不用污点的方案等价于你不设门禁但在地上画了个圈普通Pod虽然大概率不会来但也完全不排除误入的可能。5. 删除节点与驱逐实操5.1 节点下线的完整流程删除节点是运维里的常规操作比如机器要退役、硬件故障需要更换。但“删除”和“拔电源”之间差着好几个安全步骤。我总结的标准流程是排水drain - 删除节点对象 - 物理机下线。第一步先排空节点上的Pod。kubectl drain会给节点打上node.kubernetes.io/unschedulable污点也就是NoSchedule效果阻止新Pod调度上来然后把节点上已有的Pod驱逐到其他节点。kubectl drain node-05 --ignore-daemonsets --delete-emptydir-data这里有三个参数要特别留意--ignore-daemonsets忽略DaemonSet管理的Pod。如果不加这个参数drain会卡在DaemonSet Pod上因为这些Pod是注定要跑在每台节点上的驱逐了也会被调度回来drain会一直等待表现成“卡死”。--delete-emptydir-data允许删除使用emptyDir的Pod。emptyDir是临时存储驱逐时数据会丢。如果不想删可以不写这个参数但那些Pod会一直阻止drain完成。--force当Pod不受Deployment等控制器管理也就是裸Pod时需要加force参数才能驱逐。裸Pod被驱逐后不会在其他节点自动重建所以生产上裸Pod本身就该尽量避免。第二步确认节点已经排空后删除节点对象kubectl delete node node-05执行之后节点对象从集群中被删除。如果你用的是云托管集群云平台会在这之后自动创建一台新节点接入集群。如果你是自建集群那么节点对象删除后意味着集群彻底不管理这台机器了你可以安全地关机下架。5.2 驱逐过程中Pod如何“搬家”当drain对Pod发起驱逐时遵循的是graceful termination流程kubelet会调用容器运行时去停掉容器等待Pod里的容器进程优雅退出默认等待时间是30秒过了宽限期就强制SIGKILL。被驱逐的Pod如果由Deployment管理控制器会立刻在别的节点上创建出新Pod替换掉旧的。如果由StatefulSet管理虽然也会重新调度但因为有稳定的网络标识和存储卷绑定新Pod只会调度到同一份PVC可用的节点上调度约束比Deployment严格得多。这里有一个很多初级运维没意识到的问题如果Pod使用了local PV本地持久化卷驱逐后新Pod无法在其他节点挂载同一块本地盘就会一直Pending。drain之前你需要提前把这类有状态应用处理掉比如先手动缩容副本、或者确认业务本身允许数据丢失迁移。我还遇到过这样的场景一个节点上同时运行着几十个Pod其中大部分属于Deployment但有几个是离线任务Pod容器内部有复杂的清理逻辑。drain时由于宽限期默认30秒不够任务被强杀状态没有正常上报。后来我的办法是针对这类Pod单独设置更长的terminationGracePeriodSeconds并在drain时用--timeout参数控制整体等待时长让驱逐过程不是一刀切。5.3 节点删除后的状态异常排查节点删除操作本身并不复杂但删除之后经常会冒出各种关联问题。我列几个真实踩过的某次大规模裁撤节点后集群里突然出现了一批Pending状态的Pod。查了才发现这些Pod之间配置了硬性Pod反亲和性要求相同标签的副本不能在同一节点。集群节点总量减少后可用的“分散空间”变少导致部分副本没法找到一个不和已有副本在同一节点的位置。磁盘容量够、节点也Ready但就是Pending。解决方法是对这类应用改用软性反亲和。还有一次某个节点被delete后原来运行在其上的metrics-agent容器被重新调度到了控制平面节点。这说明有人曾经往控制平面节点上加了全局容忍。控制平面节点在初始化时默认带NoSchedule污点本意就是隔离业务Pod。出现这种问题说明要么有人改了污点要么某个命名空间里写了一个极其宽泛的容忍配置。排查方式是逐段分析Pod的tolerations字段并检查节点的taints字段看是谁绕过隔离。再补充一个实用命令如果你想知道某个Pod为什么调度到某节点上可以看它的spec.nodeName和事件记录kubectl describe pod pod-name事件里会有调度器对每个节点的评估结果包括不满足哪些条件、为什么被过滤掉。这是排查调度类问题最直接的入口。6. 组合拳设计与常见问题6.1 一个生产级调度方案长什么样单独的一个调度字段往往解决不了所有问题实际生产环境里他们会组合起来用。拿一个典型的“多类型节点集群”来说节点分为三种GPU型、SSD型、普通型。GPU节点打上hardwaregpu标签和dedicatedgpu:NoSchedule污点只允许AI推理Pod上去。AI推理Pod配置nodeSelector选择hardwaregpu配置容忍接受dedicatedgpu的NoSchedule再加一条软性反亲和尽量不和其他推理Pod挤在同一台机器。SSD节点打上disktypessd标签数据库类Pod用节点亲和性硬性匹配SSD节点同时用软性反亲和让两个数据库副本尽量分布到不同SSD节点。普通节点保持原样承担大部分无状态业务。所有控制平面节点保留默认的NoSchedule污点普通业务Pod绝对不允许上去。把这些组合起来就是一个可以落地的调度治理方案。你可以根据自己集群的规模继续演进比如增加可用区维度的反亲和、增加按CPU架构选择节点的亲和性等。6.2 常见问题速查表现象可能原因排查思路Pod一直Pending事件显示0/1 nodes available节点上有污点且Pod没有容忍或nodeSelector/亲和性不满足查看节点taints、Pod的nodeSelector和tolerations结合事件定位哪一步过滤掉加了nodeName的Pod在节点健康时Pending节点名写错或节点存在污点且Pod无容忍kubectl get node确认名称describe Pod看事件drain一直卡住不结束DaemonSet Pod在阻止、或存在裸Pod/emptyDir Pod加--ignore-daemonsets和--delete-emptydir-data必要时加--force删除节点后部分Pod迟迟无法重新调度硬性反亲和导致可选节点不足、或依赖local PV检查Pod事件、PVC的调度约束考虑放宽为软性反亲和新增节点后Pod没有自动调度上去节点没有对应标签、或节点处于unschedulable状态检查节点标签和污点kubectl describe node确认业务Pod跑到控制平面节点控制平面默认污点被删除、或Pod里有全局容忍检查节点taints和Pod的tolerations补回NoSchedule污点设置了podAntiAffinity硬性规则后副本无法扩容节点数量不足以满足副本分散要求改用preferred反亲和或在更粗的topologyKey维度上做反亲和6.3 调度权限与性能的几条经验调度器的过滤是有成本的。Pod亲和性和反亲和性在调度时会去查询其他Pod的分布情况本质上是一次跨节点的“搜索”。集群规模越大这个查询越重。如果某个集群Pod数量超过几千尽量少用对全量Pod生效的podAffinity改用节点亲和性配合节点标签来达到类似效果性能会好很多。另外给节点打标签时的命名规范值得统一。建议约定一套前缀体系比如用node.kubernetes.io/开头作为系统级标签项目相关的用project-name/前缀避免不同团队之间因为标签命名冲突导致调度行为互相干扰。配置变更一定要走GitOps流程尤其像污点这种“影响面极大”的操作。我见过有人执行了一条kubectl taint nodes命令打错了nodeName结果把一台正常的业务节点变成了NoSchedule状态整个节点上的存量Pod虽然不受影响但所有新Pod全都无法调度上去业务感知到扩缩容异常才排查出来。后来我们规定涉及污点、节点标签的变更必须经过评审和审计留痕。最后说一个细节删除节点之前一定要确认这个节点上是否有PVC、是否被其他服务依赖。如果一个节点上挂着一个共享存储类型的PVCPod被驱逐后PVC会重新绑定到新节点这是没问题的。但如果PVC是local类型数据就永远留在原来那台机器的磁盘上了。这种有状态数据迁移必须提前备份或者让业务方确认可丢失千万不能拿着drain命令闷头清空。
📝

华诺云谱内容团队

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

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

你可能需要的服务

订阅华诺云谱资讯周报

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

↑