0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Kubernetes Pod调度策略原理与落地指南

马哥Linux运维 来源:马哥Linux运维 2026-02-27 11:08 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

一、概述

1.1 背景介绍

Pod调度是Kubernetes的核心机制之一,决定了Pod最终运行在哪个节点上。默认调度器kube-scheduler通过一系列预选(Filtering)和优选(Scoring)算法完成调度决策,但默认行为在生产环境中往往不够用。

实际场景中经常遇到的问题:数据库Pod被调度到了没有SSD的节点上,导致IO性能差;两个高负载服务的Pod被调度到同一个节点,互相抢资源;GPU节点上跑了一堆普通业务Pod,真正需要GPU的任务反而调度不上去。

这些问题都需要通过调度策略来解决。Kubernetes提供了nodeSelector、nodeAffinity、podAffinity/podAntiAffinity、taints/tolerations、topologySpreadConstraints等多种调度机制,本文逐一讲解并给出生产环境的配置方案。

1.2 技术特点

多层调度控制:从简单的nodeSelector到复杂的自定义调度器,提供不同粒度的调度控制能力

软硬约束结合:requiredDuringScheduling是硬约束(不满足就不调度),preferredDuringScheduling是软约束(尽量满足,不满足也能调度)

拓扑感知:topologySpreadConstraints支持按zone、node、rack等拓扑域分散Pod,实现跨故障域部署

抢占机制:PriorityClass支持高优先级Pod抢占低优先级Pod的资源

1.3 适用场景

场景一:将数据库、缓存等IO密集型Pod调度到SSD节点,计算密集型Pod调度到高CPU节点

场景二:同一服务的多个副本分散到不同节点/可用区,避免单点故障导致服务全部不可用

场景三:GPU、FPGA等特殊硬件资源的独占调度,防止普通Pod占用专用资源

场景四:多租户集群中,不同团队的Pod隔离到各自的节点池

1.4 环境要求

组件 版本要求 说明
Kubernetes 1.24+ topologySpreadConstraints在1.19 GA,PodSecurity在1.25 GA
kube-scheduler 与集群版本一致 自定义调度器需要单独部署
节点标签 提前规划 调度策略依赖节点标签,需要统一标签规范
metrics-server 0.6+ 资源感知调度需要metrics数据

二、详细步骤

2.1 准备工作

2.1.1 节点标签规划

调度策略的基础是节点标签,先把标签体系规划好:

# 查看现有节点标签
kubectl get nodes --show-labels

# 按硬件类型打标签
kubectl label node k8s-worker-01 disktype=ssd
kubectl label node k8s-worker-02 disktype=ssd
kubectl label node k8s-worker-03 disktype=hdd

# 按业务用途打标签
kubectl label node k8s-worker-01 workload-type=database
kubectl label node k8s-worker-02 workload-type=application
kubectl label node k8s-worker-03 workload-type=application

# 按可用区打标签(如果是多机房部署)
kubectl label node k8s-worker-01 topology.kubernetes.io/zone=zone-a
kubectl label node k8s-worker-02 topology.kubernetes.io/zone=zone-b
kubectl label node k8s-worker-03 topology.kubernetes.io/zone=zone-c

# GPU节点标签
kubectl label node k8s-gpu-01 accelerator=nvidia-tesla-v100
kubectl label node k8s-gpu-02 accelerator=nvidia-tesla-a100

# 验证标签
kubectl get nodes -L disktype,workload-type,topology.kubernetes.io/zone

注意:标签key的命名要有规范,建议用/格式,如company.com/team=backend。Kubernetes内置标签用kubernetes.io和k8s.io前缀,自定义标签不要用这两个前缀。

2.1.2 理解调度流程

kube-scheduler的调度流程分为两个阶段:

预选(Filtering):过滤掉不满足条件的节点,比如资源不足、nodeSelector不匹配、taint不容忍等

优选(Scoring):对通过预选的节点打分,选择得分最高的节点

# 查看scheduler的调度日志(需要提高日志级别)
# 修改/etc/kubernetes/manifests/kube-scheduler.yaml
# 在command中添加 --v=4
# 然后查看日志
kubectl logs -n kube-system kube-scheduler-k8s-master-01 --tail=50

2.1.3 调度策略优先级

多种调度策略同时存在时的生效顺序:

nodeName(最高优先级):直接指定节点名,跳过调度器

taints/tolerations:节点污点过滤,不容忍的Pod直接排除

nodeSelector:简单的标签匹配过滤

nodeAffinity:更灵活的节点亲和性规则

podAffinity/podAntiAffinity:Pod间的亲和/反亲和

topologySpreadConstraints:拓扑分散约束

资源请求:节点剩余资源是否满足Pod的requests

2.2 核心配置

2.2.1 nodeSelector(最简单的调度约束)

nodeSelector是最基础的调度方式,通过标签键值对匹配节点:

# 文件:nginx-nodeselector.yaml
apiVersion:apps/v1
kind:Deployment
metadata:
name:nginx-ssd
namespace:default
spec:
replicas:3
selector:
 matchLabels:
  app:nginx-ssd
template:
 metadata:
  labels:
   app:nginx-ssd
 spec:
  nodeSelector:
   disktype:ssd
  containers:
   -name:nginx
    image:nginx:1.24
    resources:
     requests:
      cpu:100m
      memory:128Mi
     limits:
      cpu:200m
      memory:256Mi
kubectl apply -f nginx-nodeselector.yaml

# 验证Pod只调度到了ssd节点
kubectl get pods -l app=nginx-ssd -o wide

注意:nodeSelector是硬约束,如果没有节点匹配标签,Pod会一直Pending。生产环境建议配合nodeAffinity的软约束使用。

2.2.2 nodeAffinity(节点亲和性)

nodeAffinity比nodeSelector更灵活,支持多种操作符和软硬约束:

