前言

在Kubernetes(简称K8s)集群中,调度是实现资源高效利用与业务稳定运行的核心环节。它负责将Pod合理分配到集群节点上,既要满足业务的资源需求,又要保证集群整体的负载均衡。本文将从K8s组件协作机制出发,详细解析Pod的创建流程、调度器的工作原理,以及节点选择、亲和性调度、污点与容忍等关键技术,并结合实践案例说明节点维护与故障排查方法,帮助读者全面掌握K8s集群调度的核心知识。

一、Kubernetes集群调度基础

1.1 Kubernetes组件协作机制

Kubernetes各组件通过List-Watch机制实现协同工作与数据同步,这种机制确保了组件间的解耦与集群状态的实时一致性。

1.1.1 关键组件及职责
组件 职责描述
kubectl / API客户端 向APIServer发起资源创建、查询、更新等管理请求
APIServer 集群控制的核心入口,负责API调用处理、权限校验、与etcd交互
etcd 分布式键值存储,保存集群所有状态信息(如Pod、Node、配置等)
Controller Manager 运行各类控制器(如ReplicaSet、Deployment),维持资源期望状态(如副本数、自愈逻辑)
Scheduler 调度器,为未分配节点的Pod选择最合适的Node节点
kubelet 节点代理程序,负责本节点上Pod的生命周期管理(创建、启停、状态上报)
1.1.2 组件协作流程简述

用户通过kubectl或其他客户端向APIServer发送请求(如创建Pod)后,APIServer会完成权限校验并将数据存入etcd。etcd存储数据时会触发事件(如Create事件)并同步给APIServer;其他组件(Controller Manager、Scheduler、kubelet)通过监听APIServer的事件变化,触发各自的业务逻辑(如Controller Manager维持副本数、Scheduler调度Pod、kubelet运行容器),最终完成整个资源的部署与管理。

1.2 Pod创建与工作机制流程

Pod的完整生命周期需多个组件协同完成,其核心依托于List-Watch模型实现动态响应。以下是Pod创建的典型流程:

1.2.1 初始化监听(List-Watch启动)

Controller Manager、Scheduler、kubelet启动后会分别通过Watch API Server(HTTPS 6443端口)监听集群资源事件变化。

  • Controller Manager:监听副本控制类对象(如ReplicaSet、Deployment),确保资源符合期望状态;
  • Scheduler:监听未被调度的Pod(spec.nodeName为空),准备为其分配节点;
  • kubelet:监听分配到本节点的Pod,负责后续容器的创建与管理。
1.2.2 用户发起创建Pod对象请求

用户通过kubectl apply -f pod.yaml等命令,向APIServer发送创建Pod的请求。配置文件pod.yaml可能包含Pod的元数据、容器镜像、资源需求等信息。

1.2.3 etcd数据存储与事件触发

APIServer校验请求合法性后,将Pod元数据写入etcd;etcd写入成功后,向APIServer返回确认,并触发Create事件;APIServer将事件广播给所有监听的组件。

1.2.4 副本控制与调度
  • Controller Manager响应:若Pod关联ReplicaSet/Deployment,Controller Manager会检查当前副本数是否符合期望,不足则创建新副本;
  • APIServer更新数据:Controller Manager创建完Pod副本后,API Server会将Pod的详细信息更新写入etcd,触发更新事件;
  • Scheduler调度:监听至未调度的Pod(Pending状态),通过调度算法选择合适节点,并将节点信息写入APIServer;
  • 数据同步:APIServer更新etcd中Pod的节点绑定信息,完成调度确认。
1.2.5 容器运行与状态维护

目标节点的kubelet监听到新分配的Pod,调用容器运行时(如Docker、containerd)拉取镜像、创建并启动容器;kubelet将Pod状态(如Running、Failed)上报给APIServer;APIServer更新etcd中Pod的状态信息,etcd确认写入成功后,集群状态同步完成,Pod进入稳定运行(Running)阶段。

说明:kubelet会持续监听Pod事件,以应对副本数调整、镜像更新等动态变化,确保容器状态与期望一致。

二、Scheduler工作原理

2.1 调度器核心任务与目标

2.1.1 核心任务

Scheduler的核心职责是为未绑定节点的Pod(spec.nodeName == "")分配合适的Node节点,通过创建binding对象记录调度结果。

