--- reviewers: - ahg-g title: 调度框架 content_type: concept weight: 70 --- {{< feature-state for_k8s_version="1.15" state="alpha" >}} 调度框架是 Kubernetes Scheduler 的一种可插入架构,可以简化调度器的自定义。 它向现有的调度器增加了一组新的“插件” API。插件被编译到调度器程序中。 这些 API 允许大多数调度功能以插件的形式实现,同时使调度“核心”保持简单且可维护。 请参考[调度框架的设计提案](https://github.com/kubernetes/enhancements/blob/master/keps/sig-scheduling/624-scheduling-framework/README.md) 获取框架设计的更多技术信息。 # 框架工作流程 调度框架定义了一些扩展点。调度器插件注册后在一个或多个扩展点处被调用。 这些插件中的一些可以改变调度决策,而另一些仅用于提供信息。 每次调度一个 Pod 的尝试都分为两个阶段,即 **调度周期** 和 **绑定周期**。 ## 调度周期和绑定周期 调度周期为 Pod 选择一个节点,绑定周期将该决策应用于集群。 调度周期和绑定周期一起被称为“调度上下文”。 调度周期是串行运行的,而绑定周期可能是同时运行的。 如果确定 Pod 不可调度或者存在内部错误,则可以终止调度周期或绑定周期。 Pod 将返回队列并重试。 ## 扩展点 下图显示了一个 Pod 的调度上下文以及调度框架公开的扩展点。 在此图片中,“过滤器”等同于“断言”,“评分”相当于“优先级函数”。 一个插件可以在多个扩展点处注册,以执行更复杂或有状态的任务。 {{< figure src="/images/docs/scheduling-framework-extensions.png" title="调度框架扩展点" >}} ### 队列排序 {#queue-sort} 队列排序插件用于对调度队列中的 Pod 进行排序。 队列排序插件本质上提供 `less(Pod1, Pod2)` 函数。 一次只能启动一个队列插件。 ### 前置过滤 {#pre-filter} 前置过滤插件用于预处理 Pod 的相关信息,或者检查集群或 Pod 必须满足的某些条件。 如果 PreFilter 插件返回错误,则调度周期将终止。 ### 过滤 过滤插件用于过滤出不能运行该 Pod 的节点。对于每个节点, 调度器将按照其配置顺序调用这些过滤插件。如果任何过滤插件将节点标记为不可行, 则不会为该节点调用剩下的过滤插件。节点可以被同时进行评估。 ### 后置过滤 {#post-filter} 这些插件在筛选阶段后调用,但仅在该 Pod 没有可行的节点时调用。 插件按其配置的顺序调用。如果任何后过滤器插件标记节点为“可调度”, 则其余的插件不会调用。典型的后筛选实现是抢占,试图通过抢占其他 Pod 的资源使该 Pod 可以调度。 ### 前置评分 {#pre-score} 前置评分插件用于执行 “前置评分” 工作,即生成一个可共享状态供评分插件使用。 如果 PreScore 插件返回错误,则调度周期将终止。 ### 评分 {#scoring} 评分插件用于对通过过滤阶段的节点进行排名。调度器将为每个节点调用每个评分插件。 将有一个定义明确的整数范围,代表最小和最大分数。 在[标准化评分](#normalize-scoring)阶段之后,调度器将根据配置的插件权重 合并所有插件的节点分数。 ### 标准化评分 {#normalize-scoring} 标准化评分插件用于在调度器计算节点的排名之前修改分数。 在此扩展点注册的插件将使用同一插件的[评分](#scoring) 结果被调用。 每个插件在每个调度周期调用一次。 例如,假设一个 `BlinkingLightScorer` 插件基于具有的闪烁指示灯数量来对节点进行排名。 ```go func ScoreNode(_ *v1.pod, n *v1.Node) (int, error) { return getBlinkingLightCount(n) } ``` 然而,最大的闪烁灯个数值可能比 `NodeScoreMax` 小。要解决这个问题, `BlinkingLightScorer` 插件还应该注册该扩展点。 ```go func NormalizeScores(scores map[string]int) { highest := 0 for _, score := range scores { highest = max(highest, score) } for node, score := range scores { scores[node] = score*NodeScoreMax/highest } } ``` 如果任何 NormalizeScore 插件返回错误,则调度阶段将终止。 {{< note >}} 希望执行“预保留”工作的插件应该使用 NormalizeScore 扩展点。 {{< /note >}} ### Reserve Reserve 是一个信息性的扩展点。 管理运行时状态的插件(也成为“有状态插件”)应该使用此扩展点,以便 调度器在节点给指定 Pod 预留了资源时能够通知该插件。 这是在调度器真正将 Pod 绑定到节点之前发生的,并且它存在是为了防止 在调度器等待绑定成功时发生竞争情况。 这个是调度周期的最后一步。 一旦 Pod 处于保留状态,它将在绑定周期结束时触发[不保留](#unreserve) 插件 (失败时)或 [绑定后](#post-bind) 插件(成功时)。 ### Permit _Permit_ 插件在每个 Pod 调度周期的最后调用,用于防止或延迟 Pod 的绑定。 一个允许插件可以做以下三件事之一: 1. **批准** \ 一旦所有 Permit 插件批准 Pod 后,该 Pod 将被发送以进行绑定。 1. **拒绝** \ 如果任何 Permit 插件拒绝 Pod,则该 Pod 将被返回到调度队列。 这将触发[Unreserve](#unreserve) 插件。 1. **等待**(带有超时) \ 如果一个 Permit 插件返回 “等待” 结果,则 Pod 将保持在一个内部的 “等待中” 的 Pod 列表,同时该 Pod 的绑定周期启动时即直接阻塞直到得到 [批准](#frameworkhandle)。如果超时发生,**等待** 变成 **拒绝**,并且 Pod 将返回调度队列,从而触发 [Unreserve](#unreserve) 插件。 {{< note >}} 尽管任何插件可以访问 “等待中” 状态的 Pod 列表并批准它们 (查看 [`FrameworkHandle`](#frameworkhandle))。 我们希望只有允许插件可以批准处于 “等待中” 状态的预留 Pod 的绑定。 一旦 Pod 被批准了,它将发送到[预绑定](#pre-bind) 阶段。 {{< /note >}} ### 预绑定 {#pre-bind} 预绑定插件用于执行 Pod 绑定前所需的任何工作。 例如,一个预绑定插件可能需要提供网络卷并且在允许 Pod 运行在该节点之前 将其挂载到目标节点上。 如果任何 PreBind 插件返回错误,则 Pod 将被[拒绝](#unreserve) 并且 退回到调度队列中。 ### Bind Bind 插件用于将 Pod 绑定到节点上。直到所有的 PreBind 插件都完成,Bind 插件才会被调用。 各绑定插件按照配置顺序被调用。绑定插件可以选择是否处理指定的 Pod。 如果绑定插件选择处理 Pod,**剩余的绑定插件将被跳过**。 ### 绑定后 {#post-bind} 这是个信息性的扩展点。 绑定后插件在 Pod 成功绑定后被调用。这是绑定周期的结尾,可用于清理相关的资源。 ### Unreserve 这是个信息性的扩展点。 如果 Pod 被保留,然后在后面的阶段中被拒绝,则 Unreserve 插件将被通知。 Unreserve 插件应该清楚保留 Pod 的相关状态。 使用此扩展点的插件通常也使用[Reserve](#reserve)。 ## 插件 API 插件 API 分为两个步骤。首先,插件必须完成注册并配置,然后才能使用扩展点接口。 扩展点接口具有以下形式。 ```go type Plugin interface { Name() string } type QueueSortPlugin interface { Plugin Less(*v1.pod, *v1.pod) bool } type PreFilterPlugin interface { Plugin PreFilter(context.Context, *framework.CycleState, *v1.pod) error } // ... ``` # 插件配置 你可以在调度器配置中启用或禁用插件。 如果你在使用 Kubernetes v1.18 或更高版本,大部分调度 [插件](/zh/docs/reference/scheduling/config/#scheduling-plugins) 都在使用中且默认启用。 除了默认的插件,你还可以实现自己的调度插件并且将它们与默认插件一起配置。 你可以访问[scheduler-plugins](https://github.com/kubernetes-sigs/scheduler-plugins) 了解更多信息。 如果你正在使用 Kubernetes v1.18 或更高版本,你可以将一组插件设置为 一个调度器配置文件,然后定义不同的配置文件来满足各类工作负载。 了解更多关于[多配置文件](/zh/docs/reference/scheduling/config/#multiple-profiles)。