# 文件:app-node-affinity.yaml
apiVersion:apps/v1
kind:Deployment
metadata:
name:app-with-affinity
namespace:default
spec:
replicas:6
selector:
 matchLabels:
  app:app-affinity
template:
 metadata:
  labels:
   app:app-affinity
 spec:
  affinity:
   nodeAffinity:
    # 硬约束:必须调度到zone-a或zone-b
    requiredDuringSchedulingIgnoredDuringExecution:
     nodeSelectorTerms:
      -matchExpressions:
        -key:topology.kubernetes.io/zone
         operator:In
         values:
          -zone-a
          -zone-b
    # 软约束:优先调度到ssd节点,权重1-100
    preferredDuringSchedulingIgnoredDuringExecution:
     -weight:80
      preference:
       matchExpressions:
        -key:disktype
         operator:In
         values:
          -ssd
     -weight:20
      preference:
       matchExpressions:
        -key:workload-type
         operator:In
         values:
          -application
  containers:
   -name:app
    image:nginx:1.24
    resources:
     requests:
      cpu:200m
      memory:256Mi

操作符说明

In:标签值在列表中

NotIn:标签值不在列表中

Exists:标签存在(不关心值)

DoesNotExist:标签不存在

Gt:标签值大于指定值(仅限数字)

Lt:标签值小于指定值(仅限数字)

注意:requiredDuringSchedulingIgnoredDuringExecution中的IgnoredDuringExecution表示Pod已经运行后,即使节点标签变了也不会驱逐Pod。Kubernetes计划实现RequiredDuringExecution但目前还没有。

2.2.3 podAffinity和podAntiAffinity(Pod间亲和/反亲和)

控制Pod之间的调度关系,典型场景:Web应用和缓存部署在同一节点减少网络延迟,同一服务的多个副本分散到不同节点。

# 文件:web-cache-affinity.yaml
apiVersion:apps/v1
kind:Deployment
metadata:
name:web-frontend
namespace:default
spec:
replicas:3
selector:
 matchLabels:
  app:web-frontend
template:
 metadata:
  labels:
   app:web-frontend
 spec:
  affinity:
   # Pod亲和:和redis-cache部署在同一节点
   podAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
     -weight:100
      podAffinityTerm:
       labelSelector:
        matchExpressions:
         -key:app
          operator:In
          values:
           -redis-cache
       topologyKey:kubernetes.io/hostname
   # Pod反亲和:同一服务的副本分散到不同节点
   podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
     -labelSelector:
       matchExpressions:
        -key:app
         operator:In
         values:
          -web-frontend
      topologyKey:kubernetes.io/hostname
  containers:
   -name:web
    image:nginx:1.24
    resources:
     requests:
      cpu:200m
      memory:256Mi

说明

topologyKey: kubernetes.io/hostname表示以节点为拓扑域,同一节点上的Pod视为同一拓扑域

topologyKey: topology.kubernetes.io/zone表示以可用区为拓扑域

podAntiAffinity的硬约束会限制副本数不能超过节点数,3个副本至少需要3个节点

警告:podAffinity/podAntiAffinity的计算复杂度是O(N^2),N是集群中的Pod数量。在Pod数量超过5000的大集群中,大量使用podAffinity会导致调度延迟从毫秒级上升到秒级。

2.2.4 Taints和Tolerations(污点和容忍)

Taints从节点角度排斥Pod,Tolerations从Pod角度容忍污点。两者配合实现节点专用化。

# 给GPU节点添加污点,只允许GPU任务调度
kubectl taint nodes k8s-gpu-01 gpu=true:NoSchedule
kubectl taint nodes k8s-gpu-02 gpu=true:NoSchedule

# 给维护中的节点添加NoExecute污点,驱逐现有Pod
kubectl taint nodes k8s-worker-03 maintenance=true:NoExecute

# 查看节点污点
kubectl describe node k8s-gpu-01 | grep -A 5 Taints

# 删除污点
kubectl taint nodes k8s-worker-03 maintenance=true:NoExecute-

Pod中配置Tolerations:

# 文件:gpu-job.yaml
apiVersion:batch/v1
kind:Job
metadata:
name:gpu-training-job
namespace:ml-training
spec:
template:
 spec:
  tolerations:
   # 容忍gpu污点
   -key:"gpu"
    operator:"Equal"
    value:"true"
    effect:"NoSchedule"
  nodeSelector:
   accelerator:nvidia-tesla-v100
  containers:
   -name:training
    image:pytorch/pytorch:2.0.1-cuda11.7-cudnn8-runtime
    resources:
     limits:
      nvidia.com/gpu:1
     requests:
      cpu:"4"
      memory:"16Gi"
  restartPolicy:Never

Taint Effect说明

NoSchedule:新Pod不会调度到该节点,已有Pod不受影响

PreferNoSchedule:尽量不调度,但资源不足时仍可调度

NoExecute:新Pod不调度,已有Pod如果不容忍会被驱逐。可以设置tolerationSeconds指定驱逐前的等待时间

# 容忍NoExecute污点,但最多等待300秒后被驱逐
tolerations:
-key:"maintenance"
 operator:"Equal"
 value:"true"
 effect:"NoExecute"
 tolerationSeconds:300

2.2.5 topologySpreadConstraints(拓扑分散约束)

1.19版本GA的功能,比podAntiAffinity更精细地控制Pod在拓扑域间的分布:

# 文件:app-topology-spread.yaml
apiVersion:apps/v1
kind:Deployment
metadata:
name:app-spread
namespace:default
spec:
replicas:9
selector:
 matchLabels:
  app:app-spread