2.1.2 调度目标
  • 公平性:确保节点间资源分配均衡,避免个别节点负载过高;
  • 高效性:最大化集群资源利用率,减少资源浪费;
  • 效率:快速完成大批量Pod的调度,降低调度延迟;
  • 灵活性:支持自定义调度策略与插件,适配复杂业务场景。

2.2 调度流程详解

调度过程分为过滤(Predicate)和优选(Priorities)两个阶段,最终选择最优节点绑定Pod。

2.2.1 过滤阶段(Predicate)

过滤阶段通过一系列算法排除不满足Pod需求的节点,确保剩余节点均为“可行节点”。常见过滤算法如下:

算法名 功能描述
PodFitsResources 检查节点剩余CPU、内存等资源是否满足Pod需求
PodFitsHost 检查Pod指定的nodeName是否匹配当前节点
PodFitsHostPorts 检查Pod所需端口是否与节点已使用端口冲突
PodSelectorMatches 检查节点标签是否匹配Pod的标签选择器
NoDiskConflict 检查Pod挂载的Volume是否与节点已有挂载冲突

注意:若过滤后无可行节点,Pod将保持Pending状态,调度器会不断重试直至有节点满足条件;如果有多个节点满足条件,则进入优选阶段。

2.2.2 优选阶段(Priorities)

优选阶段对过滤后的可行节点进行打分排序,选择得分最高的节点作为目标。常见优选算法如下:

优先级项 描述
LeastRequestedPriority 节点资源使用率越低,得分越高(优先分配资源充足的节点)
BalancedResourceAllocation 优先选择CPU与内存使用率更均衡的节点(如20%CPU+30%内存优于10%CPU+50%内存)
ImageLocalityPriority 优先选择已缓存Pod所需镜像的节点(减少镜像拉取时间)

最终,调度器将得分最高的节点作为Pod的绑定目标,并更新APIServer中的Pod信息。

三、指定调度节点的常用方式

3.1 nodeName:强制绑定节点

pod.spec.nodeName直接将Pod调度到指定节点,跳过Scheduler的调度逻辑,属于强制绑定。

3.1.1 配置示例
vim myapp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      nodeName: node01  # 强制调度到node01
      containers:
      - name: myapp
        image: soscscs/myapp:v1
        ports:
        - containerPort: 80

执行命令创建Deployment并验证:

# 执行YAML文件创建Deployment
kubectl apply -f myapp.yaml
# 查看Pod详细信息,全部调度到了node01
kubectl get pods -o wide
# 查看详细事件(确认未经过scheduler调度分配)
kubectl describe pod myapp-699655c7fd-c7shz
3.1.2 特点
  • 直接由kubelet负责创建容器,无需Scheduler参与;
  • 若指定节点不存在或不可用,Pod会一直处于Pending状态。

3.2 nodeSelector:基于标签匹配

pod.spec.nodeSelector通过标签选择器label-selector匹配节点,由Scheduler根据标签策略调度Pod到目标节点,属于强制约束。

3.2.1 操作流程
  1. 获取节点名称:
kubectl get node
  1. 为节点添加标签:
# 给对应的node设置标签分别为yjs=a和yjs=b
kubectl label nodes node01 yjs=a
kubectl label nodes node02 yjs=b
# 查看标签
kubectl get nodes --show-labels
  1. 配置Pod的nodeSelector:
vim myapp1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp1
  template:
    metadata:
      labels:
        app: myapp1
    spec:
      nodeSelector:
        yjs: a
      containers:
      - name: myapp1
        image: soscscs/myapp:v1
        ports:
        - containerPort: 80
  1. 执行配置并验证:
kubectl apply -f myapp1.yaml
# 查看Pod运行节点(所有Pod均运行在node01)
kubectl get pods -o wide
# 查看详细事件(确认经过scheduler调度分配)
kubectl describe pod myapp1-64c58784f9-4mgc6
3.2.2 标签管理命令
  • 查看节点标签:kubectl get nodes --show-labels
  • 筛选指定标签的节点:kubectl get nodes -l yjs=a
  • 修改标签(需加--overwrite):kubectl label nodes node01 yjs=b --overwrite
  • 删除标签:kubectl label nodes node01 yjs-

四、亲和性与反亲和性调度

亲和性调度用于更灵活地控制Pod与节点、Pod与Pod之间的调度关系,分为节点亲和性、Pod亲和性与Pod反亲和性。

