kubernetes_scheduler
Kube-Schedular
简介
kube-scheduler
是 Kubernetes 核心组件之一,负责 Pod 的调度(Scheduling)。简而言之,它的主要职责是决定一个未绑定到节点的 Pod 应该被分配到哪个节点上运行。调度器在 Kubernetes 中起到“任务分派员”的作用,其调度的核心目标是满足工作负载的资源需求(如 CPU、内存等),并同时遵循用户定义的调度策略和约束(如 Pod 配置的 nodeSelector
、affinity
等)。
工作流程
一、Pod 监听:
初始化 Informer:
kube-scheduler
在启动时,会创建一个Pod Informer
,用于监听和缓存 Pod 的变化(包括新增、修改、删除事件)。只监听那些未绑定到节点(
spec.nodeName
为空)的 Pod。1
2
3
4
5
6
7
8
9
10
11
12
13
14func NewScheduler(...) (*Scheduler, error) {
...
// 创建 Pod Informer
podInformer := informerFactory.Core().V1().Pods()
// 初始化调度器
sched := &Scheduler{
podQueue: queue, // 调度队列
podInformer: podInformer, // Pod Informer
podLister: podInformer.Lister() // 用于从缓存中获取 Pod 列表
...
}
...
}
通过 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
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 |
|
五、决策和绑定(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 所有容器的
requests
和limits
必须完全相等。特性:这类 Pod 通常被视为最高优先级资源请求,因此在资源争夺时被保留。
场景:适用于需要强资源保证的关键性应用。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15apiVersion: 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
设置了,但requests
和limits
不完全相等,则 Pod 被归为Burstable
。特性:该类 Pod 会优先获取至少等于
requests
的资源,其余资源可在容量溢出时被收回。场景:适合对资源核心需求较低,但能够在负载高峰期动态扩展的场景。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15apiVersion: 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 所有容器都没有配置
requests
或limits
,则它属于BestEffort
。特性:属于最低优先级 Pod,仅在其他资源有剩余时可分配资源。
场景:适用于非核心、无资源保障需求的后备工作负载。
示例:
1
2
3
4
5
6
7
8apiVersion: 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。
- 考虑结合
Taints
和Tolerations
、资源亲和性等规则提高具体调度的确定性。
举例:调度阶段中的 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 |
|
3. 预留关键性资源节点
Kubernetes 支持通过 kube-reserved
、system-reserved
等方式预留关键性资源,保证平台本身稳定运行。
4. 配额管理
使用 ResourceQuotas 限制低 QoS 的资源消耗,如限制 BestEffort
Pod 数量,确保资源可为高 QoS 的 Pod 使用。
API 对象:requests limits
在 Kubernetes 中,requests
和 limits
是 Pod 或容器级别的资源管理功能,用于定义对 CPU 和内存等资源的需求和限制。这两个参数是 Kubernetes 调度器和 Kubelet 的关键配置,它们分别用于调度和运行时资源管理。
1. 什么是 requests
和 limits
1.1 requests
- 定义: 容器运行时的最低资源需求。Kubernetes 调度器在调度 Pod 时会使用
requests
的值来决定是否有足够的资源可用。 - 作用:
- 决定 Pod 是否能被调度到某个 Node。
- 一旦 Pod 被调度到某个 Node,Node 上必须预留至少
requests
定义的资源量。
1.2 limits
- 定义: 容器运行时的资源上限。Kubelet 和容器运行时会根据
limits
限制容器使用的资源量,超过此限制可能会被杀死或降速。 - 作用:
- 防止容器使用的资源超出指定的上限,从而保护其他容器的稳定性。
配置示例
以一个简单的 Pod 配置为例:
1 |
|
解释:
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 |
|
示例:
- 假设一个节点有 4 核 CPU 和 8 Gi 内存。
- 已经运行了 2 个 Pod,分别请求了以下资源:
- Pod1:
requests.cpu=1
和requests.memory=2Gi
- Pod2:
requests.cpu=1
和requests.memory=2Gi
- Pod1:
- 节点剩余资源:
- 剩余 CPU:
4 - (1 + 1) = 2
- 剩余内存:
8Gi - (2Gi + 2Gi) = 4Gi
- 剩余 CPU:
- 如果新创建一个 Pod 需要
requests.cpu=3
,调度器会过滤掉这个节点,因为剩余 CPU 不足。
2.2 优化节点选择
在过滤掉不满足 requests
的节点后,调度器会根据调度策略(例如:最少资源利用策略 或 均衡策略)选择最佳节点。例如:
- 找出当前负载最小的节点。
- 确保资源分配均匀,避免资源热点。
3. Kubelet 和容器运行时如何使用 limits
在 Pod 被调度到节点上之后,Kubelet 和容器运行时会使用 limits
来限制容器运行时的资源使用:
3.1 CPU 限制
limits.cpu
通过 Cgroup 的 CPU Shares 和 CPU Quota 实现:- CPU Shares: 控制 CPU 调度的优先级。例如,
requests.cpu=500m
会分配较低优先级,但如果节点空闲,容器可以使用更多 CPU。 - CPU Quota: 硬性限制 CPU 使用总量。例如,
limits.cpu=500m
意味着容器最多只能使用 50% 的 CPU 核心时间。
- CPU Shares: 控制 CPU 调度的优先级。例如,
相关 cgroup 配置路径:
1 |
|
3.2 内存限制
limits.memory
是一个硬限制,通过 Cgroup 的 memory limit 配置。- 如果容器超出
limits.memory
的值,会触发 OOM(Out of Memory)杀死容器。
相关 cgroup 配置路径:
1 |
|
4. 使用 requests
和 limits
的最佳实践
合理分配
requests
和limits
:requests
应根据容器的最低需求来设置,避免调度失败。limits
应根据容器的最大允许使用量来设置,避免资源抢占。
避免
requests
和limits
间差距过大:- 如果
limits
远大于requests
,容易导致节点资源超分配,运行时引发不稳定。 - 如果
requests
远大于实际需求,可能导致资源浪费,降低集群整体利用率。
- 如果
结合资源配额(Resource Quotas)和限制(Limit Ranges):
- 使用 Resource Quotas 在命名空间层面限制资源总量。
- 使用 Limit Ranges 强制为 Pod 配置合理的
requests
和limits
。
将 Pod 调度到指定的 Node 上
NodeSelector
是 Kubernetes 中用来将 Pod 调度到特定的 Node 上的一种机制。它本质上是一种调度约束,让你可以通过为 Pod 定义一个条件来指定只允许它运行在满足该条件的节点上。
在 Kubernetes 中,Node 通常会有一组标签(labels),比如:
1 |
|
而 NodeSelector
是 Pod 的 spec
部分中的一个字段,允许你通过指定这些标签来选择目标节点。
NodeSelector
的工作原理
NodeSelector
的核心是通过键值对匹配来选择 Node。Kubernetes 的调度器会检查集群中所有可用的节点,只有满足 NodeSelector
条件的节点才会被作为候选节点来运行该 Pod。
使用示例
1. 定义节点标签
给一个节点打上标签。假设我们有一个节点名为 node1
,我们通过以下命令给它加上一个标签:
1 |
|
2. 配置 Pod 的 NodeSelector
定义一个 Pod,并使用 nodeSelector
来选择节点:
1 |
|
在这个例子中,nodeSelector
指定了 disktype: ssd
,所以 Kubernetes 只会尝试将该 Pod 调度到具有标签 disktype=ssd
的节点上。
多个条件的 NodeSelector
NodeSelector
可以指定多个键值对条件,所有条件必须同时满足。例如:
1 |
|
这种情况下,目标节点必须同时满足以下条件:
- 标签
disktype=ssd
- 标签
region=us-west
注意: NodeSelector
的匹配条件是 AND 逻辑,所有条件都必须满足,不能用 OR 逻辑。
使用场景
- 性能优化: 你可以将特定的工作负载调度到具有高性能硬件的节点上,比如 SSD 存储或高 CPU 内核的节点。
- 地理分布: 通过标签标识区域(region)和可用区(zone),你可以将工作负载调度到离用户更近的节点上。
- 隔离性: 可以使用一些特殊标签将敏感的工作负载调度到专门的隔离节点上。
为什么只用 NodeSelector
是不够的?
NodeSelector
是 Kubernetes 中最简单的调度约束工具,但它有以下局限性:
无法表达复杂条件:
NodeSelector
只支持简单的键值对,并且只能用 AND 逻辑,无法表达更复杂的调度规则(例如 OR 或 NOT)。不够灵活: 如果节点标签被更改,Pod 不会自动重新调度到符合新条件的节点。
替代方案:
更强大的调度工具包括:
- 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
有两种主要类型:
硬约束 (
requiredDuringSchedulingIgnoredDuringExecution
)- Pod 必须调度到符合条件的节点上。
- 如果没有符合条件的节点,Pod 会一直待在 Pending 状态,直到有满足条件的节点出现。
- 等价于硬性规则。
软约束 (
preferredDuringSchedulingIgnoredDuringExecution
)- Pod 优先调度到符合条件的节点,但如果没有符合条件的节点,也可以调度到其他节点。
- 等价于建议性规则。
键名解释:
DuringScheduling
:调度时检查规则是否满足。IgnoredDuringExecution
:调度完成后,不再检查规则是否持续满足。
3. NodeAffinity
的语法
NodeAffinity
的定义在 Pod Spec 的 affinity.nodeAffinity
字段下,采用 MatchExpressions
或 MatchFields
的形式。以下是一个典型的 YAML 配置示例:
1 |
|
解析:
硬约束:
requiredDuringSchedulingIgnoredDuringExecution
- 定义了 Pod 只允许调度到
kubernetes.io/hostname
为node1
或node2
的节点。 - 如果没有满足条件的节点,Pod 不会调度。
- 定义了 Pod 只允许调度到
软约束:
preferredDuringSchedulingIgnoredDuringExecution
- 定义了 Pod 优先调度到
disktype
为ssd
的节点。 - 如果没有满足条件的节点,Pod 仍然可以调度到其他节点。
- 定义了 Pod 优先调度到
4. NodeAffinity
的高级配置
4.1 matchExpressions
的语法
matchExpressions
是常见的表达式,用来定义约束规则。它支持以下操作符:
In
:匹配标签的值在指定的值列表中。NotIn
:匹配标签的值不在指定的值列表中。Exists
:匹配节点是否具有某个标签(不用指定值)。DoesNotExist
:匹配节点是否没有某个标签。Gt
:匹配标签的值大于指定值。Lt
:匹配标签的值小于指定值。
示例:
1 |
|
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
,可以实现以下常见需求:
- 加强服务间的局部性。例如,某些微服务需要与缓存服务位于同一个节点或同一个拓扑域(区域、可用区)以提高性能。
- 数据密集型应用需要靠近数据所在的节点。
- 降低集群内的网络延迟。
- 节约跨节点流量的带宽开销。
而通过 PodAntiAffinity
,可以实现以下需求:
- 增强高可用性,确保副本分散在不同的节点上,避免单点故障。
- 降低资源争用的可能性,避免同类型 Pod 集中在一个节点上。
PodAffinity
的核心字段
PodAffinity
是 Pod 的调度策略的一部分,它位于 Pod 的 spec.affinity
字段下。主要相关的字段如下:
1 |
|
类似的结构也适用于 PodAntiAffinity
。
1. requiredDuringSchedulingIgnoredDuringExecution
表示强制规则(硬性约束),如果规则无法被满足,Pod 将不会被调度到某个节点上。
这个字段适合用在要求非常严格的场景,比如某些关键服务之间的紧密绑定。
2. preferredDuringSchedulingIgnoredDuringExecution
表示偏好规则(软性约束),如果规则能够满足,调度器会优先选择满足条件的节点;但如果无法满足,Pod 仍然可以被调度到其他节点上。
这个字段适合用在需要优化但不强制的场景。
PodAffinity 的使用示例
1. 配置 Pod Affinity(亲和性)
假如你希望一个应用的 Pod 调度到与指定标签 app=webserver
的 Pod 所在的节点上,可以使用以下配置:
1 |
|
字段解释:
labelSelector
: 指定需要与哪些 Pod 匹配,这里是所有具备app=webserver
标签的 Pod。topologyKey
: 指定亲和性的拓扑域。在这个例子中,它是kubernetes.io/hostname
,表示 Pod 会被调度到与目标 Pod 位于相同主机的节点上。
2. 配置 Pod Anti-Affinity(反亲和性)
如果你希望 Pod 避开与指定标签 app=webserver
的 Pod 所在的节点,可以使用如下配置:
1 |
|
这会确保 Pod 避开任何有 app=webserver
的节点。
结合 TopologyKey 的使用
Kubernetes 中的 topologyKey
是一个关键字段,它指定亲和性或反亲和性规则所应用的拓扑域。常见的值包括:
kubernetes.io/hostname
- 节点级别的亲和性。failure-domain.beta.kubernetes.io/zone
- 可用区级别的亲和性。failure-domain.beta.kubernetes.io/region
- 区域级别的亲和性。
假设你希望 Pod 调度到与目标 Pod 位于相同的可用区,而不是完全相同的节点,你可以将 topologyKey
设置为 failure-domain.beta.kubernetes.io/zone
s
注意事项
性能开销:
- 使用复杂的亲和性规则可能会显著增加调度器的计算负担,因为调度器需要检查每个节点是否满足约束。
- 如果集群规模较大、规则复杂,可能会对调度器性能产生负面影响。
节点资源竞争:
- 当 PodAffinity 被强制使用为硬性约束时,可能导致 Pod 在资源不足时无法调度。
与节点亲和性(NodeAffinity)的对比:
NodeAffinity
是基于节点标签的调度规则,而PodAffinity
是基于 Pod 标签的调度规则。- 两者可以结合使用,形成更灵活的调度策略。
在 Kubernetes 中,Taints
和 Tolerations
是用来控制 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 |
|
例如:
1 |
|
这表示节点 node1
上有一个污点 dedicated=special-workload
,任何没有适当 Toleration
的 Pod 都不能调度到该节点。
Tolerations(容忍)
Tolerations
是对 Pod 的配置,用于声明该 Pod 可以“容忍”哪些节点上的污点,从而允许自己被调度到这些节点上。
每个 Toleration 的结构如下:
- Key: 和节点的 Taints 的 Key 对应。
- Operator: 表示如何匹配 Key,可能的值有
Equal
和Exists
。Equal
: Key 和 Value 必须完全相等。Exists
: Key 必须存在,而不要求 Value。
- Value: 和节点的 Taints 的 Value 对应。(可选)
- Effect: 与节点上的污点的 Effect 对应。
- TolerationSeconds: 仅对
NoExecute
生效。如果指定了该字段,表示 Pod 在被驱逐前可以容忍这个污点的时间。
一个 Toleration 的 YAML 配置示例:
1 |
|
2. Taints 和 Tolerations 的工作机制
- 当一个节点上有污点(Taint)时,Kubernetes 调度器会检查所有待调度的 Pod,只有那些有匹配的 Toleration 的 Pod 才会被调度到该节点。
- 如果 Pod 没有匹配的 Toleration,则调度器会跳过这个节点,继续寻找其他合适的节点。
需要注意的是:
- Taints 是主动的:节点上有污点时,会对不满足条件的 Pod 施加限制。
- Tolerations 是被动的:Pod 上有 Toleration 时,表明它可以容忍某种污点,但它不会主动要求调度到有特定污点的节点上。
3. 示例
3.1 在特定节点上添加 Taint
假设我们希望将节点 node1
设为专用节点,用于跑某些特定工作负载(如后台任务)。我们可以给这个节点添加如下 Taint:
1 |
|
3.2 配置 Pod 的 Toleration
假设我们有一个 Pod 专门用于后台任务,我们希望它能够调度到 node1
上。可以在 Pod 的 YAML 文件中添加如下配置:
1 |
|
3.3 添加 NoExecute
的场景
假设我们希望标记一个节点为不可用(例如硬件维护时),并且希望正在运行的 Pod 能够在 60 秒内迁移到其他节点。可以这样设置:
1 |
|
然后,在 Pod 的 Toleration 中,我们可以指定容忍时间:
1 |
|
此时,Pod 会在 60 秒内迁移到其他节点。
4. 应用场景
场景 1:专用节点调度
通过设置 Taints
和 Tolerations
,可以实现节点专用于某些特殊工作负载(如 GPU 工作负载、日志收集等)。
场景 2:节点维护
当节点需要下线维护时,可以添加 NoExecute
类型的 Taint,将该节点上的所有 Pod 驱逐,同时阻止新 Pod 调度到该节点。
场景 3:负载隔离
通过对节点添加 PreferNoSchedule
类型的 Taint,可以降低调度到该节点的优先级,但仍然允许在特殊情况下将 Pod 调度到该节点。
5. 注意事项
- Taints 和 Tolerations 并不主动分配工作负载到特定节点:它们只是限制哪些节点可以运行哪些 Pod。如果需要主动分配,可以结合
nodeSelector
或nodeAffinity
。 - Tolerations 只允许调度,不强制调度:即使 Pod 有符合条件的 Toleration,也可能因为资源不足等原因无法调度到对应节点。
- TolerationSeconds 仅适用于
NoExecute
:当节点添加NoExecute
类型的 Taint 时,容忍时间才会生效。
故障转移
Kubernetes 通过 Taints
和 Tolerations
配合来实现节点的故障转移(Failover),当某些节点出现故障(如离线或不可用)时,Taints
可以标记这些节点不适合继续运行 Pod,而调度器(Scheduler)会将受影响的 Pod 迁移到其他可用节点,从而实现故障转移。
故障转移的核心机制
在 Kubernetes 中,故障转移通常依赖以下几个机制:
节点不可用的检测:
Kubernetes 使用kubelet
和Node Controller
来监控节点的可用性。如果节点未在指定时间内报告心跳(默认 40 秒内未响应),Node Controller 会将该节点标记为不可用,进而触发相应的处理。节点的
NoExecute
Taint:
当节点被标记为不可用时,Kubernetes 会自动为该节点添加一个特殊的 Taint:1
node.kubernetes.io/unreachable:NoExecute
这是一个系统级的 Taint,表示该节点已不可达。
Pod 的 Tolerations:
如果部署的 Pod 没有显式声明可以容忍这个 Taint,它们会被驱逐(Evicted)并重新调度到其他健康的节点上。驱逐与重新调度:
调度器检测到 Pod 因为 Taints 被驱逐后,会在其他节点上重新调度这些 Pod(前提是集群中有其他符合条件的健康节点)。
故障转移中的 Tolerations
作用
Tolerations
是用来定义 Pod 能够容忍哪些类型的 Taints 的。如果一个 Pod 声明了能够容忍 NoExecute
类型的 Taint,会影响 Pod 的行为:
没有 Tolerations:
- 当节点被标记为不可用(添加
NoExecute
Taint)时,Pod 会立即被驱逐,并重新调度到其他健康节点。
- 当节点被标记为不可用(添加
有 Toleration,但没有
tolerationSeconds
:- 如果 Pod 声明了可以容忍
node.kubernetes.io/unreachable:NoExecute
的 Taint,但没有指定tolerationSeconds
,它会无限期地留在该节点上,即使节点不可达也不会被驱逐。
- 如果 Pod 声明了可以容忍
有 Toleration,且指定了
tolerationSeconds
:- 如果 Pod 声明了可以容忍
node.kubernetes.io/unreachable:NoExecute
的 Taint,并设置了tolerationSeconds
,Pod 会在节点不可达的指定时间后被驱逐。例如:这表示该 Pod 当节点不可达时,可以容忍 60 秒,超过这个时间后会被驱逐。1
2
3
4
5tolerations:
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 60
- 如果 Pod 声明了可以容忍
故障转移示例
1. 模拟节点故障
假设有一个 Pod 被调度到了节点 node1
,我们可以通过以下命令模拟该节点的不可用状态:
1 |
|
这和节点因为健康检查失败被自动添加 NoExecute
Taint 的情况等价。
2. Pod 的行为
如果 Pod 没有定义任何 Toleration:
- Pod 会被立即驱逐并重新调度到其他健康节点(如果有可用节点)。
如果 Pod 有定义以下 Toleration:
1
2
3
4
5tolerations:
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 30- Pod 在
node1
上可以继续运行,但最多容忍 30 秒。如果在 30 秒内节点状态恢复,Pod 不会被驱逐;如果超过 30 秒节点仍不可用,Pod 会被驱逐并重新调度。
- Pod 在
故障转移的工作流程
以下是 Kubernetes 故障转移的完整工作流程:
节点检测与标记:
kubelet
定期向kube-apiserver
发送心跳。如果节点的心跳在 40 秒内没有响应(通过node-status-update-frequency
和node-monitor-grace-period
参数控制),Node Controller 会将节点标记为NotReady
并添加一个NoExecute
Taint:1
node.kubernetes.io/unreachable:NoExecute
Pod 驱逐逻辑:
- 对于运行在该节点上的 Pod,调度器会检查它们是否有
Toleration
能容忍NoExecute
Taint:- 没有 Tolerations 的 Pod 会被立刻驱逐。
- 有
tolerationSeconds
的 Pod 会在指定时间后被驱逐。 - 有 Tolerations 且无限期容忍的 Pod 会继续保留在节点上(即使节点不可用)。
- 对于运行在该节点上的 Pod,调度器会检查它们是否有
重新调度:
- 被驱逐的 Pod 状态会变为
Pending
,调度器会尝试将 Pod 分配到其他符合条件的健康节点。 - 如果集群中没有足够的资源,Pod 会一直处于
Pending
状态,直到有可用资源。
- 被驱逐的 Pod 状态会变为
节点恢复:
- 如果节点恢复(心跳正常),Taint 会被自动移除,新的 Pod 可以调度到该节点。
实际使用场景
无状态应用(Stateless Applications):
- 对于无状态应用(如 Web 服务器),通常不会定义
NoExecute
Taint 的 Tolerations。当节点不可用时,这些 Pod 会被快速驱逐并重新调度到其他节点。
- 对于无状态应用(如 Web 服务器),通常不会定义
有状态应用(Stateful Applications):
- 对于有状态应用(如数据库),可以使用
tolerationSeconds
为 Pod 设置一个宽限期,以允许节点临时不可用的情况下减少不必要的驱逐。例如,数据库可能会因为网络抖动而短时间失联,此时无需立即驱逐 Pod。
- 对于有状态应用(如数据库),可以使用
关键任务负载(Critical Workloads):
- 如果某些 Pod 是关键任务,且对节点的不可用有更高的容忍度,可以为其设置无限期容忍:
1
2
3
4tolerations:
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
- 如果某些 Pod 是关键任务,且对节点的不可用有更高的容忍度,可以为其设置无限期容忍:
调优建议
合理设置心跳间隔:
- 如果节点的不可用只是暂时的(例如网络抖动),可以通过调整
node-monitor-grace-period
参数来避免误判。
- 如果节点的不可用只是暂时的(例如网络抖动),可以通过调整
为不同类型的应用设置不同的
tolerationSeconds
:- 无状态应用可以设置较短的容忍时间(甚至不容忍)。
- 有状态应用可以设置较长的容忍时间,避免频繁驱逐。
结合 Pod 的 Liveness Probe 和 Readiness Probe:
- 确保 Pod 本身的健康状态,避免将调度问题与 Pod 内部问题混淆。
在 Kubernetes 中,调度器是负责将 Pod 分配到集群中的合适节点上的组件。对于复杂的生产环境,可能会遇到调度效率、资源竞争和容灾等挑战,Kubernetes 提供了多种功能来优化调度策略,其中包括 PriorityClass(优先级调度) 和 多调度器(Multiple Schedulers)。
PriorityClass
1. 优先级调度(PriorityClass)
PriorityClass 是 Kubernetes 提供的一种机制,用于给不同 Pod 设置调度优先级。它允许用户指定某些工作负载的重要性,从而确保高优先级的工作负载能够获得优先调度。
关键概念
- PriorityClass 对象: 定义了优先级名称和其对应的整数字段(
value
),value
越大,优先级越高。 - 系统优先级范围:
system-cluster-critical
和system-node-critical
是 Kubernetes 预定义的最高优先级,通常用于核心组件(如coredns
、kube-proxy
)。- 自定义的优先级值取值范围为正整数,通常由管理员定义。
- 抢占(Preemption):
- 如果没有足够的资源调度高优先级 Pod,会触发抢占机制,驱逐低优先级的 Pod 为高优先级的 Pod 腾出资源。
示例
定义一个 PriorityClass:
1
2
3
4
5
6
7apiVersion: 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。
使用 PriorityClass 的 Pod 定义:
1
2
3
4
5
6
7
8
9apiVersion: 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 或自定义调度器)实现需求。
示例
部署多个调度器
- 部署默认调度器
kube-scheduler
。 - 部署一个自定义调度器。
自定义调度器示例配置:
1
2
3
4
5
6
7
8
9
10apiVersion: v1
kind: Pod
metadata:
name: custom-scheduler
spec:
containers:
- name: custom-scheduler
image: custom-scheduler-image
args:
- --scheduler-name=custom-scheduler- 部署默认调度器
指定 Pod 使用自定义调度器
1
2
3
4
5
6
7
8
9apiVersion: 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)和自定义调度器,隔离潜在的恶意或异常工作负载。
通过这些改进,可以有效提升系统的稳定性和调度效率,同时避免因调度问题引发的级联故障。