template:
 metadata:
  labels:
   app:app-spread
 spec:
  topologySpreadConstraints:
   # 跨可用区均匀分布,最大偏差1
   -maxSkew:1
    topologyKey:topology.kubernetes.io/zone
    whenUnsatisfiable:DoNotSchedule
    labelSelector:
     matchLabels:
      app:app-spread
   # 跨节点均匀分布,最大偏差1,软约束
   -maxSkew:1
    topologyKey:kubernetes.io/hostname
    whenUnsatisfiable:ScheduleAnyway
    labelSelector:
     matchLabels:
      app:app-spread
  containers:
   -name:app
    image:nginx:1.24
    resources:
     requests:
      cpu:100m
      memory:128Mi

参数说明

maxSkew:拓扑域间Pod数量的最大差值。设为1表示任意两个域的Pod数量差不超过1

topologyKey:拓扑域的标签key

whenUnsatisfiable:不满足约束时的行为,DoNotSchedule(硬约束)或ScheduleAnyway(软约束)

9个副本在3个zone中的分布结果:zone-a=3, zone-b=3, zone-c=3。如果zone-c只有1个节点且资源不足,DoNotSchedule会导致部分Pod Pending,ScheduleAnyway则会尽量均匀但允许偏差。

2.2.6 PriorityClass(优先级和抢占)

高优先级Pod可以抢占低优先级Pod的资源:

# 定义优先级类
apiVersion:scheduling.k8s.io/v1
kind:PriorityClass
metadata:
name:critical-production
value:1000000
globalDefault:false
preemptionPolicy:PreemptLowerPriority
description:"生产核心服务,可抢占低优先级Pod"
---
apiVersion:scheduling.k8s.io/v1
kind:PriorityClass
metadata:
name:normal-production
value:500000
globalDefault:true
preemptionPolicy:PreemptLowerPriority
description:"普通生产服务"
---
apiVersion:scheduling.k8s.io/v1
kind:PriorityClass
metadata:
name:batch-job
value:100000
globalDefault:false
preemptionPolicy:Never
description:"批处理任务,不抢占其他Pod"

在Pod中引用:

apiVersion:apps/v1
kind:Deployment
metadata:
name:core-api
spec:
replicas:3
selector:
 matchLabels:
  app:core-api
template:
 metadata:
  labels:
   app:core-api
 spec:
  priorityClassName:critical-production
  containers:
   -name:api
    image:myapp:v1.0
    resources:
     requests:
      cpu:"1"
      memory:"2Gi"

警告:preemptionPolicy: PreemptLowerPriority会驱逐低优先级Pod来腾出资源,被驱逐的Pod会收到SIGTERM信号。确保应用能正确处理优雅关闭,否则会丢数据。生产环境建议批处理任务设置preemptionPolicy: Never。

2.3 启动和验证

2.3.1 验证调度结果

# 查看Pod调度到了哪个节点
kubectl get pods -o wide -l app=app-spread

# 查看Pod的调度事件
kubectl describe pod  | grep -A 10 Events

# 查看调度失败的原因
kubectl get events --field-selector reason=FailedScheduling -A

# 查看节点资源分配情况
kubectl describe node k8s-worker-01 | grep -A 20"Allocated resources"

2.3.2 调度模拟测试

# 使用kubectl创建一个dry-run的Pod,查看是否能调度成功
kubectl runtest-schedule --image=nginx:1.24 --dry-run=server -o yaml 
 --overrides='{
  "spec": {
   "nodeSelector": {"disktype": "ssd"},
   "containers": [{"name": "test", "image": "nginx:1.24", "resources": {"requests": {"cpu": "100m", "memory": "128Mi"}}}]
  }
 }'

# 查看各节点的可分配资源
kubectl get nodes -o custom-columns=
NAME:.metadata.name,
CPU_ALLOC:.status.allocatable.cpu,
MEM_ALLOC:.status.allocatable.memory,
PODS_ALLOC:.status.allocatable.pods

2.3.3 验证拓扑分散

# 查看Pod在各zone的分布
kubectl get pods -l app=app-spread -o custom-columns=
NAME:.metadata.name,
NODE:.spec.nodeName,
ZONE:.spec.nodeName

# 更精确的方式:通过节点标签查看
forpodin$(kubectl get pods -l app=app-spread -o jsonpath='{.items[*].spec.nodeName}');do
 zone=$(kubectl get node$pod-o jsonpath='{.metadata.labels.topology.kubernetes.io/zone}')
echo"Node:$pod, Zone:$zone"
done

三、示例代码和配置

3.1 完整配置示例

3.1.1 多租户节点池隔离方案

生产环境中不同团队共用一个集群,通过taint+nodeAffinity实现节点池隔离:

# 文件:namespace-resource-setup.yaml
# 第一步:创建团队namespace
apiVersion:v1
kind:Namespace
metadata:
name:team-backend
labels:
 team:backend
---
apiVersion:v1
kind:Namespace
metadata:
name:team-data
labels:
 team:data
---
# 第二步:为每个团队设置ResourceQuota
apiVersion:v1
kind:ResourceQuota
metadata:
name:backend-quota
namespace:team-backend
spec:
hard:
 requests.cpu:"20"
 requests.memory:40Gi
 limits.cpu:"40"
 limits.memory:80Gi
 pods:"100"
---
apiVersion:v1
kind:ResourceQuota
metadata:
name:data-quota
namespace:team-data
spec:
hard:
 requests.cpu:"40"
 requests.memory:80Gi
 limits.cpu:"80"
 limits.memory:160Gi
 pods:"200"

节点打标签和污点:

# backend团队节点池
kubectl label node k8s-worker-{01..05} node-pool=backend
kubectl taint nodes k8s-worker-{01..05} node-pool=backend:NoSchedule

# data团队节点池
kubectl label node k8s-worker-{06..10} node-pool=data
kubectl taint nodes k8s-worker-{06..10} node-pool=data:NoSchedule

# 公共节点池(不加污点,所有Pod都能调度)
kubectl label node k8s-worker-{11..15} node-pool=shared