4.1 节点亲和性(NodeAffinity)

节点亲和性定义Pod对节点的“偏好”或“硬性要求”,类似“想去哪个班”(软策略)或“必须去哪个班”(硬策略)。

4.1.1 配置结构
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:  # 硬策略(必须满足)
      nodeSelectorTerms:
      - matchExpressions:  # 匹配规则
        - key: <节点标签键>
          operator: <操作符>
          values: [<值列表>]
    preferredDuringSchedulingIgnoredDuringExecution: # 软策略(优先满足)
    - weight: <权重,0-100>  # 多个软策略时,权重越高优先级越高
      preference:
        matchExpressions:
        - key: <节点标签键>
          operator: <操作符>
          values: [<值列表>]
4.1.2 支持的操作符
操作符 含义 示例
In 标签值在列表中 env In (dev, test)
NotIn 标签值不在列表中 env NotIn (prod)
Gt 标签值大于指定数值 version Gt 3
Lt 标签值小于指定数值 version Lt 3
Exists 标签存在(忽略值) Exists zone
DoesNotExist 标签不存在 DoesNotExist debug
4.1.3 实践案例
案例1:硬策略(必须排除node02)
# 创建目录并编写配置文件
mkdir /opt/affinity
cd /opt/affinity
vim pod1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: affinity
  labels:
    app: node-affinity-pod
spec:
  containers:
  - name: with-node-affinity
    image: soscscs/myapp:v1
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname    # 指定node的标签
            operator: NotIn # 设置Pod安装到标签值不在values列表中的node上
            values:
            - node02

执行配置并验证:

# 创建Pod
kubectl apply -f pod1.yaml
# 查看Pod调度节点(确认不在node02上)
kubectl get pods -o wide
# 重新部署并验证(可选)
kubectl delete pod --all && kubectl apply -f pod1.yaml && kubectl get pods -o wide

注意:如果硬策略不满足条件,Pod状态会一直处于Pending状态。

案例2:软策略
vim pod2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: affinity01
  labels:
    app: node-affinity-pod
spec:
  containers:
  - name: with-node-affinity
    image: soscscs/myapp:v1
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 5   # 多个软策略时,权重越大优先级越高
        preference:
          matchExpressions:
          - key: kubernetes.io/hostname
            operator: In
            values:
            - node01
      - weight: 10   # 权重更高,优先调度到node02
        preference:
          matchExpressions:
          - key: kubernetes.io/hostname
            operator: In
            values:
            - node02

执行配置并验证:

kubectl apply -f pod2.yaml
# 查看Pod状态及调度节点(优先调度到权重高的node02)
kubectl get pods -o wide
案例3:软硬策略

如果同时使用硬策略和软策略,需先满足硬策略再满足软策略:

vim pod3.yaml
apiVersion: v1
kind: Pod
metadata:
  name: affinity03
  labels:
    app: node-affinity-pod
spec:
  containers:
  - name: with-node-affinity
    image: soscscs/myapp:v1
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:   # 先满足硬策略,排除node02
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: NotIn
            values:
            - node02
      preferredDuringSchedulingIgnoredDuringExecution:  # 再满足软策略,优先选择yjs=a的节点
      - weight: 1
        preference:
          matchExpressions:
          - key: yjs
            operator: In
            values:
            - a

执行配置并验证:

kubectl apply -f pod3.yaml
# 查看Pod调度节点(不在node02且优先在yjs=a的节点)
kubectl get pods -o wide

4.2 Pod亲和性与反亲和性

Pod亲和性/反亲和性基于已有Pod的标签控制调度,用于实现“Pod与目标Pod同节点/同拓扑域”或“Pod与目标Pod分离”的需求。

4.2.1 核心区别
调度策略 匹配对象 核心作用 拓扑域支持
nodeAffinity 主机 根据节点标签控制Pod调度到指定节点(硬/软策略) 不支持
podAffinity 其他Pod 使当前Pod与目标Pod在同一拓扑域 支持
podAntiAffinity 其他Pod 使当前Pod与目标Pod在不同拓扑域 支持

拓扑域:通过topologyKey指定节点标签,标签值相同的节点属于同一拓扑域(如同一机房、同一机架)。

4.2.2 实践案例
案例1:Pod亲和性(与myapp01同拓扑域)
  1. 为节点添加标签并创建目标Pod:
kubectl label nodes node01 yjs=a      
kubectl label nodes node02 yjs=b  
vim pod4.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp01
  labels:
    app: myapp01
spec:
  containers:
  - name: with-node-affinity
    image: soscscs/myapp:v1
kubectl apply -f pod4.yaml
# 查看目标Pod状态及标签
kubectl get pods --show-labels -o wide
  1. 配置Pod亲和性调度:
vim pod5.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp02
  labels:
    app: myapp02
spec:
  containers:
  - name: myapp02
    image: soscscs/myapp:v1
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - myapp01    # 匹配标签app=myapp01的Pod
        topologyKey: yjs # 拓扑域为标签yjs,值相同的节点为同一域
  1. 执行配置并验证:
kubectl apply -f pod5.yaml
# 查看Pod调度节点(与myapp01同属yjs=a域)
kubectl get pods --show-labels -o wide
案例2:Pod反亲和性(与myapp01不同节点)
示例1:软策略反亲和
vim pod6.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp10
  labels:
    app: myapp10
spec:
  containers:
  - name: myapp10
    image: soscscs/myapp:v1
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - myapp01    # 匹配标签app=myapp01的Pod
          topologyKey: kubernetes.io/hostname  # 拓扑域为节点主机名(单个节点)

执行配置并验证:

kubectl apply -f pod6.yaml
# 查看Pod调度节点(与myapp01不同节点)
kubectl get pods --show-labels -o wide
示例2:硬策略反亲和

前提:node01和node02都有yjs=a标签

vim pod7.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp20
  labels:
    app: myapp20
spec:
  containers:
  - name: myapp20
    image: soscscs/myapp:v1
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - myapp01
        topologyKey: yjs

执行配置并验证:

kubectl apply -f pod7.yaml
# 查看Pod状态(因无可用拓扑域,处于Pending状态)
kubectl get pod --show-labels -o wide

说明:node01和node02同属yjs=a拓扑域,反亲和要求新Pod与myapp01不在同一拓扑域,导致无可用节点。

五、污点(Taint)与容忍(Toleration)

节点亲和性是Pod的属性,使Pod被吸引到特定节点;而Taint(污点)则相反,使节点能够排斥特定Pod。污点与容忍机制用于实现节点对Pod的“排斥”与“例外”,节点通过污点拒绝不匹配的Pod,Pod通过容忍突破排斥。

5.1 污点(Taint)

5.1.1 污点格式与作用

污点格式:key=value:effect,其中effect定义排斥行为,支持以下三种类型:

effect类型 描述
NoSchedule 拒绝调度新Pod到该节点(已运行的Pod不受影响)
PreferNoSchedule 尽量不调度新Pod到该节点(非强制)
NoExecute 拒绝新Pod调度,且驱逐已运行的不匹配Pod
5.1.2 污点管理命令
  • 添加污点:kubectl taint node node01 key=value:NoSchedule
  • 查看节点污点:kubectl describe node node01 | grep Taint
  • 删除污点:kubectl taint node node01 key:NoSchedule-

示例:

# 查看节点列表
kubectl get nodes
# 查看master节点污点(默认有NoSchedule污点,不调度Pod)
kubectl describe node master | grep Taint
# 为node02添加NoExecute污点,驱逐现有不匹配Pod
kubectl taint node node02 check=mycheck:NoExecute
# 查看Pod状态(node02上的Pod被驱逐)
kubectl get pods -o wide

5.2 容忍(Toleration)

Pod通过容忍配置可突破节点的污点限制,需与节点污点的keyvalueeffect匹配。

5.2.1 配置示例
  1. 为节点添加污点并尝试创建Pod:
kubectl taint node node01 check=mycheck:NoExecute
# 创建无容忍配置的Pod(创建失败)
kubectl apply -f pod3.yaml
kubectl get pods -o wide
  1. 配置Pod容忍:
vim pod3.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp01
  labels:
    app: myapp01
spec:
  containers:
  - name: with-node-affinity
    image: soscscs/myapp:v1
  tolerations:
  - key: "check"
    operator: "Equal"  # Equal需匹配value,Exists忽略value
    value: "mycheck"   # 与污点的value匹配
    effect: "NoExecute"      # 与污点的effect匹配
    tolerationSeconds: 3600  # 被驱逐前可在节点上保留的时间(仅对NoExecute有效)
  1. 执行配置并验证:
kubectl apply -f pod3.yaml
# 查看Pod状态(创建成功)
kubectl get pods -o wide
5.2.2 特殊配置
  1. 容忍所有污点:
tolerations:
- operator: "Exists"  # 忽略key和effect,容忍所有污点
  1. 容忍指定key的所有effect:
tolerations:
- key: "key"
  operator: "Exists"  # 忽略effect,容忍key=key的所有effect
  1. 多Master节点资源优化:
kubectl taint node Master-Name node-role.kubernetes.io/master=:PreferNoSchedule
5.2.3 污点在Node节点更新组件时的作用
# 节点更新前,添加NoExecute污点驱逐Pod
kubectl taint node node01 check=mycheck:NoExecute
# 资源不足时,临时允许Pod调度到Master
kubectl taint node master node-role.kubernetes.io/master=:PreferNoSchedule
# 节点更新完成后,去除污点恢复正常
kubectl taint node node01 check=mycheck:NoExecute-

注意:key、value、effect必须与Node上的污点匹配;operator: Exists表示存在即可,无需匹配value。

六、节点维护与Pod生命周期

6.1 节点维护操作

当需要对节点进行升级、检修时,可通过以下命令暂时隔离节点并迁移Pod:

命令 功能描述
kubectl cordon <node> 标记节点为“不可调度”,阻止新Pod调度至此
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data 驱逐节点上的Pod(忽略DaemonSet,删除本地数据)
kubectl uncordon <node> 恢复节点为“可调度”状态

说明:drain命令等价于“cordon + 驱逐”,适用于节点离线维护场景。

示例:

# 标记node01为不可调度
kubectl cordon node01
# 查看节点污点(确认添加不可调度标记)
kubectl describe nodes node01 | grep Taint
# 驱逐node01上的Pod
kubectl drain node01 --ignore-daemonsets --delete-emptydir-data --force
# 查看Pod状态(已迁移至其他节点)
kubectl get pods -o wide
# 恢复node01为可调度状态
kubectl uncordon node01
# 验证污点已移除
kubectl describe nodes node01 | grep Taint

Kubernetes中的驱逐(Eviction)机制:

  • 触发场景:节点维护/升级、节点资源不足;
  • 决策依据:节点负载、Pod优先级;
  • 终止流程:发送SIGTERM信号→预留终止时间窗口→超时强制终止。

6.2 Pod生命周期阶段(Phase)

Pod在生命周期中会经历以下阶段,反映其当前状态:

阶段 说明
Pending 已创建但未调度,或正在拉取镜像
Running 至少一个容器运行中(或启动/重启中)
Succeeded 所有容器正常终止(如Job类任务完成)
Failed 至少一个容器异常终止(非0退出码或被强制终止)
Unknown 无法获取状态(通常为通信故障)

阶段详解:

  • Pending:APIServer已创建Pod并存入etcd,但未完成调度或正在下载镜像;
  • Running:Pod已调度到节点,所有容器已创建,至少一个容器运行/启动/重启中(不一定可提供服务);
  • Succeeded:适用于非持久运行Pod,所有容器成功终止且不重启;
  • Failed:所有容器终止,至少一个容器故障退出;
  • Unknown:kube-controller-manager与Pod通信异常。

七、故障排查常用命令

操作需求 命令示例
查看Pod事件(调度失败原因) kubectl describe pod <pod-name>
查看Pod日志 kubectl logs <pod-name> [-c <container-name>]
进入容器执行命令 kubectl exec -it <pod-name> -- bash
查看集群状态 kubectl cluster-info
查看节点状态 kubectl get nodes
查看kubelet日志 journalctl -xefu kubelet

总结

Kubernetes调度是集群资源管理的核心,通过组件协作、调度算法、亲和性策略、污点与容忍等机制,实现了Pod的智能分配。本文从基础机制到实践操作,详细介绍了:

  1. 组件通过List-Watch机制协同工作,完成Pod从创建到运行的全流程;
  2. 调度器通过过滤与优选阶段选择最优节点,平衡资源利用率与业务需求;
  3. 节点选择方式从简单的强制绑定(nodeName)到灵活的标签匹配(nodeSelector)、亲和性调度,满足不同场景需求;
  4. 污点与容忍机制实现节点对Pod的精细控制,配合节点维护命令保障集群稳定性。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