kubernetes_scheduler

Kube-Schedular

简介

kube-scheduler 是 Kubernetes 核心组件之一,负责 Pod 的调度(Scheduling)。简而言之,它的主要职责是决定一个未绑定到节点的 Pod 应该被分配到哪个节点上运行。调度器在 Kubernetes 中起到“任务分派员”的作用,其调度的核心目标是满足工作负载的资源需求(如 CPU、内存等),并同时遵循用户定义的调度策略和约束(如 Pod 配置的 nodeSelectoraffinity 等)。

工作流程

一、Pod 监听:

  1. 初始化 Informer

    • kube-scheduler 在启动时,会创建一个 Pod Informer,用于监听和缓存 Pod 的变化(包括新增、修改、删除事件)。

    • 只监听那些未绑定到节点(spec.nodeName 为空)的 Pod。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      func NewScheduler(...) (*Scheduler, error) {
      ...
      // 创建 Pod Informer
      podInformer := informerFactory.Core().V1().Pods()

      // 初始化调度器
      sched := &Scheduler{
      podQueue: queue, // 调度队列
      podInformer: podInformer, // Pod Informer
      podLister: podInformer.Lister() // 用于从缓存中获取 Pod 列表
      ...
      }
      ...
      }
  2. 通过 List 和 Watch 获取数据

    • List:在调度器启动时,通过 API Server 全量获取当前所有未被绑定的 Pod。
    • Watch:之后通过 Watch API,订阅 Pod 的增量更新事件(新增、修改、删除)。

二、接收调度请求:

  • 当用户创建一个 Pod 时,Pod 没有绑定到特定的节点.spec.nodeName 为空),此时 kube-scheduler 会将其视为候选调度对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    // 在 Informer 的初始化过程中,设置了过滤器
    podInformer := informerFactory.Core().V1().Pods().Informer()

    // 过滤只监听未绑定到节点的 Pod
    podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
    AddFunc: func(obj interface{}) {
    pod := obj.(*v1.Pod)
    if pod.Spec.NodeName == "" {
    // Pod 未被绑定,加入调度队列
    sched.AddPodToQueue(pod)
    }
    },

    UpdateFunc: func(oldObj, newObj interface{}) {
    oldPod := oldObj.(*v1.Pod)
    newPod := newObj.(*v1.Pod)
    if oldPod.Spec.NodeName == "" && newPod.Spec.NodeName == "" {
    // Pod 更新了但仍未绑定
    sched.AddPodToQueue(newPod)
    }
    },
    DeleteFunc: func(obj interface{}) {
    pod := obj.(*v1.Pod)
    if pod.Spec.NodeName == "" {
    // Pod 被删除
    sched.DeletePodFromQueue(pod)
    }
    },
    })

三、可调度候选节点筛选(Predicate/Filter Phase):

kube-scheduler 会根据一系列过滤规则(称为 “Predicates”,目前叫 Filter Plugins),筛选出所有满足 Pod 调度需求的候选节点。例如:

1. PodFitsHostPorts

  • 含义:验证目标节点上是否有 Pod 使用了相同的 hostPort,避免端口冲突。
  • 场景:当 Pod 定义了容器的 hostPort 时,调度器会检查该端口在目标节点上是否已经被占用。

2. PodFitsResources

  • 含义:检查目标节点的资源是否足够,包括:
    • CPU内存、GPU(通过 OpaqueIntResources 定义)。
  • 实现逻辑:对比 Pod 的资源请求(requests)与节点的可用资源(考虑已分配和未分配资源)。如果资源不足,则调度失败。

3. HostName

  • 含义:检查 Pod 的 spec.nodeName 是否与候选节点的名称一致。
  • 场景:当 Pod 明确指定了运行的节点(通过 spec.nodeName 字段),调度器会检查是否匹配。

4. MatchNodeSelector

  • 含义:检查候选节点是否满足 Pod 的 nodeSelector 条件。
  • 场景:当 Pod 使用 nodeSelector 指定了需要运行的节点特性(通常是节点的标签),调度器会验证候选节点是否满足这些条件。

5. NoVolumeZoneConflict

  • 含义:验证 Pod 引用的 Volume(例如云存储卷)是否可以在目标节点所在的地域(Zone)使用。
  • 场景:某些存储类型(如 GCE PD、AWS EBS 等)具有区域限制,调度器需要检查节点的 Zone 是否匹配。

6. MatchInterPodAffinity

  • 含义:检查 Pod 是否匹配其他 Pod 的亲和性(Affinity)或反亲和性(Anti-Affinity)规则。
  • 场景:用于实现将相关的 Pod 放置在相同或不同节点上的需求。

7. NoDiskConflict

  • 含义:验证目标节点上是否存在 Pod 的 Volume 冲突。
  • 支持的存储类型:仅限 GCE PD、AWS EBS、Ceph RBD、iSCSI 等。

8. PodToleratesNodeTaints

  • 含义:检查 Pod 是否能容忍(Toleration)目标节点的污点(Taint)。
  • 场景:用于过滤那些不能容忍节点特殊属性(通过 Taints 表达)的 Pod。

9. CheckNodeMemoryPressure

  • 含义:验证目标节点是否处于内存压力(MemoryPressure)状态。
  • 场景:当节点内存使用接近或达到阈值,Pod 不应该调度到该节点。

10. CheckNodeDiskPressure

  • 含义:验证目标节点是否处于磁盘压力(DiskPressure)状态。
  • 场景:当节点磁盘空间不足时,调度器会避免将新的 Pod 调度到该节点。

11. NoVolumeNodeConflict

  • 含义:验证目标节点是否满足 Pod 所需 Volume 的条件,例如是否存在挂载冲突。
  • 场景:在某些存储驱动中,一个 Volume 可能只能挂载到一个节点上,因此需要检查是否有冲突。