团队Deployment模板:

# 文件:backend-app-template.yaml
apiVersion:apps/v1
kind:Deployment
metadata:
name:backend-api
namespace:team-backend
spec:
replicas:5
selector:
 matchLabels:
  app:backend-api
template:
 metadata:
  labels:
   app:backend-api
 spec:
  tolerations:
   -key:"node-pool"
    operator:"Equal"
    value:"backend"
    effect:"NoSchedule"
  affinity:
   nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
     nodeSelectorTerms:
      -matchExpressions:
        -key:node-pool
         operator:In
         values:
          -backend
          -shared
   podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
     -weight:100
      podAffinityTerm:
       labelSelector:
        matchExpressions:
         -key:app
          operator:In
          values:
           -backend-api
       topologyKey:kubernetes.io/hostname
  containers:
   -name:api
    image:backend-api:v2.1.0
    ports:
     -containerPort:8080
    resources:
     requests:
      cpu:500m
      memory:512Mi
     limits:
      cpu:"1"
      memory:1Gi
    readinessProbe:
     httpGet:
      path:/health
      port:8080
     initialDelaySeconds:10
     periodSeconds:5
    livenessProbe:
     httpGet:
      path:/health
      port:8080
     initialDelaySeconds:30
     periodSeconds:10

3.1.2 调度策略自动注入脚本

通过Kyverno策略自动为特定namespace的Pod注入调度规则,避免每个Deployment都手动配置:

# 文件:kyverno-scheduling-policy.yaml
apiVersion:kyverno.io/v1
kind:ClusterPolicy
metadata:
name:inject-node-affinity-backend
spec:
rules:
 -name:add-backend-scheduling
  match:
   any:
    -resources:
      kinds:
       -Pod
      namespaces:
       -team-backend
  mutate:
   patchStrategicMerge:
    spec:
     tolerations:
      -key:"node-pool"
       operator:"Equal"
       value:"backend"
       effect:"NoSchedule"
     affinity:
      nodeAffinity:
       requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
         -matchExpressions:
           -key:node-pool
            operator:In
            values:
             -backend
             -shared

注意:Kyverno需要单独安装(helm install kyverno kyverno/kyverno -n kyverno --create-namespace)。这种方式比在每个Deployment里写调度规则更易维护,团队只需要关注业务配置,调度策略由平台团队统一管理。

3.2 实际应用案例

案例一:数据库Pod的调度策略

场景描述:MySQL主从集群部署在K8s中,主库需要SSD+高内存节点,从库可以用普通节点。主从Pod不能在同一节点上,避免节点故障导致主从同时不可用。

实现代码

# 文件:mysql-master-statefulset.yaml
apiVersion:apps/v1
kind:StatefulSet
metadata:
name:mysql-master
namespace:database
spec:
serviceName:mysql-master
replicas:1
selector:
 matchLabels:
  app:mysql
  role:master
template:
 metadata:
  labels:
   app:mysql
   role:master
 spec:
  priorityClassName:critical-production
  affinity:
   nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
     nodeSelectorTerms:
      -matchExpressions:
        -key:disktype
         operator:In
         values:
          -ssd
        -key:workload-type
         operator:In
         values:
          -database
   podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
     -labelSelector:
       matchExpressions:
        -key:app
         operator:In
         values:
          -mysql
      topologyKey:kubernetes.io/hostname
  containers:
   -name:mysql
    image:mysql:8.0.35
    ports:
     -containerPort:3306
    env:
     -name:MYSQL_ROOT_PASSWORD
      valueFrom:
       secretKeyRef:
        name:mysql-secret
        key:root-password
    resources:
     requests:
      cpu:"2"
      memory:4Gi
     limits:
      cpu:"4"
      memory:8Gi
    volumeMounts:
     -name:mysql-data
      mountPath:/var/lib/mysql
volumeClaimTemplates:
 -metadata:
   name:mysql-data
  spec:
   accessModes:["ReadWriteOnce"]
   storageClassName:local-ssd
   resources:
    requests:
     storage:100Gi
---
# MySQL从库
apiVersion:apps/v1
kind:StatefulSet
metadata:
name:mysql-slave
namespace:database
spec:
serviceName:mysql-slave
replicas:2
selector:
 matchLabels:
  app:mysql
  role:slave
template:
 metadata:
  labels:
   app:mysql
   role:slave
 spec:
  priorityClassName:normal-production
  affinity:
   podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
     -labelSelector:
       matchExpressions:
        -key:app
         operator:In
         values:
          -mysql
      topologyKey:kubernetes.io/hostname
  topologySpreadConstraints:
   -maxSkew:1
    topologyKey:topology.kubernetes.io/zone
    whenUnsatisfiable:DoNotSchedule
    labelSelector:
     matchLabels:
      app:mysql
      role:slave
  containers:
   -name:mysql
    image:mysql:8.0.35
    ports:
     -containerPort:3306
    resources:
     requests:
      cpu:"1"
      memory:2Gi
     limits:
      cpu:"2"
      memory:4Gi
    volumeMounts:
     -name:mysql-data
      mountPath:/var/lib/mysql
volumeClaimTemplates:
 -metadata:
   name:mysql-data
  spec:
   accessModes:["ReadWriteOnce"]
   storageClassName:standard
   resources:
    requests:
     storage:100Gi

运行结果

NAME       READY  STATUS  NODE      ZONE
mysql-master-0  1/1   Running  k8s-worker-01  zone-a  (SSD+database节点)
mysql-slave-0  1/1   Running  k8s-worker-02  zone-b  (不同节点不同zone)
mysql-slave-1  1/1   Running  k8s-worker-03  zone-c  (不同节点不同zone)

案例二:混合调度策略实现灰度发布

场景描述:灰度发布时,新版本Pod先调度到特定的灰度节点,验证通过后再扩展到所有节点。通过标签和调度策略控制灰度范围。

