一. 新建的Pod如何调度到Node节点?
Kubernetes 调度过程是将 Pod 分配到集群中合适节点的过程。具体流程如下:
-
Pod 创建 :
用户通过 kubectl 或 API 创建 Pod -
API Server 处理 :
- API Server 对 Pod 的合法性进行校验(如资源请求格式、权限检查)
- 将 Pod 的元数据写入 etcd
- Pod 此时处于 Pending 状态
-
调度器监听 :
- 调度器通过 Informer 机制监听到新创建的 Pod
- 发现
spec.nodeName
为空的 Pod,将其标记为待调度 - 根据优先级将 Pod 加入调度队列(如
ActiveQ
)
-
调度决策 :
- PreFilter:预处理 Pod 信息
- Filter预选 :过滤不满足条件的节点
- PostFilter(抢占):无可用节点时的处理,触发抢占(Preemption),驱逐低优先级 Pod 以腾出资源
- PreScore:预处理数据,如计算拓扑分布权重
- Scoring优选 :对节点进行打分
- Reserve资源预留:临时预留节点资源,避免其他 Pod 抢占
- Permit审批:可阻塞等待外部条件(如人工审批、依赖资源就绪)
-
绑定操作 :
- PreBind:执行绑定前的操作(如挂载存储卷、配置网络)
- Bind:向 API Server 提交
Binding
对象,设置 Pod 的spec.nodeName
- PostBind:清理临时数据或发送通知(如记录调度指标)
-
节点上的 Kubelet 执行 :
- Kubelet 监测到被分配的 Pod
- 拉取镜像并启动容器
- 状态上报
完整流程:
用户创建 Pod → API Server 写入 etcd → 调度器监听到 Pod → 加入调度队列 → 调度框架处理(Filter→Score→Reserve→Bind) → Kubelet 启动容器 → Pod 状态更新为 Running
二. 调度机制的工作原理
Kubernetes 调度器的核心是 两个协同工作的控制循环,分别负责状态同步和调度决策。
第一个控制循环:Informer Path(状态同步)
实时同步集群状态到调度器缓存,为调度决策提供最新数据。
-
监听资源变化:
- 通过
Informer
监听 API Server 的资源变更事件(Pod、Node、PV/PVC 等)。 - 使用 List-Watch 机制 从 API Server 获取初始全量数据 + 增量事件流。
- 通过
-
更新调度器缓存:
- Scheduler Cache:维护集群状态的本地缓存(如节点资源余量、已调度 Pod 列表等)。
- 事件驱动更新:根据监听到的事件(如
PodAdded
、NodeUpdated
),实时更新缓存状态。
-
触发调度队列更新:
- 待调度 Pod 入队:当监听到
Pod
的spec.nodeName
为空且status.phase
为Pending
时,将其加入调度队列(如ActiveQ
)。 - 重调度处理:若已调度 Pod 的依赖资源发生变化(如节点宕机),触发重新入队(如加入
UnschedulableQ
)。
- 待调度 Pod 入队:当监听到
第二个控制循环:Scheduling Path(调度决策)
从队列中取出待调度 Pod,通过多阶段流水线完成调度决策。
核心机制:自 Kubernetes 1.19 引入的 调度框架(Scheduling Framework),将调度过程拆解为多个扩展点(Extension Points)。
调度框架(Scheduling Framework)的扩展点与流程:
阶段 | 功能 | 关键插件示例 |
---|---|---|
1. QueueSort | 决定调度队列中 Pod 的排序顺序(如按优先级排序) | PrioritySort |
2. PreFilter | 预处理 Pod 信息或检查前置条件(如校验资源请求合法性) | PodTopologySpread (校验拓扑约束) |
3. Filter | 过滤不符合条件的节点(如资源不足、亲和性冲突) | NodeResourcesFit 、NodeAffinity |
4. PostFilter | 当没有可用节点时,执行备选逻辑(如抢占低优先级 Pod) | DefaultPreemption |
5. PreScore | 为 Score 阶段预处理数据(如计算拓扑分布权重) | InterPodAffinity |
6. Score | 为通过 Filter 的节点打分(0-100 分),确定最优节点 | NodeResourcesBalancedAllocation |
7. Reserve | 预留节点资源(防止其他 Pod 占用),此阶段后资源被视为“已分配” | VolumeBinding (预留存储卷) |
8. Permit | 批准或拒绝调度决策(可阻塞等待外部条件满足) | Approval (人工审批插件) |
9. PreBind | 绑定前的准备工作(如创建网络配置、挂载存储卷) | VolumeBinding (执行存储卷绑定) |
10. Bind | 将 Pod 绑定到节点(通过 API Server 提交 Binding 对象) |
DefaultBinder |
11. PostBind | 绑定后的清理或通知操作(如更新监控指标) | Metrics (记录调度耗时) |
调度周期的关键细节
- 调度阶段(Scheduling Cycle):
- 包含
QueueSort
→PreFilter
→Filter
→PostFilter
→PreScore
→Score
→Reserve
→Permit
。 - 无状态计算:此阶段仅基于缓存状态进行计算,不修改集群实际状态。
- 包含
- 绑定阶段(Binding Cycle):
- 包含
PreBind
→Bind
→PostBind
。 - 异步提交:通过 API Server 异步执行绑定操作,绑定失败时触发重试或回滚。
- 包含
- 插件机制:
- 每个扩展点可注册多个插件(例如同时使用
NodeAffinity
和PodTopologySpread
)。 - 插件可配置执行顺序(通过
Score
插件的权重值决定最终节点得分)。
- 每个扩展点可注册多个插件(例如同时使用
调度Pod为例
- Informer Path 监听到新 Pod,将其加入
ActiveQ
。 - Scheduling Path 从队列中取出 Pod,执行
PreFilter
校验资源请求。 Filter
排除资源不足的节点,Score
为剩余节点打分,选择最高分节点。Reserve
预留节点资源,Permit
批准调度。PreBind
挂载存储卷,Bind
向 API Server 提交绑定请求。- 若绑定成功,
PostBind
更新监控指标;若失败,Pod 重新入队。
三. Kubernetes 调度器的"Cache化"
Scheduler Cache在创建Pod的工作内容:
当新Pod创建后,整个调度过程是一个流畅的工作流。首先,通过Informer机制,调度器会收到Pod的Add事件,这是整个流程的起点。接着,Scheduler Cache会立即将这个新Pod的信息添加到内存缓存中,为后续的调度决策做好准备。随后,当调度器从队列中取出这个Pod准备进行调度时,它会创建一个缓存快照。这个快照非常重要,因为它提供了一个一致的集群状态视图,调度器就基于这个快照进行调度决策,包括节点过滤和打分。经过一系列的计算和比较后,调度器为Pod选择出最佳节点,然后执行Assume操作。这个操作会在内存缓存中预先更新Pod和节点的关联关系,是一种乐观的更新机制。Assume操作完成后,调度器并不会立即等待API Server的响应,而是会异步执行实际的绑定操作。这种设计减少了关键路径上的延迟。
Scheduler Cache 它维护了集群资源状态的内存缓存,以提高调度决策的效率。以下是其工作流程:
1. 初始化阶段
Scheduler Cache 在调度器启动时初始化,主要包括:
- 创建 Node、Pod 等资源的内存数据结构
- 注册 Informer 的事件处理函数
- 初始化缓存同步机制
2. 数据填充阶段
通过 Informer 机制从 API Server 获取初始数据:
- List-Watch 机制获取集群中所有 Node、Pod 等资源
- 将资源信息填充到内存缓存中
- 建立资源之间的关联关系(如 Pod 与 Node 的映射)
3. 实时更新阶段
通过 Informer 的事件处理函数实时更新缓存:
- Add 事件:将新资源添加到缓存
- Update 事件:更新缓存中的资源状态
- Delete 事件:从缓存中移除资源
4. Assume 机制
调度决策做出后,使用 Assume 机制预先更新缓存:
- 当调度器为 Pod 选择了目标 Node 后
- 在向 API Server 发送绑定请求前,先在缓存中"假设"该 Pod 已绑定
- 这种乐观更新机制避免了在关键路径上的 API Server 访问
5. 异步确认
Assume 后异步执行实际的绑定操作:
- 创建 Goroutine 向 API Server 发送绑定请求
- 如果绑定成功,Informer 会收到更新事件,确认缓存状态
- 如果绑定失败,下一次缓存同步会修正不一致状态
6. 快照机制(1.19 后引入)
调度周期开始时创建缓存快照:
- 快照提供了一个一致的、不可变的集群状态视图
- 调度决策基于快照进行,避免了在决策过程中状态变化导致的问题
- 多个调度周期可以并行执行,每个周期使用自己的快照
7. 资源跟踪
Scheduler Cache 精确跟踪各种资源:
- 节点资源:CPU、内存、存储等
- Pod 资源请求和限制
- 拓扑约束和亲和性规则
- 存储卷状态和容量
这种内存缓存机制大大提高了调度器的性能,特别是在大规模集群中,避免了频繁访问 API Server 的开销。
四. Kubernetes 调度器的“乐观”绑定的设计
在Kubernetes调度系统中,Assume操作是一种非常巧妙的性能优化设计。当调度器为Pod选择好目标节点后,不会立即向API Server发送绑定请求,而是先在内存中的Scheduler Cache里更新Pod和Node的关联信息。这种方式被称为"乐观"更新,因为它假设后续的实际绑定操作大概率会成功。
这种设计的核心优势在于避免了在关键调度路径上对API Server的远程访问。在大规模集群中,API Server可能成为性能瓶颈,尤其是在高并发调度场景下。通过Assume操作,调度器可以快速完成一个Pod的调度决策并立即处理下一个Pod,而不必等待API Server的响应。
从代码实现角度看,Assume操作通常会使用锁机制来保护Scheduler Cache的并发访问。就像编写代码中的 Lock() 一样,在更新共享数据结构时需要加锁保护。在Scheduler Cache中,当执行Assume操作时,也会使用类似的锁机制来确保数据一致性。
完成Assume操作后,调度器会创建一个异步的goroutine来执行实际的Bind操作,向API Server发送请求。如果绑定成功,一切正常;如果失败,下一次缓存同步时会修正这种不一致状态。
这种"乐观"更新策略是Kubernetes调度器性能优化的重要手段之一,它与缓存机制一起,大大提高了调度器的吞吐量和响应速度。
"乐观设计"优势 :
- 减少锁竞争
- 提高并发性能
五. Kubernetes 调度器的"无锁化"
Kubernetes 调度器的“无锁化”设计是其高效处理大规模集群资源调度的核心机制之一。这种设计通过避免传统锁机制(如互斥锁或读写锁)的竞争,显著提升了调度器的并发性能和可扩展性。
调度过程的状态隔离
调度器的核心职责是将 Pod 与 Node 匹配,但调度过程本身不直接修改集群状态。所有状态变更(如 Pod 绑定到节点)通过 API Server 的原子操作完成,调度器仅负责计算调度结果。这种职责分离避免了调度过程中对共享状态的直接竞争。
- 调度周期(Scheduling Cycle):每个 Pod 的调度过程是独立的,调度器通过
Informer
监听集群状态的变化,但调度决策基于当前状态的快照(通过SharedIndexInformer
的本地缓存),避免了频繁与 API Server 交互。 - 绑定周期(Binding Cycle):调度结果通过 API Server 异步提交,使用乐观并发控制(Optimistic Concurrency Control, OCC)保证一致性(基于资源的
ResourceVersion
)。
队列化与异步处理
调度器通过多级队列管理待调度的 Pod,不同队列之间通过优先级和调度条件隔离,避免了全局锁的需求:
- 调度队列(Scheduling Queue):
- ActiveQ:按优先级排序的待调度 Pod 队列(基于 PriorityClass)。
- BackoffQ:调度失败的 Pod 进入退避队列,延迟重试。
- UnschedulableQ:暂时无法调度的 Pod(如资源不足)暂存于此。
- 无锁队列操作:队列内部通过原子操作(如 Go 的
heap
结构)管理顺序,无需显式加锁。
单调度器实例的串行化处理
尽管 Kubernetes 支持多调度器实例,但默认情况下:
- 每个调度器实例独立工作,通过 Leader Election 机制确保同一时间只有一个实例处理特定绑定操作。
- 调度器的核心逻辑(如 Filter 和 Score 阶段)是无状态的,多个实例可并行处理不同 Pod 的调度,仅绑定阶段需要协调。
事件驱动架构
调度器通过 Watch 机制 监听集群状态变化(如新 Pod 创建、Node 资源变更等),而非主动轮询:
- 事件驱动的设计减少了不必要的状态查询,降低了对共享资源的竞争。
- 当事件触发时,调度器仅处理受影响的 Pod 或 Node,而非全局状态。
六. Kubernetes 默认调度器的可扩展性设计
Kubernetes 默认调度器(kube-scheduler)的可扩展性设计是其核心优势之一,它允许用户在不修改核心代码的情况下,通过多种机制扩展调度能力,满足不同场景的定制化需求。
调度器提供了多种扩展机制:
-
调度框架 :
- 定义了多个扩展点(Extension Points)
- 允许插件在不同阶段介入调度过程
-
扩展点示例 :
- Filter :过滤不符合条件的节点
- Score :为节点打分
- Reserve :预留资源
- Bind :执行绑定操作
-
自定义调度器 :
- 可以完全自定义调度器
- 通过 Pod 的 schedulerName 字段指定
关键扩展点与功能
扩展点 | 功能 | 典型应用场景 |
---|---|---|
QueueSort | 定义调度队列中 Pod 的排序规则(如按优先级排序) | 高优先级 Pod 优先调度 |
PreFilter | 预处理 Pod 信息或校验前置条件(如资源请求合法性) | 校验 Pod 的拓扑分布约束 |
Filter | 过滤不符合条件的节点(资源不足、亲和性冲突等) | 排除资源不足的节点 |
PostFilter | 当无可用节点时触发备选逻辑(如抢占低优先级 Pod) | 实现抢占(Preemption)机制 |
PreScore | 预处理数据(如计算拓扑分布权重) | 为 Score 阶段准备数据 |
Score | 为节点打分(0-100 分),确定最优节点 | 节点资源均衡、亲和性权重 |
Reserve | 预留节点资源(临时锁定资源,防止其他 Pod 抢占) | 避免资源竞争冲突 |
Permit | 批准或延迟调度决策(如等待外部审批或资源就绪) | 人工审批、依赖资源等待 |
PreBind | 绑定前的准备工作(如挂载存储卷、配置网络) | 存储卷绑定(VolumeBinding) |
Bind | 将 Pod 绑定到节点(可自定义绑定逻辑) | 多调度器协作、自定义绑定策略 |
PostBind | 绑定后的清理或通知操作(如记录指标、触发通知) | 监控、日志记录 |