四、候选节点评分(Priority/Scoring Phase)

  • 对于通过过滤的候选节点,调度器会根据一系列优先级规则(称为 “Priorities”,现在叫 Scoring Plugins)为每个节点打分。
  • 评分的目标是从所有可用节点中选择一个最优节点。例如:
    • 尽量将 Pod 调度到负载较低的节点。
    • 尽量将 Pod 调度到与其数据存储位置较近的节点。
    • 避免过多 Pod 调度到同一个节点,导致资源热点。

在 Kubernetes 中,kube-scheduler 是负责将 Pod 调度到合适的 Node 上的组件。调度过程分为两步:过滤(Filter)打分(Score)。其中,Priorities 策略是调度器在打分阶段使用的规则,用于评估每个 Node 的优先级,通过分配得分来选择最优的候选节点。

下面详细介绍常见的 Priorities 策略:


1. SelectorSpreadPriority

  • 作用:
    • 优先将 Pod 分布到不同的 Node 上,减少某个 Node 上同一个 Service 或 Replication Controller 的 Pod 数量。
  • 目的:
    • 达到负载均衡,避免将同一类型的 Pod 全部调度到一个 Node 上。
  • 典型场景:
    • 应用高可用性,提高服务容错能力(例如分布式系统)。

2. InterPodAffinityPriority

  • 作用:
    • 优先将 Pod 调度到具有相同拓扑(如同一个节点、Rack、Zone 等)的节点上。
  • 目的:
    • 符合 Pod 的亲和性要求,例如让 Pod 与相同组件的其他 Pod 部署在同一个区域中。
  • 典型场景:
    • 有数据本地性需求的应用或需要紧密协同的组件。

3. LeastRequestedPriority

  • 作用:
    • 优先调度到资源请求最少的 Node 上。
  • 目的:
    • 避免资源紧张,提高资源利用率。
  • 典型场景:
    • 集群中存在部分节点资源接近饱和时,优先调度到空闲较多的节点。

4. BalancedResourceAllocation

  • 作用:
    • 优先选择 CPU 和内存使用率相对均衡的节点。
  • 目的:
    • 避免单一维度资源(如 CPU 或内存)成为瓶颈,优化集群资源利用。
  • 典型场景:
    • 资源分布不均的集群环境下,适合有高资源利用需求的场景。

5. NodePreferAvoidPodsPriority

  • 作用:
    • 优先避开有 preferAvoidPods 字段标记的节点。
  • 目的:
    • 避免对某些节点施加额外负载。
  • 典型场景:
    • 某些节点需要保留资源或需要减少干扰。

6. NodeAffinityPriority

  • 作用:
    • 优先调度到匹配 NodeAffinity 的节点上。
  • 目的:
    • 符合节点亲和性规则,满足用户的调度要求。
  • 典型场景:
    • 应用对特定的硬件、标签等有明确需求。

7. TaintTolerationPriority

  • 作用:
    • 优先调度到能容忍特定 Taint 的节点上。
  • 目的:
    • 解决 Taint 和 Toleration 机制下的调度问题。
  • 典型场景:
    • 使用节点隔离机制时。

8. ServiceSpreadingPriority(已被替代)

  • 作用:
    • 将同一个 Service 的 Pod 分布到不同的 Node 上。
  • 替代:
    • 该策略已被 SelectorSpreadPriority 替代。

9. EqualPriority

  • 作用:
    • 为所有节点分配相同的优先级,优先级值为 1。
  • 目的:
    • 用于测试或没有特殊调度需求的场景。

10. ImageLocalityPriority

  • 作用:
    • 优先调度到已经缓存了容器镜像的节点上。
  • 目的:
    • 减少镜像拉取时间,提高调度效率。
  • 典型场景:
    • 集群节点镜像拉取速度较慢时。

11. MostRequestedPriority

  • 作用:
    • 优先调度到已经使用过资源的节点上。
  • 目的:
    • 尤其适用于 cluster-autoscaler,提高已有节点的资源利用率。
  • 典型场景:
    • 自动扩容场景,避免浪费新的节点资源。

kube-scheduler 的 Priorities 实现位于 pkg/scheduler/framework/plugins 中。例如,BalancedResourceAllocation 的代码实现可以参考该插件下的具体实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func (b *BalancedAllocation) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
nodeInfo, err := b.nodeInfoLister.Get(nodeName)
if err != nil {
return 0, framework.NewStatus(framework.Error, err.Error())
}
return b.score(pod, nodeInfo.Node())
}

func (b *BalancedAllocation) score(pod *v1.Pod, node *v1.Node) int64 {
// 计算 CPU 和内存的使用率
cpuFraction := node.UsedCPU() / node.TotalCPU()
memoryFraction := node.UsedMemory() / node.TotalMemory()
// 计算得分
score := 100 - int64(math.Abs(cpuFraction-memoryFraction)*100)
return score
}

五、决策和绑定(Bind Phase)

  • 从评分最高的节点中挑选一个节点,作为该 Pod 的最终调度目标。
  • 调度器将调度结果写入到 etcd 中,将 Pod 绑定到目标节点。

整体来说,调度过程可以概括为:过滤(Filter) -> 评分(Score) -> 绑定(Bind)

在 Kubernetes(K8S)中,不同的 QoS(Quality of Service) 类表示 Pod 的服务质量保障级别,这对调度、资源管理和优先级管理起到重要作用。Kubernetes 基于 Pod 的资源请求和限制自动确定它的 QoS 类,因此理解 QoS 类以及它们如何影响调度至关重要。本文将系统介绍 QoS 类的划分及其调度行为。


K8S 的 QoS 类分类

Kubernetes 定义了三种 QoS 类,分别是:

1. Guaranteed

  • 一个 Pod 所有容器requestslimits 必须完全相等。

  • 特性:这类 Pod 通常被视为最高优先级资源请求,因此在资源争夺时被保留。

  • 场景:适用于需要强资源保证的关键性应用。

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    apiVersion: v1
    kind: Pod
    metadata:
    name: guaranteed-pod
    spec:
    containers:
    - name: app
    image: nginx
    resources:
    requests:
    memory: "500Mi"
    cpu: "0.5"
    limits:
    memory: "500Mi"
    cpu: "0.5"