实现步骤

给灰度节点打标签:

kubectl label node k8s-worker-01 canary=true

灰度版本Deployment:

# 文件:app-canary.yaml
apiVersion:apps/v1
kind:Deployment
metadata:
name:myapp-canary
namespace:production
spec:
replicas:2
selector:
 matchLabels:
  app:myapp
  version:canary
template:
 metadata:
  labels:
   app:myapp
   version:canary
 spec:
  affinity:
   nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
     nodeSelectorTerms:
      -matchExpressions:
        -key:canary
         operator:In
         values:
          -"true"
  containers:
   -name:myapp
    image:myapp:v2.0.0-rc1
    ports:
     -containerPort:8080
    resources:
     requests:
      cpu:200m
      memory:256Mi

灰度验证通过后,全量发布:

# 移除灰度节点约束,更新稳定版Deployment的镜像
kubectlsetimage deployment/myapp-stable myapp=myapp:v2.0.0 -n production

# 缩容灰度Deployment
kubectl scale deployment/myapp-canary --replicas=0 -n production

# 清理灰度标签
kubectl label node k8s-worker-01 canary-

四、最佳实践和注意事项

4.1 最佳实践

4.1.1 性能优化

减少podAffinity的使用范围:podAffinity/podAntiAffinity的调度计算复杂度高,在500+节点集群中,一个带podAffinity的Pod调度耗时从5ms增加到200ms。能用topologySpreadConstraints替代的场景优先用topologySpreadConstraints。

# 不推荐:用podAntiAffinity实现分散
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
 -labelSelector:
   matchLabels:
    app:myapp
  topologyKey:kubernetes.io/hostname

# 推荐:用topologySpreadConstraints替代
topologySpreadConstraints:
-maxSkew:1
 topologyKey:kubernetes.io/hostname
 whenUnsatisfiable:DoNotSchedule
 labelSelector:
  matchLabels:
   app:myapp

合理设置resource requests:调度器根据requests而非limits做调度决策。requests设太高导致节点利用率低(实测平均CPU利用率只有15%),设太低导致节点超卖严重,Pod被OOMKill。建议requests设为实际使用量的P95值。

# 查看Pod实际资源使用,作为requests设置参考
kubectl top pods -n production --sort-by=cpu
kubectl top pods -n production --sort-by=memory

使用Descheduler重平衡:节点扩容或Pod漂移后,集群负载可能不均衡。Descheduler可以驱逐不符合当前调度策略的Pod,让调度器重新调度。

# 安装Descheduler
helm repo add descheduler https://kubernetes-sigs.github.io/descheduler/
helm install descheduler descheduler/descheduler -n kube-system 
 --setschedule="*/5 * * * *"

4.1.2 安全加固

限制nodeName直接指定:nodeName会跳过调度器的所有检查(包括资源检查),生产环境通过RBAC限制普通用户使用nodeName字段。

# OPA/Gatekeeper策略:禁止使用nodeName
apiVersion:constraints.gatekeeper.sh/v1beta1
kind:K8sDenyNodeName
metadata:
name:deny-nodename
spec:
match:
 kinds:
  -apiGroups:[""]
   kinds:["Pod"]
 excludedNamespaces:["kube-system"]

PriorityClass权限控制:高优先级PriorityClass的创建和使用需要限制,防止普通用户创建高优先级Pod抢占核心服务资源。

# RBAC:只允许admin使用critical-production优先级
apiVersion:rbac.authorization.k8s.io/v1
kind:ClusterRole
metadata:
name:use-critical-priority
rules:
-apiGroups:["scheduling.k8s.io"]
 resources:["priorityclasses"]
 resourceNames:["critical-production"]
 verbs:["get","list"]

节点污点防篡改:关键节点(如GPU节点)的污点被误删会导致普通Pod涌入。通过准入控制webhook拦截对特定节点taint的修改操作。

4.1.3 高可用配置

HA方案一:核心服务至少3副本,配合podAntiAffinity硬约束分散到不同节点,再用topologySpreadConstraints分散到不同可用区

HA方案二:使用PodDisruptionBudget(PDB)限制同时不可用的Pod数量,防止节点维护时服务中断

apiVersion:policy/v1
kind:PodDisruptionBudget
metadata:
name:myapp-pdb
spec:
minAvailable:2
selector:
 matchLabels:
  app:myapp

备份策略:调度策略配置(PriorityClass、节点标签、污点)纳入GitOps管理,用ArgoCD或FluxCD同步

4.2 注意事项

4.2.1 配置注意事项

警告:调度策略配置错误可能导致Pod无法调度或调度到错误节点,修改前在测试环境验证。

注意nodeAffinity的requiredDuringSchedulingIgnoredDuringExecution中,多个nodeSelectorTerms之间是OR关系,同一个nodeSelectorTerm中的多个matchExpressions之间是AND关系。搞混了会导致调度结果不符合预期。

注意podAntiAffinity硬约束会限制副本数上限。如果topologyKey是kubernetes.io/hostname,副本数不能超过可用节点数,否则多出来的Pod永远Pending。

注意topologySpreadConstraints的labelSelector必须和Pod自身的标签匹配,否则约束不生效。这个错误很隐蔽,Pod能正常调度但分布不均匀。

4.2.2 常见错误

错误现象 原因分析 解决方案
Pod一直Pending,事件显示0/6 nodes are available nodeSelector或nodeAffinity没有匹配的节点 检查节点标签是否正确:kubectl get nodes --show-labels
Pod调度成功但分布不均匀 topologySpreadConstraints的labelSelector写错 确认labelSelector和Pod的labels完全一致
高优先级Pod抢占后,被抢占的Pod无法重新调度 集群资源不足,被抢占的Pod也找不到合适节点 扩容节点或降低resource requests
taint添加后已有Pod没被驱逐 使用了NoSchedule而非NoExecute NoSchedule只影响新Pod,要驱逐已有Pod用NoExecute
节点维护drain失败 Pod有PDB限制,minAvailable不满足 先扩容副本数,或临时调整PDB的minAvailable

