调度框架( Scheduling Framework
)是Kubernetes 的调度器 kube-scheduler
设计的的可插拔架构,将插件(调度算法)嵌入到调度上下文的每个扩展点中,并编译为 kube-scheduler
在 kube-scheduler
1.22 之后,在 pkg/scheduler/framework/interface.go 中定义了一个 Plugin 的 interface ,这个 interface 作为了所有插件的父级。而每个未调度的 Pod,Kubernetes 调度器会根据一组规则尝试在集群中寻找一个节点。
type Plugin interface { Name() string }
下面会对每个算法是如何实现的进行分析
在初始化 scheduler 时,会创建一个 profile
,profile是关于 scheduler 调度配置相关的定义
func New(client clientset.Interface,
...
profiles, err := profile.NewMap(options.profiles, registry, recorderFactory, stopCh,
frameworkruntime.WithComponentConfigVersion(options.componentConfigVersion),
frameworkruntime.WithClientSet(client),
frameworkruntime.WithKubeConfig(options.kubeConfig),
frameworkruntime.WithInformerFactory(informerFactory),
frameworkruntime.WithSnapshotSharedLister(snapshot),
frameworkruntime.WithPodNominator(nominator),
frameworkruntime.WithCaptureProfile(frameworkruntime.CaptureProfile(options.frameworkCapturer)),
frameworkruntime.WithClusterEventMap(clusterEventMap),
frameworkruntime.WithParallelism(int(options.parallelism)),
frameworkruntime.WithExtenders(extenders),
)
if err != nil {
return nil, fmt.Errorf("initializing profiles: %v", err)
}
if len(profiles) == 0 {
return nil, errors.New("at least one profile is required")
}
....
}
关于 profile
的实现,则为 KubeSchedulerProfile
,也是作为 yaml生成时传入的配置
// KubeSchedulerProfile 是一个 scheduling profile.
type KubeSchedulerProfile struct {
// SchedulerName 是与此配置文件关联的调度程序的名称。
// 如果 SchedulerName 与 pod “spec.schedulerName”匹配,则使用此配置文件调度 pod。
SchedulerName string
// Plugins指定应该启用或禁用的插件集。
// 启用的插件是除了默认插件之外应该启用的插件。禁用插件应是禁用的任何默认插件。
// 当没有为扩展点指定启用或禁用插件时,将使用该扩展点的默认插件(如果有)。
// 如果指定了 QueueSort 插件,
/// 则必须为所有配置文件指定相同的 QueueSort Plugin 和 PluginConfig。
// 这个Plugins展现的形式则是调度上下文中的所有扩展点(这是抽象),实际中会表现为多个扩展点
Plugins *Plugins
// PluginConfig 是每个插件的一组可选的自定义插件参数。
// 如果省略PluginConfig参数等同于使用该插件的默认配置。
PluginConfig []PluginConfig
}
对于 profile.NewMap
就是根据给定的配置来构建这个framework,因为配置可能是存在多个的。而 Registry 则是所有可用插件的集合,内部构造则是 PluginFactory ,通过函数来构建出对应的 plugin
func NewMap(cfgs []config.KubeSchedulerProfile, r frameworkruntime.Registry, recorderFact RecorderFactory,
stopCh <-chan struct{}, opts ...frameworkruntime.Option) (Map, error) {
m := make(Map)
v := cfgValidator{m: m}
for _, cfg := range cfgs {
p, err := newProfile(cfg, r, recorderFact, stopCh, opts...)
if err != nil {
return nil, fmt.Errorf("creating profile for scheduler name %s: %v", cfg.SchedulerName, err)
}
if err := v.validate(cfg, p); err != nil {
return nil, err
}
m[cfg.SchedulerName] = p
}
return m, nil
}
// newProfile 给的配置构建出一个profile
func newProfile(cfg config.KubeSchedulerProfile, r frameworkruntime.Registry, recorderFact RecorderFactory,
stopCh <-chan struct{}, opts ...frameworkruntime.Option) (framework.Framework, error) {
recorder := recorderFact(cfg.SchedulerName)
opts = append(opts, frameworkruntime.WithEventRecorder(recorder))
return frameworkruntime.NewFramework(r, &cfg, stopCh, opts...)
}
可以看到最终返回的是一个 Framework
。那么来看下这个 Framework
Framework 是一个抽象,管理着调度过程中所使用的所有插件,并在调度上下文中适当的位置去运行对应的插件
type Framework interface {
Handle
// QueueSortFunc 返回对调度队列中的 Pod 进行排序的函数
// 也就是less,在Sort打分阶段的打分函数
QueueSortFunc() LessFunc
// RunPreFilterPlugins 运行配置的一组PreFilter插件。
// 如果这组插件中,任何一个插件失败,则返回 *Status 并设置为non-success。
// 如果返回状态为non-success,则调度周期中止。
// 它还返回一个 PreFilterResult,它可能会影响到要评估下游的节点。
RunPreFilterPlugins(ctx context.Context, state *CycleState, pod *v1.Pod) (*PreFilterResult, *Status)
// RunPostFilterPlugins 运行配置的一组PostFilter插件。
// PostFilter 插件是通知性插件,在这种情况下应配置为先执行并返回 Unschedulable 状态,
// 或者尝试更改集群状态以使 pod 在未来的调度周期中可能会被调度。
RunPostFilterPlugins(ctx context.Context, state *CycleState, pod *v1.Pod, filteredNodeStatusMap NodeToStatusMap) (*PostFilterResult, *Status)
// RunPreBindPlugins 运行配置的一组 PreBind 插件。
// 如果任何一个插件返回错误,则返回 *Status 并且code设置为non-success。
// 如果code为“Unschedulable”,则调度检查失败,
// 则认为是内部错误。在任何一种情况下,Pod都不会被bound。
RunPreBindPlugins(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) *Status
// RunPostBindPlugins 运行配置的一组PostBind插件
RunPostBindPlugins(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string)
// RunReservePluginsReserve运行配置的一组Reserve插件的Reserve方法。
// 如果在这组调用中的任何一个插件返回错误,则不会继续运行剩余调用的插件并返回错误。
// 在这种情况下,pod将不能被调度。
RunReservePluginsReserve(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) *Status
// RunReservePluginsUnreserve运行配置的一组Reserve插件的Unreserve方法。
RunReservePluginsUnreserve(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string)
// RunPermitPlugins运行配置的一组Permit插件。
// 如果这些插件中的任何一个返回“Success”或“Wait”之外的状态,则它不会继续运行其余插件并返回错误。
// 否则,如果任何插件返回 “Wait”,则此函数将创建等待pod并将其添加到当前等待pod的map中,
// 并使用“Wait” code返回状态。 Pod将在Permit插件返回的最短持续时间内保持等待pod。
RunPermitPlugins(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) *Status
// 如果pod是waiting pod,WaitOnPermit 将阻塞,直到等待的pod被允许或拒绝。
WaitOnPermit(ctx context.Context, pod *v1.Pod) *Status
// RunBindPlugins运行配置的一组bind插件。 Bind插件可以选择是否处理Pod。
// 如果 Bind 插件选择跳过binding,它应该返回 code=5("skip")状态。
// 否则,它应该返回“Error”或“Success”。
// 如果没有插件处理绑定,则RunBindPlugins返回code=5("skip")的状态。
RunBindPlugins(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) *Status
// 如果至少定义了一个filter插件,则HasFilterPlugins返回true
HasFilterPlugins() bool
// 如果至少定义了一个PostFilter插件,则HasPostFilterPlugins返回 true。
HasPostFilterPlugins() bool
// 如果至少定义了一个Score插件,则HasScorePlugins返回 true。
HasScorePlugins() bool
// ListPlugins将返回map。key为扩展点名称,value则是配置的插件列表。
ListPlugins() *config.Plugins
// ProfileName则是与profile name关联的framework
ProfileName() string
}
而实现这个抽象的则是 frameworkImpl ; frameworkImpl
是初始化与运行 scheduler plugins 的组件,并在调度上下文中会运行这些扩展点
type frameworkImpl struct {
registry Registry
snapshotSharedLister framework.SharedLister
waitingPods *waitingPodsMap
scorePluginWeight map[string]int
queueSortPlugins []framework.QueueSortPlugin
preFilterPlugins []framework.PreFilterPlugin
filterPlugins []framework.FilterPlugin
postFilterPlugins []framework.PostFilterPlugin
preScorePlugins []framework.PreScorePlugin
scorePlugins []framework.ScorePlugin
reservePlugins []framework.ReservePlugin
preBindPlugins []framework.PreBindPlugin
bindPlugins []framework.BindPlugin
postBindPlugins []framework.PostBindPlugin
permitPlugins []framework.PermitPlugin
clientSet clientset.Interface
kubeConfig *restclient.Config
eventRecorder events.EventRecorder
informerFactory informers.SharedInformerFactory
metricsRecorder *metricsRecorder
profileName string
extenders []framework.Extender
framework.PodNominator
parallelizer parallelize.Parallelizer
}
那么来看下 Registry , Registry
是作为一个可用插件的集合。 framework
使用 registry
来启用和对插件配置的初始化。在初始化框架之前,所有插件都必须在注册表中。表现形式就是一个 map[]
; key 是插件的名称,value是 PluginFactory
。
type Registry map[string]PluginFactory
而在 pkg\scheduler\framework\plugins\registry.go 中会将所有的 in-tree plugin
注册进来。通过 NewInTreeRegistry
。后续如果还有插件要注册,可以通过 WithFrameworkOutOfTreeRegistry 来注册其他的插件。
func NewInTreeRegistry() runtime.Registry {
fts := plfeature.Features{
EnableReadWriteOncePod: feature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
EnableVolumeCapacityPriority: feature.DefaultFeatureGate.Enabled(features.VolumeCapacityPriority),
EnableMinDomainsInPodTopologySpread: feature.DefaultFeatureGate.Enabled(features.MinDomainsInPodTopologySpread),
EnableNodeInclusionPolicyInPodTopologySpread: feature.DefaultFeatureGate.Enabled(features.NodeInclusionPolicyInPodTopologySpread),
}
return runtime.Registry{
selectorspread.Name: selectorspread.New,
imagelocality.Name: imagelocality.New,
tainttoleration.Name: tainttoleration.New,
nodename.Name: nodename.New,
nodeports.Name: nodeports.New,
nodeaffinity.Name: nodeaffinity.New,
podtopologyspread.Name: runtime.FactoryAdapter(fts, podtopologyspread.New),
nodeunschedulable.Name: nodeunschedulable.New,
noderesources.Name: runtime.FactoryAdapter(fts, noderesources.NewFit),
noderesources.BalancedAllocationName: runtime.FactoryAdapter(fts, noderesources.NewBalancedAllocation),
volumebinding.Name: runtime.FactoryAdapter(fts, volumebinding.New),
volumerestrictions.Name: runtime.FactoryAdapter(fts, volumerestrictions.New),
volumezone.Name: volumezone.New,
nodevolumelimits.CSIName: runtime.FactoryAdapter(fts, nodevolumelimits.NewCSI),
nodevolumelimits.EBSName: runtime.FactoryAdapter(fts, nodevolumelimits.NewEBS),
nodevolumelimits.GCEPDName: runtime.FactoryAdapter(fts, nodevolumelimits.NewGCEPD),
nodevolumelimits.AzureDiskName: runtime.FactoryAdapter(fts, nodevolumelimits.NewAzureDisk),
nodevolumelimits.CinderName: runtime.FactoryAdapter(fts, nodevolumelimits.NewCinder),
interpodaffinity.Name: interpodaffinity.New,
queuesort.Name: queuesort.New,
defaultbinder.Name: defaultbinder.New,
defaultpreemption.Name: runtime.FactoryAdapter(fts, defaultpreemption.New),
}
}
这里插入一个题外话,关于 in-tree plugin
在这里没有找到关于, kube-scheduler ,只是找到有关的概念,大概可以解释为,in-tree表示为随kubernetes官方提供的二进制构建的 plugin 则为 in-tree
,而独立于kubernetes代码库之外的为 out-of-tree
。这种情况下,可以理解为,AA则是 out-of-tree
而 Pod
, DeplymentSet
等是 in-tree
。
接下来回到初始化 scheduler ,在初始化一个 scheduler 时,会通过 NewInTreeRegistry
来初始化
func New(client clientset.Interface,
....
registry := frameworkplugins.NewInTreeRegistry()
if err := registry.Merge(options.frameworkOutOfTreeRegistry); err != nil {
return nil, err
}
...
profiles, err := profile.NewMap(options.profiles, registry, recorderFactory, stopCh,
frameworkruntime.WithComponentConfigVersion(options.componentConfigVersion),
frameworkruntime.WithClientSet(client),
frameworkruntime.WithKubeConfig(options.kubeConfig),
frameworkruntime.WithInformerFactory(informerFactory),
frameworkruntime.WithSnapshotSharedLister(snapshot),
frameworkruntime.WithPodNominator(nominator),
f