调度框架
FEATURE STATE: Kubernetes 1.15
alpha
该功能目前处于 alpha 状态,意味着:
- 版本名称包含 alpha(例如 v1alpha1)。
- 可能存在问题,启用该功能可能会暴露 bug。默认情况下被禁用。
- 对该功能的支持可能在任何时候被取消,而不另行通知。
- API 可能会在以后的软件版本中以不兼容的方式被更改,而不另行通知。
- 建议仅在短期测试集群中使用该功能,这是因为使用该功能会增加出现 bug 的风险,而且缺乏长期支持。
调度框架是 Kubernetes Scheduler 的一种新的可插入架构,可以简化调度器的自定义。它向现有的调度器增加了一组新的“插件” API。插件被编译到调度器程序中。这些 API 允许大多数调度功能以插件的形式实现,同时使调度“核心”保持简单且可维护。请参考调度框架的设计提案获取框架设计的更多技术信息。
框架工作流程
调度框架定义了一些扩展点。调度器插件注册后在一个或多个扩展点处被调用。这些插件中的一些可以改变调度决策,而另一些仅用于提供信息。
每次调度一个 pod 的尝试都分为两个阶段,即调度周期和绑定周期。
调度周期和绑定周期
调度周期为 pod 选择一个节点,绑定周期将该决策应用于集群。调度周期和绑定周期一起被称为“调度上下文”。
调度周期是串行运行的,而绑定周期可能是同时运行的。
如果确定 pod 不可调度或者存在内部错误,则可以终止调度周期或绑定周期。Pod 将返回队列并重试。
扩展点
下图显示了一个 pod 的调度上下文以及调度框架公开的扩展点。在此图片中,“过滤器”等同于“断言”,“评分”相当于“优先级函数”。
一个插件可以在多个扩展点处注册,以执行更复杂或有状态的任务。
调度框架扩展点
队列排序
队列排序插件用于对调度队列中的 pod 进行排序。队列排序插件本质上将提供 “less(Pod1, Pod2)” 函数。一次只能启动一个队列插件。
前置过滤
前置过滤插件用于预处理 pod 的相关信息,或者检查集群或 pod 必须满足的某些条件。如果前置过滤插件返回错误,则调度周期将终止。
过滤
过滤插件用于过滤出不能运行该 pod 的节点。对于每个节点,调度器将按照其配置顺序调用这些过滤插件。如果任何过滤插件将节点标记为不可行,则不会为该节点调用剩下的过滤插件。节点可以被同时进行评估。
后置过滤
后置过滤是一个信息性的扩展点。通过过滤阶段的节点列表将调用这些后置过滤。插件将使用这些数据来更新内部的状态或者生成日志/指标。
注意:希望执行“预评分”工作的插件应该使用后置过滤扩展点。
评分
评分插件用于对通过过滤阶段的节点进行排名。调度器将为每个节点调用每个评分插件。将有一个定义明确的整数范围,代表最小和最大分数。在标准化评分阶段之后,调度器将根据配置的插件权重合并所有插件的节点分数。
标准化评分
标准化评分插件用于在调度器计算节点的排名之前修改分数。在此扩展点注册的插件将使用同一插件的评分结果被调用。每个插件在每个调度周期调用一次。
例如,假设一个 BlinkingLightScorer
插件基于具有的闪烁指示灯数量来对节点进行排名。
func ScoreNode(_ *v1.pod, n *v1.Node) (int, error) {
return getBlinkingLightCount(n)
}
然而,最大的闪烁灯个数值可能比 NodeScoreMax
小。要解决这个问题,BlinkingLightScorer
插件还应该注册该扩展点。
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
}
}
如果任何标准化评分插件返回错误,则调度阶段将终止。
注意:希望执行“预保留”工作的插件应该使用标准化评分扩展点。
保留
保留是一个信息性的扩展点。管理运行时状态的插件(也成为“有状态插件”)应该使用此扩展点,以便调度器在节点给指定 pod 预留了资源时能够通知该插件。这是在调度器真正将 pod 绑定到节点之前发生的,并且它存在是为了防止在调度器等待绑定成功时发生竞争情况。
这个是调度周期的最后一步。一旦 pod 处于保留状态,它将在绑定周期结束时触发不保留插件(失败时)或 绑定后插件(成功时)。
注意:此概念曾被称为“假设”。
允许
允许插件用于防止或延迟 pod 的绑定。一个允许插件可以做以下三件事之一。
批准
一旦所有允许插件批准 pod 后,该 pod 将被发送以进行绑定。拒绝
如果任何允许插件拒绝 pod,则该 pod 将被返回到调度队列。这将触发不保留插件。等待(带有超时)
如果一个允许插件返回“等待”结果,则 pod 将保持在允许阶段,直到插件批准它。如果超时发生,等待变成拒绝,并且 pod 将返回调度队列,从而触发不保留插件。
批准 pod 绑定
尽管任何插件可以从缓存中访问“等待”状态的 pod 列表并批准它们。我们希望只有允许插件可以批准处于“等待”状态的 预留 pod 的绑定。一旦 pod 被批准了,它将发送到预绑定阶段。
预绑定
预绑定插件用于执行 pod 绑定前所需的任何工作。例如,一个预绑定插件可能需要提供网络卷并且在允许 pod 运行在该节点之前将其挂载到目标节点上。
如果任何预绑定插件返回错误,则 pod 将被拒绝并且返回到调度队列中。
绑定
绑定插件用于将 pod 绑定到节点上。直到所有的预绑定插件都完成,绑定插件才会被调用。每个绑定插件按照配置顺序被调用。绑定插件可以选择是否处理指定的 pod。如果绑定插件选择处理 pod,剩余的绑定插件将被跳过。
绑定后
这是个信息性的扩展点。绑定后插件在 pod 成功绑定后被调用。这是绑定周期的结尾,可用于清理相关的资源。
不保留
这是个信息性的扩展点。如果 pod 被保留,然后在后面的阶段中被拒绝,则不保留插件将被通知。不保留插件应该清楚保留 pod 的相关状态。
使用此扩展点的插件通常也使用保留。
插件 API
插件 API 分为两个步骤。首先,插件必须注册并配置,然后才能使用扩展点接口。扩展点接口具有以下形式。
type Plugin interface {
Name() string
}
type QueueSortPlugin interface {
Plugin
Less(*v1.pod, *v1.pod) bool
}
type PreFilterPlugin interface {
Plugin
PreFilter(PluginContext, *v1.pod) error
}
// ...
插件配置
可以在调度器配置中启用插件。另外,默认的插件可以在配置中禁用。在 1.15 版本,调度框架没有默认的插件。
调度器配置也可以包含插件的配置。这些配置在调度器初始化插件时传给插件。配置是一个任意值。接收插件应该解码并处理配置信息。
下面的例子显示一个调度器配置,该配置在 reserve
和 preBind
扩展点启用了一些插件并且禁用了一个插件。它还提供了 foo
插件的配置。
apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
...
plugins:
reserve:
enabled:
- name: foo
- name: bar
disabled:
- name: baz
preBind:
enabled:
- name: foo
disabled:
- name: baz
pluginConfig:
- name: foo
args: >
Arbitrary set of args to plugin foo
当配置省略扩展点时,将使用该扩展点的默认插件。当存在扩展掉并且配置为 enabled
,则 enabled
的插件将和默认插件一同调用。首先调用默认插件,然后以配置中指定的顺序来调用其他已启用的插件。如果希望以不同的顺序来调用默认插件,默认插件必须 disabled
,然后以期望的顺序 enabled
。
假设在 reserve
扩展点有一个默认的 foo
插件,且添加 bar
插件并且希望在 foo
插件之前执行,我们应该禁用 foo
并且按顺序启用 bar
和 foo
。下面的例子显示了实现此目的的配置:
apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
...
plugins:
reserve:
enabled:
- name: bar
- name: foo
disabled:
- name: foo