4.2.3 兼容性问题

版本兼容:topologySpreadConstraints在1.18 Beta、1.19 GA;minDomains字段在1.25 Beta,使用前确认集群版本

平台兼容:云厂商托管K8s通常自动设置topology.kubernetes.io/zone标签,自建集群需要手动设置

组件依赖:Descheduler版本需要和K8s版本匹配,0.27.x支持K8s 1.25-1.28

五、故障排查和监控

5.1 故障排查

5.1.1 日志查看

# 查看kube-scheduler日志
kubectl logs -n kube-system -l component=kube-scheduler --tail=100

# 查看调度事件
kubectl get events -A --field-selector reason=FailedScheduling --sort-by='.lastTimestamp'

# 查看特定Pod的调度事件
kubectl describe pod  -n  | grep -A 20 Events

# 查看节点资源分配详情
kubectl describe node  | grep -A 30"Allocated resources"

5.1.2 常见问题排查

问题一:Pod Pending,报Insufficient cpu

# 诊断命令
kubectl describe pod  | grep -A 5"Events"
kubectl get nodes -o custom-columns=NAME:.metadata.name,CPU_REQ:.status.allocatable.cpu,MEM_REQ:.status.allocatable.memory

# 查看各节点已分配资源
fornodein$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}');do
echo"===$node==="
 kubectl describe node$node| grep -A 5"Allocated resources"
done

解决方案

检查Pod的resource requests是否设置过高

检查节点是否有足够的可分配资源(allocatable - 已分配)

考虑扩容节点或优化现有Pod的资源配置

问题二:调度策略不生效,Pod没有按预期分布

# 诊断命令
kubectl get pod  -o yaml | grep -A 50"affinity"
kubectl get pod  -o yaml | grep -A 20"topologySpreadConstraints"

# 检查节点标签是否正确
kubectl get nodes --show-labels | grep 

解决方案

确认YAML缩进正确,affinity字段层级关系容易写错

确认labelSelector和Pod标签一致

用kubectl apply --dry-run=server验证YAML语法

问题三:节点drain卡住不动

症状:kubectl drain命令长时间无响应

排查

# 查看哪些Pod阻止了drain
kubectl drain  --ignore-daemonsets --delete-emptydir-data --dry-run=client

# 检查PDB
kubectl get pdb -A

解决

DaemonSet的Pod加--ignore-daemonsets跳过

使用emptyDir的Pod加--delete-emptydir-data

PDB限制导致的,先扩容副本再drain

有Pod设置了terminationGracePeriodSeconds很长,加--timeout=300s限制等待时间

5.1.3 调试模式

# 提高scheduler日志级别
# 编辑/etc/kubernetes/manifests/kube-scheduler.yaml
# 在command中添加 --v=10(最详细,仅调试用)

# 查看scheduler的调度决策过程
kubectl logs -n kube-system kube-scheduler-k8s-master-01 | grep"pod-name"

# 使用kubectl-scheduler-simulator模拟调度(需要单独安装)
# https://github.com/kubernetes-sigs/kube-scheduler-simulator

# 查看scheduler的metrics
kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes

5.2 性能监控

5.2.1 关键指标监控

# 调度延迟
kubectl get --raw /metrics | grep scheduler_scheduling_algorithm_duration_seconds

# 调度队列长度
kubectl get --raw /metrics | grep scheduler_pending_pods

# 抢占次数
kubectl get --raw /metrics | grep scheduler_preemption_victims

# 节点资源使用率
kubectl top nodes

5.2.2 监控指标说明

指标名称 正常范围 告警阈值 说明
调度延迟(P99) <100ms >500ms 超过500ms说明调度器过载或策略过于复杂
Pending Pod数量 0 >10持续5分钟 持续有Pod无法调度需要排查
调度失败率 <1% >5% 高失败率说明资源不足或策略配置有问题
节点CPU分配率 40%-70% >85% 分配率过高会导致新Pod无法调度
节点内存分配率 50%-80% >90% 接近100%时需要扩容
抢占事件数 0 >5次/小时 频繁抢占说明资源规划不合理

5.2.3 Prometheus监控规则

# 文件:scheduler-alerts.yaml
apiVersion:monitoring.coreos.com/v1
kind:PrometheusRule
metadata:
name:scheduler-alerts
namespace:monitoring
spec:
groups:
 -name:kube-scheduler
  rules:
   -alert:SchedulerHighLatency
    expr:|
      histogram_quantile(0.99,
       sum(rate(scheduler_scheduling_algorithm_duration_seconds_bucket[5m])) by (le)
      ) > 0.5
    for:10m
    labels:
     severity:warning
    annotations:
     summary:"Scheduler P99 latency exceeds 500ms"

   -alert:PodsPendingTooLong
    expr:|
      sum(scheduler_pending_pods{queue="active"}) > 10
    for:5m
    labels:
     severity:warning
    annotations:
     summary:"More than 10 pods pending for over 5 minutes"

   -alert:SchedulerUnhealthy
    expr:absent(up{job="kube-scheduler"}==1)
    for:3m
    labels:
     severity:critical
    annotations:
     summary:"kube-scheduler is not running"

   -alert:NodeHighAllocation
    expr:|
      (1 - sum(kube_node_status_allocatable{resource="cpu"} - kube_pod_container_resource_requests{resource="cpu"}) by (node)
      / sum(kube_node_status_allocatable{resource="cpu"}) by (node)) > 0.85
    for:10m
    labels:
     severity:warning
    annotations:
     summary:"Node{{ $labels.node }}CPU allocation exceeds 85%"

   -alert:FrequentPreemption
    expr:|
      increase(scheduler_preemption_victims[1h]) > 5
    for:5m
    labels:
     severity:warning
    annotations:
     summary:"More than 5 preemption events in the last hour"