2. Burstable

  • 如果 Pod 至少有一个容器的 requests 设置了,但 requestslimits 不完全相等,则 Pod 被归为 Burstable

  • 特性:该类 Pod 会优先获取至少等于 requests 的资源,其余资源可在容量溢出时被收回。

  • 场景:适合对资源核心需求较低,但能够在负载高峰期动态扩展的场景。

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    apiVersion: v1
    kind: Pod
    metadata:
    name: burstable-pod
    spec:
    containers:
    - name: app
    image: nginx
    resources:
    requests:
    memory: "200Mi"
    cpu: "0.2"
    limits:
    memory: "500Mi"
    cpu: "0.5"

3. BestEffort

  • 如果 Pod 所有容器都没有配置 requestslimits,则它属于 BestEffort

  • 特性:属于最低优先级 Pod,仅在其他资源有剩余时可分配资源。

  • 场景:适用于非核心、无资源保障需求的后备工作负载。

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    apiVersion: v1
    kind: Pod
    metadata:
    name: besteffort-pod
    spec:
    containers:
    - name: app
    image: nginx

QoS Class 在调度中的运作机制

Kubernetes 的调度器主要通过以下几个相关逻辑处理 QoS 类:

1. 资源分配优先级

  • Guaranteed > Burstable > BestEffort
  • Kubernetes Scheduler 在评估节点资源是否充足时,对于 Guaranteed 的 Pod 会尝试确保其分配请求的资源总量和上限。
  • Burstable 会优先与 Requests 值匹配,但 Limits 超出部分可能因抢占而被剥夺。
  • BestEffort Pod 通常在资源充足时才被调度,但在资源紧张时可能完全无法运行。

2. Node Eviction(节点逐出机制)

当节点资源耗尽或压力过高(例如内存压力MemoryPressure),Kubernetes 使用 QoS 类来决定驱逐的优先级:

  • BestEffort:首当其冲被驱逐,适合非关键性负载。
  • Burstable:在满足请求的基础上,超出的部分会被挤占或驱逐。
  • Guaranteed:保证级别最高,最后才会被驱逐。

3. 调度优先级

  • 调度器会根据节点的资源可用性优先分配高 QoS 的 Pod。
  • 考虑结合 TaintsTolerations、资源亲和性等规则提高具体调度的确定性。

举例:调度阶段中的 QoS Decision

当新的 Pod 到来时,Kubernetes Scheduler 会依次检查以下项:

  • 是否满足 Pod 的 requests(按 QoS 优先顺序检查);
  • 节点剩余容量能否满足 Pod 的 limits
  • Resource Fit Filter(调度器中的 Fit 规则)根据 QoS 级别动态评估节点状态和适合性。

调度 QoS 类的实际操作与优化

为了更好地调度不同 QoS 的 Pod,我们可以采取以下策略:

1. 调整资源分配规则

为关键性的应用分配 QoS Guaranteed,明确资源上下界,保证资源独占或排他性。

2. Taints 和 Tolerations

配合使用 Taints 和 Tolerations,将高 QoS 的应用调度到专用节点。

1
2
3
4
taints:
- key: critical
value: true
effect: NoSchedule

3. 预留关键性资源节点

Kubernetes 支持通过 kube-reservedsystem-reserved 等方式预留关键性资源,保证平台本身稳定运行。

4. 配额管理

使用 ResourceQuotas 限制低 QoS 的资源消耗,如限制 BestEffort Pod 数量,确保资源可为高 QoS 的 Pod 使用。

API 对象:requests limits

在 Kubernetes 中,requestslimits 是 Pod 或容器级别的资源管理功能,用于定义对 CPU 和内存等资源的需求和限制。这两个参数是 Kubernetes 调度器和 Kubelet 的关键配置,它们分别用于调度和运行时资源管理。


1. 什么是 requestslimits

1.1 requests

  • 定义: 容器运行时的最低资源需求。Kubernetes 调度器在调度 Pod 时会使用 requests 的值来决定是否有足够的资源可用。
  • 作用:
    • 决定 Pod 是否能被调度到某个 Node。
    • 一旦 Pod 被调度到某个 Node,Node 上必须预留至少 requests 定义的资源量。

1.2 limits

  • 定义: 容器运行时的资源上限。Kubelet 和容器运行时会根据 limits 限制容器使用的资源量,超过此限制可能会被杀死或降速。
  • 作用:
    • 防止容器使用的资源超出指定的上限,从而保护其他容器的稳定性。

配置示例

以一个简单的 Pod 配置为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
containers:
- name: example-container
image: nginx
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"

解释:

  • requests.memory: 64Mi: 容器至少需要 64Mi 内存才能正常运行。
  • limits.memory: 128Mi: 容器最多只能使用 128Mi 内存,超过时可能会被 OOM(Out of Memory)杀死。
  • requests.cpu: 250m: 容器需要 250 毫核的 CPU,调度器会确保目标节点能够提供这部分资源。
  • limits.cpu: 500m: 容器最多只能使用 500 毫核的 CPU。

2. Kubernetes 调度器如何使用 requests

调度器是 Kubernetes 的核心组件之一,负责将待调度的 Pod 分配到合适的节点上。调度器通过以下步骤使用 requests 值来进行资源调度:

2.1 节点资源过滤

调度器首先会根据 Pod 的 requests 值过滤掉不满足资源需求的节点:

  • 对比节点的 可用资源 和 Pod 的 requests
  • 如果节点的可用资源小于 Pod 的 requests,则该节点会被过滤掉。

节点可用资源计算公式:

1
节点可用资源 = 节点总资源 - sum(所有已分配 Podrequests)

示例:

  • 假设一个节点有 4 核 CPU 和 8 Gi 内存。
  • 已经运行了 2 个 Pod,分别请求了以下资源:
    • Pod1: requests.cpu=1requests.memory=2Gi
    • Pod2: requests.cpu=1requests.memory=2Gi
  • 节点剩余资源:
    • 剩余 CPU: 4 - (1 + 1) = 2
    • 剩余内存:8Gi - (2Gi + 2Gi) = 4Gi
  • 如果新创建一个 Pod 需要 requests.cpu=3,调度器会过滤掉这个节点,因为剩余 CPU 不足。

2.2 优化节点选择

在过滤掉不满足 requests 的节点后,调度器会根据调度策略(例如:最少资源利用策略均衡策略)选择最佳节点。例如:

  • 找出当前负载最小的节点。
  • 确保资源分配均匀,避免资源热点。

3. Kubelet 和容器运行时如何使用 limits

在 Pod 被调度到节点上之后,Kubelet 和容器运行时会使用 limits 来限制容器运行时的资源使用:

3.1 CPU 限制

  • limits.cpu 通过 Cgroup 的 CPU SharesCPU Quota 实现:
    • CPU Shares: 控制 CPU 调度的优先级。例如,requests.cpu=500m 会分配较低优先级,但如果节点空闲,容器可以使用更多 CPU。
    • CPU Quota: 硬性限制 CPU 使用总量。例如,limits.cpu=500m 意味着容器最多只能使用 50% 的 CPU 核心时间。

相关 cgroup 配置路径:

1
2
/sys/fs/cgroup/cpu/kubepods/<pod_id>/<container_id>/cpu.shares
/sys/fs/cgroup/cpu/kubepods/<pod_id>/<container_id>/cpu.cfs_quota_us

3.2 内存限制

  • limits.memory 是一个硬限制,通过 Cgroup 的 memory limit 配置。
  • 如果容器超出 limits.memory 的值,会触发 OOM(Out of Memory)杀死容器。

相关 cgroup 配置路径:

1
/sys/fs/cgroup/memory/kubepods/<pod_id>/<container_id>/memory.limit_in_bytes

4. 使用 requestslimits 的最佳实践

  • 合理分配 requestslimits:

    • requests 应根据容器的最低需求来设置,避免调度失败。
    • limits 应根据容器的最大允许使用量来设置,避免资源抢占。
  • 避免 requestslimits 间差距过大:

    • 如果 limits 远大于 requests,容易导致节点资源超分配,运行时引发不稳定。
    • 如果 requests 远大于实际需求,可能导致资源浪费,降低集群整体利用率。
  • 结合资源配额(Resource Quotas)和限制(Limit Ranges):

    • 使用 Resource Quotas 在命名空间层面限制资源总量。
    • 使用 Limit Ranges 强制为 Pod 配置合理的 requestslimits

将 Pod 调度到指定的 Node 上

NodeSelector 是 Kubernetes 中用来将 Pod 调度到特定的 Node 上的一种机制。它本质上是一种调度约束,让你可以通过为 Pod 定义一个条件来指定只允许它运行在满足该条件的节点上。

在 Kubernetes 中,Node 通常会有一组标签(labels),比如:

1
2
3
4
metadata:
labels:
disktype: ssd
region: us-west

NodeSelector 是 Pod 的 spec 部分中的一个字段,允许你通过指定这些标签来选择目标节点。


NodeSelector 的工作原理

NodeSelector 的核心是通过键值对匹配来选择 Node。Kubernetes 的调度器会检查集群中所有可用的节点,只有满足 NodeSelector 条件的节点才会被作为候选节点来运行该 Pod。


使用示例

1. 定义节点标签

给一个节点打上标签。假设我们有一个节点名为 node1,我们通过以下命令给它加上一个标签:

1
kubectl label nodes node1 disktype=ssd

2. 配置 Pod 的 NodeSelector

定义一个 Pod,并使用 nodeSelector 来选择节点:

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx
image: nginx
nodeSelector:
disktype: ssd

在这个例子中,nodeSelector 指定了 disktype: ssd,所以 Kubernetes 只会尝试将该 Pod 调度到具有标签 disktype=ssd 的节点上。


多个条件的 NodeSelector

NodeSelector 可以指定多个键值对条件,所有条件必须同时满足。例如:

1
2
3
nodeSelector:
disktype: ssd
region: us-west

这种情况下,目标节点必须同时满足以下条件:

  • 标签 disktype=ssd
  • 标签 region=us-west

注意: NodeSelector 的匹配条件是 AND 逻辑,所有条件都必须满足,不能用 OR 逻辑。


使用场景

  1. 性能优化: 你可以将特定的工作负载调度到具有高性能硬件的节点上,比如 SSD 存储或高 CPU 内核的节点。
  2. 地理分布: 通过标签标识区域(region)和可用区(zone),你可以将工作负载调度到离用户更近的节点上。
  3. 隔离性: 可以使用一些特殊标签将敏感的工作负载调度到专门的隔离节点上。

为什么只用 NodeSelector 是不够的?

NodeSelector 是 Kubernetes 中最简单的调度约束工具,但它有以下局限性:

  1. 无法表达复杂条件: NodeSelector 只支持简单的键值对,并且只能用 AND 逻辑,无法表达更复杂的调度规则(例如 OR 或 NOT)。

  2. 不够灵活: 如果节点标签被更改,Pod 不会自动重新调度到符合新条件的节点。

  3. 替代方案:

    更强大的调度工具包括:

    • Node Affinity:支持更复杂的调度规则,并允许使用 OR/NOT 等逻辑。
    • Taints and Tolerations:允许特定 Pod 避免或容忍某些节点的特殊污点条件。
    • Custom Schedulers:你可以实现自己的调度器来处理更复杂的需求。

在 Kubernetes 中,NodeAffinity (节点亲和性) 是一种调度约束,用于决定某些 Pod 应该运行在哪些节点上。它是 Pod 调度器的重要配置之一,可以帮助用户控制 Pod 在集群中的分布。通过 NodeAffinity,用户可以定义 Pod 如何选择合适的节点。它是 Kubernetes 中 “亲和性和反亲和性” 特性的一部分。

下面我们详细讲解一下 NodeAffinity 的概念、工作原理和使用方法:


NodeAffinity

1. 什么是 NodeAffinity

NodeAffinity 是一个与节点标签匹配的调度策略。它允许你基于节点的标签设置调度规则,从而指定:

  • Pod 更倾向于调度到哪些节点上(软约束)
  • Pod 必须调度到哪些节点上(硬约束)