5.3 备份与恢复

5.3.1 备份策略

#!/bin/bash
# 调度策略配置备份脚本
# 文件:/opt/scripts/scheduling-config-backup.sh

set-euo pipefail

BACKUP_DIR="/data/scheduling-backup/$(date +%Y%m%d)"
mkdir -p"${BACKUP_DIR}"

# 备份PriorityClass
kubectl get priorityclass -o yaml >"${BACKUP_DIR}/priorityclasses.yaml"

# 备份PDB
kubectl get pdb -A -o yaml >"${BACKUP_DIR}/pdbs.yaml"

# 备份节点标签和污点
fornodein$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}');do
 kubectl get node"$node"-o jsonpath='{.metadata.labels}'>"${BACKUP_DIR}/${node}-labels.json"
 kubectl get node"$node"-o jsonpath='{.spec.taints}'>"${BACKUP_DIR}/${node}-taints.json"
done

# 备份Kyverno策略(如果使用)
kubectl get clusterpolicy -o yaml >"${BACKUP_DIR}/kyverno-policies.yaml"2>/dev/null ||true

echo"[$(date)] Scheduling config backup completed:${BACKUP_DIR}"

5.3.2 恢复流程

停止服务:暂停业务部署,避免恢复过程中的调度冲突

恢复数据:kubectl apply -f ${BACKUP_DIR}/priorityclasses.yaml

验证完整性:kubectl get priorityclass确认PriorityClass恢复

恢复节点配置:逐节点恢复标签和污点

验证调度:创建测试Pod验证调度策略是否生效

六、总结

6.1 技术要点回顾

要点一:nodeSelector适合简单场景,nodeAffinity适合需要软硬约束结合的场景,两者都基于节点标签,标签规划是基础

要点二:podAntiAffinity硬约束会限制副本数不超过节点数,大规模集群中计算开销大,优先用topologySpreadConstraints替代

要点三:taints/tolerations从节点角度控制调度,适合节点专用化场景(GPU节点、数据库节点),NoExecute会驱逐已有Pod

要点四:PriorityClass的抢占机制要谨慎使用,批处理任务设置preemptionPolicy: Never,核心服务设置高优先级

要点五:topologySpreadConstraints是生产环境跨故障域部署的首选方案,maxSkew: 1配合DoNotSchedule保证严格均匀分布

6.2 进阶学习方向

自定义调度器:当内置调度策略无法满足需求时,可以开发自定义调度器,通过Scheduling Framework扩展点实现

学习资源:Scheduling Framework

实践建议:先用调度器扩展(Extender)验证逻辑,再考虑写Framework插件

Descheduler深度使用:配置LowNodeUtilization、RemoveDuplicates等策略,自动重平衡集群负载

学习资源:Descheduler GitHub

实践建议:先在非生产环境测试Descheduler策略,避免误驱逐核心服务

Volcano批调度器:针对AI/大数据场景的批调度器,支持Gang Scheduling(一组Pod要么全部调度成功,要么全部不调度)

6.3 参考资料

Kubernetes调度官方文档 - 调度机制全面说明

kube-scheduler源码 - 理解调度算法实现

Descheduler项目 - Pod重调度工具

Volcano项目 - 批调度器

附录

A. 命令速查表

# 节点标签管理
kubectl label node  key=value       # 添加标签
kubectl label node  key=value --overwrite # 修改标签
kubectl label node  key-          # 删除标签
kubectl get nodes --show-labels         # 查看所有标签
kubectl get nodes -L key1,key2          # 查看指定标签列

# 污点管理
kubectl taint nodes  key=value:NoSchedule # 添加污点
kubectl taint nodes  key=value:NoSchedule- # 删除污点
kubectl taint nodes  key-         # 删除指定key的所有污点
kubectl describe node  | grep Taints    # 查看污点

# 调度排查
kubectl get events --field-selector reason=FailedScheduling -A # 调度失败事件
kubectl describe pod  | grep -A 10 Events  # Pod事件
kubectl get pods -o wide             # 查看Pod所在节点
kubectl top nodes                # 节点资源使用
kubectl describe node  | grep -A 20"Allocated resources"# 已分配资源

# 节点维护
kubectl drain  --ignore-daemonsets --delete-emptydir-data # 腾空节点
kubectl uncordon              # 恢复调度
kubectl cordon               # 标记不可调度

B. 配置参数详解

nodeAffinity操作符

操作符 含义 示例
In 值在列表中 values: ["ssd", "nvme"]
NotIn 值不在列表中 values: ["hdd"]
Exists 标签存在 不需要values字段
DoesNotExist 标签不存在 不需要values字段
Gt 值大于 values: ["100"] (数字字符串)
Lt 值小于 values: ["50"] (数字字符串)

Taint Effect对比

Effect 对新Pod 对已有Pod 适用场景
NoSchedule 不调度 不影响 节点专用化
PreferNoSchedule 尽量不调度 不影响 软限制
NoExecute 不调度 驱逐 节点维护、故障隔离

topologySpreadConstraints参数

参数 类型 说明
maxSkew int 拓扑域间Pod数量最大差值,1表示严格均匀
topologyKey string 拓扑域标签key
whenUnsatisfiable string DoNotSchedule 硬约束 /ScheduleAnyway软约束
labelSelector object 匹配Pod的标签选择器
minDomains int 最少拓扑域数量(1.25 Beta)
matchLabelKeys []string 用于区分不同版本的Pod(1.27 Beta)

C. 术语表