NodeAffinity 是 Pod 的属性,定义在 Pod 的 Spec 中。调度器会根据 NodeAffinity 的规则,在集群中选择合适的节点来运行 Pod。


2. NodeAffinity 的分类

NodeAffinity 有两种主要类型:

  1. 硬约束 (requiredDuringSchedulingIgnoredDuringExecution)

    • Pod 必须调度到符合条件的节点上。
    • 如果没有符合条件的节点,Pod 会一直待在 Pending 状态,直到有满足条件的节点出现。
    • 等价于硬性规则。
  2. 软约束 (preferredDuringSchedulingIgnoredDuringExecution)

    • Pod 优先调度到符合条件的节点,但如果没有符合条件的节点,也可以调度到其他节点。
    • 等价于建议性规则。

键名解释:

  • DuringScheduling:调度时检查规则是否满足。
  • IgnoredDuringExecution:调度完成后,不再检查规则是否持续满足。

3. NodeAffinity 的语法

NodeAffinity 的定义在 Pod Spec 的 affinity.nodeAffinity 字段下,采用 MatchExpressionsMatchFields 的形式。以下是一个典型的 YAML 配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-example
labels:
app: demo
spec:
containers:
- name: demo-container
image: nginx
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬约束
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node1
- node2
preferredDuringSchedulingIgnoredDuringExecution: # 软约束
- weight: 1
preference:
matchExpressions:
- key: disktype
operator: In
values:
- ssd

解析:

  1. 硬约束:requiredDuringSchedulingIgnoredDuringExecution

    • 定义了 Pod 只允许调度到 kubernetes.io/hostnamenode1node2 的节点。
    • 如果没有满足条件的节点,Pod 不会调度。
  2. 软约束:preferredDuringSchedulingIgnoredDuringExecution

    • 定义了 Pod 优先调度到 disktypessd 的节点。
    • 如果没有满足条件的节点,Pod 仍然可以调度到其他节点。

4. NodeAffinity 的高级配置

4.1 matchExpressions 的语法

matchExpressions 是常见的表达式,用来定义约束规则。它支持以下操作符:

  • In:匹配标签的值在指定的值列表中。
  • NotIn:匹配标签的值不在指定的值列表中。
  • Exists:匹配节点是否具有某个标签(不用指定值)。
  • DoesNotExist:匹配节点是否没有某个标签。
  • Gt:匹配标签的值大于指定值。
  • Lt:匹配标签的值小于指定值。

示例:

1
2
3
4
5
6
7
8
matchExpressions:
- key: environment
operator: In
values:
- production
- staging
- key: disktype
operator: Exists

4.2 多标签规则

NodeAffinity 支持多标签条件的组合。一个 nodeSelectorTerms 的条件是 “或” 的关系(满足任意一个条件即可)。而 matchExpressions 中的多个表达式是 “且” 的关系(需要同时满足)。


PodAffinity

在 Kubernetes 中,PodAffinity 是调度器中一种高级的调度约束,用于控制 Pod 在集群中的调度位置。它允许用户定义一个 Pod 应该尽量调度在哪些节点上,基于与其他 Pod 的亲和性规则。通过 PodAffinity,用户可以确保某些服务之间的 Pod 被调度到更接近的位置,优化性能、减少网络延迟或者提高数据访问效率。

Kubernetes 为 PodAffinity 提供了以下两个类型的亲和性:

  • Pod Affinity (亲和性):指示 Pod 应该调度到与特定 Pod 接近的节点。
  • Pod Anti-Affinity (反亲和性):指示 Pod 应该调度到远离特定 Pod 的节点。

PodAffinity 的应用场景

通过 PodAffinity,可以实现以下常见需求:

  1. 加强服务间的局部性。例如,某些微服务需要与缓存服务位于同一个节点或同一个拓扑域(区域、可用区)以提高性能。
  2. 数据密集型应用需要靠近数据所在的节点。
  3. 降低集群内的网络延迟。
  4. 节约跨节点流量的带宽开销。

而通过 PodAntiAffinity,可以实现以下需求:

  1. 增强高可用性,确保副本分散在不同的节点上,避免单点故障。
  2. 降低资源争用的可能性,避免同类型 Pod 集中在一个节点上。

PodAffinity 的核心字段

PodAffinity 是 Pod 的调度策略的一部分,它位于 Pod 的 spec.affinity 字段下。主要相关的字段如下:

1
2
3
4
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬性亲和性规则
preferredDuringSchedulingIgnoredDuringExecution: # 软性亲和性规则

类似的结构也适用于 PodAntiAffinity

1. requiredDuringSchedulingIgnoredDuringExecution

表示强制规则(硬性约束),如果规则无法被满足,Pod 将不会被调度到某个节点上。
这个字段适合用在要求非常严格的场景,比如某些关键服务之间的紧密绑定。

2. preferredDuringSchedulingIgnoredDuringExecution

表示偏好规则(软性约束),如果规则能够满足,调度器会优先选择满足条件的节点;但如果无法满足,Pod 仍然可以被调度到其他节点上。
这个字段适合用在需要优化但不强制的场景。


PodAffinity 的使用示例

1. 配置 Pod Affinity(亲和性)

假如你希望一个应用的 Pod 调度到与指定标签 app=webserver 的 Pod 所在的节点上,可以使用以下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: webserver
topologyKey: "kubernetes.io/hostname"
containers:
- name: nginx
image: nginx

字段解释:

  • labelSelector: 指定需要与哪些 Pod 匹配,这里是所有具备 app=webserver 标签的 Pod。
  • topologyKey: 指定亲和性的拓扑域。在这个例子中,它是 kubernetes.io/hostname,表示 Pod 会被调度到与目标 Pod 位于相同主机的节点上。

2. 配置 Pod Anti-Affinity(反亲和性)

如果你希望 Pod 避开与指定标签 app=webserver 的 Pod 所在的节点,可以使用如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: webserver
topologyKey: "kubernetes.io/hostname"
containers:
- name: nginx
image: nginx

这会确保 Pod 避开任何有 app=webserver 的节点。


结合 TopologyKey 的使用

Kubernetes 中的 topologyKey 是一个关键字段,它指定亲和性或反亲和性规则所应用的拓扑域。常见的值包括:

  1. kubernetes.io/hostname - 节点级别的亲和性。
  2. failure-domain.beta.kubernetes.io/zone - 可用区级别的亲和性。
  3. failure-domain.beta.kubernetes.io/region - 区域级别的亲和性。

假设你希望 Pod 调度到与目标 Pod 位于相同的可用区,而不是完全相同的节点,你可以将 topologyKey 设置为 failure-domain.beta.kubernetes.io/zones


注意事项

  1. 性能开销:

    • 使用复杂的亲和性规则可能会显著增加调度器的计算负担,因为调度器需要检查每个节点是否满足约束。
    • 如果集群规模较大、规则复杂,可能会对调度器性能产生负面影响。
  2. 节点资源竞争:

    • 当 PodAffinity 被强制使用为硬性约束时,可能导致 Pod 在资源不足时无法调度。
  3. 与节点亲和性(NodeAffinity)的对比:

    • NodeAffinity 是基于节点标签的调度规则,而 PodAffinity 是基于 Pod 标签的调度规则。
    • 两者可以结合使用,形成更灵活的调度策略。

在 Kubernetes 中,TaintsTolerations 是用来控制 Pod 调度的机制。通过它们,集群管理员可以实现更细粒度的调度控制,确保工作负载被调度到最合适的节点上执行。

Taints 和 Tolerations

1. Taints 和 Tolerations 概念

Taints(污点)

Taints 是对节点(Node)设置的一种标记,用来表示该节点存在某种“特殊性”,不能随便调度任意的 Pod。只有那些显式声明“能够容忍”这些污点的 Pod 才能调度到这些节点上。

每个 Taint 由以下三部分组成:

  • Key:一个字符串,表示污点的键。
  • Value:一个字符串,表示污点的值。(可选)
  • Effect:污点的影响,表示不满足某些条件的 Pod 会被如何处理。

Effect 的取值有以下三种:

  • NoSchedule: 不允许 Pod 被调度到这个节点上。
  • PreferNoSchedule: 尽量不要调度 Pod 到这个节点上,但不是强制的。
  • NoExecute: 如果 Pod 已经运行在这个节点上,也会驱逐它,同时阻止新的 Pod 被调度到这个节点上。

创建一个 Taint 的语法是:

1
kubectl taint nodes <node-name> <key>=<value>:<effect>

例如:

1
kubectl taint nodes node1 dedicated=special-workload:NoSchedule

这表示节点 node1 上有一个污点 dedicated=special-workload,任何没有适当 Toleration 的 Pod 都不能调度到该节点。

Tolerations(容忍)

Tolerations 是对 Pod 的配置,用于声明该 Pod 可以“容忍”哪些节点上的污点,从而允许自己被调度到这些节点上。

每个 Toleration 的结构如下:

  • Key: 和节点的 Taints 的 Key 对应。
  • Operator: 表示如何匹配 Key,可能的值有 EqualExists
    • Equal: Key 和 Value 必须完全相等。
    • Exists: Key 必须存在,而不要求 Value。
  • Value: 和节点的 Taints 的 Value 对应。(可选)
  • Effect: 与节点上的污点的 Effect 对应。
  • TolerationSeconds: 仅对 NoExecute 生效。如果指定了该字段,表示 Pod 在被驱逐前可以容忍这个污点的时间。

一个 Toleration 的 YAML 配置示例:

1
2
3
4
5
tolerations:
- key: "dedicated"
operator: "Equal"
value: "special-workload"
effect: "NoSchedule"

2. Taints 和 Tolerations 的工作机制

  • 当一个节点上有污点(Taint)时,Kubernetes 调度器会检查所有待调度的 Pod,只有那些有匹配的 Toleration 的 Pod 才会被调度到该节点。
  • 如果 Pod 没有匹配的 Toleration,则调度器会跳过这个节点,继续寻找其他合适的节点。

需要注意的是:

  • Taints 是主动的:节点上有污点时,会对不满足条件的 Pod 施加限制。
  • Tolerations 是被动的:Pod 上有 Toleration 时,表明它可以容忍某种污点,但它不会主动要求调度到有特定污点的节点上。

3. 示例

3.1 在特定节点上添加 Taint

假设我们希望将节点 node1 设为专用节点,用于跑某些特定工作负载(如后台任务)。我们可以给这个节点添加如下 Taint:

1
kubectl taint nodes node1 dedicated=background-tasks:NoSchedule

3.2 配置 Pod 的 Toleration

假设我们有一个 Pod 专门用于后台任务,我们希望它能够调度到 node1 上。可以在 Pod 的 YAML 文件中添加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Pod
metadata:
name: my-background-task
spec:
tolerations:
- key: "dedicated"
operator: "Equal"
value: "background-tasks"
effect: "NoSchedule"
containers:
- name: my-container
image: busybox
command: ["sleep", "3600"]

3.3 添加 NoExecute 的场景

假设我们希望标记一个节点为不可用(例如硬件维护时),并且希望正在运行的 Pod 能够在 60 秒内迁移到其他节点。可以这样设置:

1
kubectl taint nodes node1 maintenance=true:NoExecute

然后,在 Pod 的 Toleration 中,我们可以指定容忍时间:

1
2
3
4
5
6
tolerations:
- key: "maintenance"
operator: "Equal"
value: "true"
effect: "NoExecute"
tolerationSeconds: 60

此时,Pod 会在 60 秒内迁移到其他节点。

4. 应用场景

场景 1:专用节点调度

通过设置 TaintsTolerations,可以实现节点专用于某些特殊工作负载(如 GPU 工作负载、日志收集等)。

场景 2:节点维护

当节点需要下线维护时,可以添加 NoExecute 类型的 Taint,将该节点上的所有 Pod 驱逐,同时阻止新 Pod 调度到该节点。

场景 3:负载隔离