术语 英文 解释
预选 Filtering 调度第一阶段,过滤不满足条件的节点
优选 Scoring 调度第二阶段,对候选节点打分排序
亲和性 Affinity Pod倾向于调度到满足条件的节点或Pod附近
反亲和性 Anti-Affinity Pod倾向于远离满足条件的Pod
污点 Taint 节点上的排斥标记,阻止不容忍的Pod调度
容忍 Toleration Pod上的声明,表示可以容忍节点的污点
拓扑域 Topology Domain 按标签划分的节点组,如同一可用区的节点
抢占 Preemption 高优先级Pod驱逐低优先级Pod以获取资源
PDB PodDisruptionBudget Pod中断预算,限制同时不可用的Pod数量

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • gpu
    gpu
    +关注

    关注

    28

    文章

    5258

    浏览量

    136039
  • 数据库
    +关注

    关注

    7

    文章

    4078

    浏览量

    68524
  • kubernetes
    +关注

    关注

    0

    文章

    273

    浏览量

    9530

原文标题:别再让 Pod “乱跑”:Kubernetes 调度策略原理与落地指南

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    Kubernetes的Device Plugin设计解读

    设计解读最近在调研Kubernetes的GPU调度和运行机制,发现传统的alpha.kubernetes.io/nvidia-gpu即将在1.11版本中下线,和GPU相关的调度和部署的
    发表于 03-12 16:23

    阿里云容器Kubernetes监控(二) - 使用Grafana展现Pod监控数据

    kubernetes中承担的责任远不止监控数据的采集,还包括控制台的监控接口、HPA的POD弹性伸缩等都依赖于Heapster的功能。简介在kubernetes的监控方案中
    发表于 05-10 15:28

    从零开始入门 K8s| 详解 Pod 及容器设计模式

    一、为什么需要 Pod容器的基本概念我们知道 PodKubernetes 项目里面一个非常重要的概念,也是非常重要的一个原子调度单位,但是为什么我们会需要这样一个概念呢?在使用容
    发表于 09-20 15:12

    不吹不黑,今天我们来聊一聊 Kubernetes 落地的三种方式

    Kubernetes 作为自己的基础设施重心,"一万个人眼中就有一万个哈姆雷特",虽说 Kubernetes 是容器管理领域的事实标准,但实际上在不同背景的企业中,Kubernetes
    发表于 10-12 16:07

    Pod资源配置

    Kubernetes进阶实战》第四章《管理Pod资源对象》
    发表于 10-22 14:39

    深入研究Kubernetes调度

    “本文从 Pod 和节点的配置开始,介绍了 Kubernetes Scheduler 框架、扩展点、API 以及可能发生的与资源相关的瓶颈,并展示了性能调整设置,涵盖了 Kubernetes
    的头像 发表于 08-23 10:39 2028次阅读

    Kubernetes组件pod核心原理

    1. 核心组件原理 —— pod 核心原理 1.1 pod 是什么 pod 也可以理解是一个容器,装的是 docker 创建的容器,也就是用来封装容器的一个容器; pod 是一个虚拟化
    的头像 发表于 09-02 09:27 2573次阅读

    Kubernetes中的Pod简易理解

    PodKubernetes中非常重要的概念,也是Kubernetes管理的基本单位。正如其名,Pod像一个豌豆荚,可以容纳多个container,拥有相同的IP地址。
    的头像 发表于 02-15 10:44 2204次阅读

    Kubernetes Pod如何独立工作

    在学习 Kubernetes 网络模型的过程中,了解各种网络组件的作用以及如何交互非常重要。本文就介绍了各种网络组件在 Kubernetes 集群中是如何交互的,以及如何帮助每个 Pod 都能获取 IP 地址。
    的头像 发表于 05-16 14:29 1276次阅读
    <b class='flag-5'>Kubernetes</b> <b class='flag-5'>Pod</b>如何独立工作

    Kubernetes Pod如何获取IP地址呢?

    Kubernetes 网络模型的核心要求之一是每个 Pod 都拥有自己的 IP 地址并可以使用该 IP 地址进行通信。很多人刚开始使用 Kubernetes 时,还不清楚如何为每个 Pod
    的头像 发表于 07-21 10:00 1595次阅读
    <b class='flag-5'>Kubernetes</b> <b class='flag-5'>Pod</b>如何获取IP地址呢?

    配置KubernetesPod使用代理的两种常见方式

    在企业网络环境中进行Kubernetes集群的管理时,经常会遇到需要配置Pods通过HTTP代理服务器访问Internet的情况。这可能是由于各种原因,如安全策略限制、网络架构要求或者访问特定资源
    的头像 发表于 01-05 11:22 2373次阅读
    配置<b class='flag-5'>Kubernetes</b>中<b class='flag-5'>Pod</b>使用代理的两种常见方式

    Kubernetes Pod常用管理命令详解

    Kubernetes Pod常用管理命令详解
    的头像 发表于 02-17 14:06 1628次阅读
    <b class='flag-5'>Kubernetes</b> <b class='flag-5'>Pod</b>常用管理命令详解

    详解Kubernetes中的Pod调度亲和性

    Kubernetes(K8s)中,Pod 调度亲和性(Affinity) 是一种高级调度策略,用于控制
    的头像 发表于 06-07 13:56 1082次阅读

    Kubernetes Pod异常问题排查实战

    集群跑着跑着,Pod 挂了。Slack 告警一刷屏,脑子一片空白。打开终端敲 kubectl get pods,看到一堆 CrashLoopBackOff、ImagePullBackOff、Pending,不知道从哪下手。这是每个刚接触 Kubernetes 运维的人都会
    的头像 发表于 03-18 15:43 241次阅读

    Kubernetes Pod启动失败的各种场景及其排障方法

    Kubernetes 日常运维中,Pod 起不来是最常见的故障形态之一。很多运维工程师看到 Pod 状态不是 Running 时,第一反应是盯着 kubectl get pod
    的头像 发表于 04-13 13:53 78次阅读