通过对节点添加 PreferNoSchedule 类型的 Taint,可以降低调度到该节点的优先级,但仍然允许在特殊情况下将 Pod 调度到该节点。

5. 注意事项

  1. Taints 和 Tolerations 并不主动分配工作负载到特定节点:它们只是限制哪些节点可以运行哪些 Pod。如果需要主动分配,可以结合 nodeSelectornodeAffinity
  2. Tolerations 只允许调度,不强制调度:即使 Pod 有符合条件的 Toleration,也可能因为资源不足等原因无法调度到对应节点。
  3. TolerationSeconds 仅适用于 NoExecute:当节点添加 NoExecute 类型的 Taint 时,容忍时间才会生效。

故障转移

Kubernetes 通过 TaintsTolerations 配合来实现节点的故障转移(Failover),当某些节点出现故障(如离线或不可用)时,Taints 可以标记这些节点不适合继续运行 Pod,而调度器(Scheduler)会将受影响的 Pod 迁移到其他可用节点,从而实现故障转移。

故障转移的核心机制

在 Kubernetes 中,故障转移通常依赖以下几个机制:

  1. 节点不可用的检测
    Kubernetes 使用 kubeletNode Controller 来监控节点的可用性。如果节点未在指定时间内报告心跳(默认 40 秒内未响应),Node Controller 会将该节点标记为不可用,进而触发相应的处理。

  2. 节点的 NoExecute Taint
    当节点被标记为不可用时,Kubernetes 会自动为该节点添加一个特殊的 Taint:

    1
    node.kubernetes.io/unreachable:NoExecute

    这是一个系统级的 Taint,表示该节点已不可达。

  3. Pod 的 Tolerations
    如果部署的 Pod 没有显式声明可以容忍这个 Taint,它们会被驱逐(Evicted)并重新调度到其他健康的节点上。

  4. 驱逐与重新调度
    调度器检测到 Pod 因为 Taints 被驱逐后,会在其他节点上重新调度这些 Pod(前提是集群中有其他符合条件的健康节点)。


故障转移中的 Tolerations 作用

Tolerations 是用来定义 Pod 能够容忍哪些类型的 Taints 的。如果一个 Pod 声明了能够容忍 NoExecute 类型的 Taint,会影响 Pod 的行为:

  1. 没有 Tolerations

    • 当节点被标记为不可用(添加 NoExecute Taint)时,Pod 会立即被驱逐,并重新调度到其他健康节点。
  2. 有 Toleration,但没有 tolerationSeconds

    • 如果 Pod 声明了可以容忍 node.kubernetes.io/unreachable:NoExecute 的 Taint,但没有指定 tolerationSeconds,它会无限期地留在该节点上,即使节点不可达也不会被驱逐。
  3. 有 Toleration,且指定了 tolerationSeconds

    • 如果 Pod 声明了可以容忍 node.kubernetes.io/unreachable:NoExecute 的 Taint,并设置了 tolerationSeconds,Pod 会在节点不可达的指定时间后被驱逐。例如:
      1
      2
      3
      4
      5
      tolerations:
      - key: "node.kubernetes.io/unreachable"
      operator: "Exists"
      effect: "NoExecute"
      tolerationSeconds: 60
      这表示该 Pod 当节点不可达时,可以容忍 60 秒,超过这个时间后会被驱逐。

故障转移示例

1. 模拟节点故障

假设有一个 Pod 被调度到了节点 node1,我们可以通过以下命令模拟该节点的不可用状态:

1
2
# 标记节点为不可用
kubectl taint nodes node1 node.kubernetes.io/unreachable:NoExecute

这和节点因为健康检查失败被自动添加 NoExecute Taint 的情况等价。

2. Pod 的行为

  • 如果 Pod 没有定义任何 Toleration:

    • Pod 会被立即驱逐并重新调度到其他健康节点(如果有可用节点)。
  • 如果 Pod 有定义以下 Toleration:

    1
    2
    3
    4
    5
    tolerations:
    - key: "node.kubernetes.io/unreachable"
    operator: "Exists"
    effect: "NoExecute"
    tolerationSeconds: 30
    • Pod 在 node1 上可以继续运行,但最多容忍 30 秒。如果在 30 秒内节点状态恢复,Pod 不会被驱逐;如果超过 30 秒节点仍不可用,Pod 会被驱逐并重新调度。

故障转移的工作流程

以下是 Kubernetes 故障转移的完整工作流程:

  1. 节点检测与标记

    • kubelet 定期向 kube-apiserver 发送心跳。如果节点的心跳在 40 秒内没有响应(通过 node-status-update-frequencynode-monitor-grace-period 参数控制),Node Controller 会将节点标记为 NotReady 并添加一个 NoExecute Taint:
      1
      node.kubernetes.io/unreachable:NoExecute
  2. Pod 驱逐逻辑

    • 对于运行在该节点上的 Pod,调度器会检查它们是否有 Toleration 能容忍 NoExecute Taint:
      • 没有 Tolerations 的 Pod 会被立刻驱逐。
      • tolerationSeconds 的 Pod 会在指定时间后被驱逐。
      • 有 Tolerations 且无限期容忍的 Pod 会继续保留在节点上(即使节点不可用)。
  3. 重新调度

    • 被驱逐的 Pod 状态会变为 Pending,调度器会尝试将 Pod 分配到其他符合条件的健康节点。
    • 如果集群中没有足够的资源,Pod 会一直处于 Pending 状态,直到有可用资源。
  4. 节点恢复

    • 如果节点恢复(心跳正常),Taint 会被自动移除,新的 Pod 可以调度到该节点。

实际使用场景

  1. 无状态应用(Stateless Applications)

    • 对于无状态应用(如 Web 服务器),通常不会定义 NoExecute Taint 的 Tolerations。当节点不可用时,这些 Pod 会被快速驱逐并重新调度到其他节点。
  2. 有状态应用(Stateful Applications)

    • 对于有状态应用(如数据库),可以使用 tolerationSeconds 为 Pod 设置一个宽限期,以允许节点临时不可用的情况下减少不必要的驱逐。例如,数据库可能会因为网络抖动而短时间失联,此时无需立即驱逐 Pod。
  3. 关键任务负载(Critical Workloads)

    • 如果某些 Pod 是关键任务,且对节点的不可用有更高的容忍度,可以为其设置无限期容忍:
      1
      2
      3
      4
      tolerations:
      - key: "node.kubernetes.io/unreachable"
      operator: "Exists"
      effect: "NoExecute"

调优建议

  1. 合理设置心跳间隔

    • 如果节点的不可用只是暂时的(例如网络抖动),可以通过调整 node-monitor-grace-period 参数来避免误判。
  2. 为不同类型的应用设置不同的 tolerationSeconds

    • 无状态应用可以设置较短的容忍时间(甚至不容忍)。
    • 有状态应用可以设置较长的容忍时间,避免频繁驱逐。
  3. 结合 Pod 的 Liveness Probe 和 Readiness Probe

    • 确保 Pod 本身的健康状态,避免将调度问题与 Pod 内部问题混淆。

在 Kubernetes 中,调度器是负责将 Pod 分配到集群中的合适节点上的组件。对于复杂的生产环境,可能会遇到调度效率、资源竞争和容灾等挑战,Kubernetes 提供了多种功能来优化调度策略,其中包括 PriorityClass(优先级调度)多调度器(Multiple Schedulers)


PriorityClass

1. 优先级调度(PriorityClass)

PriorityClass 是 Kubernetes 提供的一种机制,用于给不同 Pod 设置调度优先级。它允许用户指定某些工作负载的重要性,从而确保高优先级的工作负载能够获得优先调度。

关键概念

  • PriorityClass 对象: 定义了优先级名称和其对应的整数字段(value),value 越大,优先级越高。
  • 系统优先级范围:
    • system-cluster-criticalsystem-node-critical 是 Kubernetes 预定义的最高优先级,通常用于核心组件(如 corednskube-proxy)。
    • 自定义的优先级值取值范围为正整数,通常由管理员定义。
  • 抢占(Preemption):
    • 如果没有足够的资源调度高优先级 Pod,会触发抢占机制,驱逐低优先级的 Pod 为高优先级的 Pod 腾出资源。

示例

  1. 定义一个 PriorityClass:

    1
    2
    3
    4
    5
    6
    7
    apiVersion: scheduling.k8s.io/v1
    kind: PriorityClass
    metadata:
    name: high-priority
    value: 1000
    globalDefault: false
    description: "This priority class is for high-priority workloads."
    • value: 优先级值。
    • globalDefault: 是否默认应用到没有指定优先级的 Pod。
  2. 使用 PriorityClass 的 Pod 定义:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    apiVersion: v1
    kind: Pod
    metadata:
    name: high-priority-pod
    spec:
    priorityClassName: high-priority
    containers:
    - name: nginx
    image: nginx

应用场景

  • 关键工作负载保障: 确保核心服务(如监控、日志)优先获得资源。
  • 抢占低优先级任务: 在资源紧张时,驱逐低优先级的任务以调度高优先级任务。

生产问题分析

  • 调度放大效应: 如图所示,当集群资源紧张或 Node 出现问题时,优先级调度可能导致某些 Pod(如低优先级的批量任务)无法被调度。
  • 长时间调度失败问题: 调度缓存(scheduler cache)滞后可能导致调度错误。

2. 多调度器(Multiple Schedulers)

Kubernetes 默认使用 kube-scheduler 作为调度器,但在某些场景下,我们可能需要自定义调度逻辑。这时可以部署多个调度器,每个调度器负责调度特定的 Pod 子集。

关键概念

  • 调度器名称(SchedulerName): 每个调度器需要有一个唯一的名称,Pod 可以通过 spec.schedulerName 指定其使用哪个调度器。
  • 自定义调度器: 使用调度框架(如 Kubernetes 的调度器扩展 API 或自定义调度器)实现需求。

示例

  1. 部署多个调度器

    • 部署默认调度器 kube-scheduler
    • 部署一个自定义调度器。

    自定义调度器示例配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    apiVersion: v1
    kind: Pod
    metadata:
    name: custom-scheduler
    spec:
    containers:
    - name: custom-scheduler
    image: custom-scheduler-image
    args:
    - --scheduler-name=custom-scheduler
  2. 指定 Pod 使用自定义调度器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    apiVersion: v1
    kind: Pod
    metadata:
    name: custom-scheduled-pod
    spec:
    schedulerName: custom-scheduler
    containers:
    - name: nginx
    image: nginx

应用场景

  • 隔离调度逻辑: 不同调度器负责不同类型的任务(如批量任务、在线服务)。
  • 性能优化: 当单一调度器无法满足高并发 Pod 创建需求时,使用多调度器分担调度压力。
  • 自定义调度需求: 特定业务场景下需要定制复杂的调度策略,如基于 GPU 资源、拓扑感知等。

生产问题分析

  • 小集群高并发调度问题: 如图所示,在高并发情况下,调度器可能因为缓存更新滞后导致调度失败,增加一个专用调度器可能缓解问题。
  • 危险 Pod 爆炸问题: 自定义调度器可以用来隔离潜在的危险 Pod,从而避免调度到非预期的 Node。

3. 总结生产经验

根据图中的生产经验提示,我们可以结合优先级调度和多调度器功能来优化生产环境。

  • 小集群高并发场景: 通过多调度器分摊调度压力,或优化调度器的缓存刷新机制。
  • 资源倾斜与放大效应: 使用 PriorityClass 控制任务优先级,同时监控 Pod 的分布,避免过载单一 Node。
  • 危险 Pod 防护: 结合资源配额(ResourceQuota)和自定义调度器,隔离潜在的恶意或异常工作负载。

通过这些改进,可以有效提升系统的稳定性和调度效率,同时避免因调度问题引发的级联故障。


kubernetes_scheduler
https://mfzzf.github.io/2025/03/18/kubernetes-scheduler/
作者
Mzzf
发布于
2025年3月18日
许可协议