源碼分析系列文章已經開源到github,地址如下:
-
github:
https://github.com/farmer-hutao/k8s-source-code-analysis -
gitbook:
https://farmer-hutao.github.io/k8s-source-code-analysis
本文大綱
1. 概述
今天我們要做一些瑣碎的知識點分析,比如調度器啟動的時候默認配置是怎么來的?默認生效了哪些調度算法?自定義的算法是如何注入的?諸如這些問題,我們順帶會看一下調度器相關的一些數據結構的含義。看完前面這些節的分析后再看完本篇文章你可能會有一種醍醐灌頂的感覺哦~
2. 從 --config 開始
如果我們編譯出來一個 kube-scheduler 二進制文件,運行./kube-scheduler -h
后會看到很多的幫助信息,這些信息是分組的,比如第一組 Misc,差不多是“大雜燴”的意思,不好分類的幾個 flag,其實也是最重要的幾個 flag,如下:
很好理解,第一個紅框框圈出來的--config
用於指定配置文件,老版本的各種參數基本都不建議使用了,所以這個 config flag 指定的 config 文件中基本包含了所有可配置項,我們看一下代碼中獲取這個 flag 的相關代碼:
cmd/kube-scheduler/app/options/options.go:143
func (o *Options) Flags() (nfs apiserverflag.NamedFlagSets) { fs := nfs.FlagSet("misc") // 關注 --config fs.StringVar(&o.ConfigFile, "config", o.ConfigFile, "The path to the configuration file. Flags override values in this file.") fs.StringVar(&o.WriteConfigTo, "write-config-to", o.WriteConfigTo, "If set, write the configuration values to this file and exit.") fs.StringVar(&o.Master, "master", o.Master, "The address of the Kubernetes API server (overrides any value in kubeconfig)") o.SecureServing.AddFlags(nfs.FlagSet("secure serving")) o.CombinedInsecureServing.AddFlags(nfs.FlagSet("insecure serving")) o.Authentication.AddFlags(nfs.FlagSet("authentication")) o.Authorization.AddFlags(nfs.FlagSet("authorization")) o.Deprecated.AddFlags(nfs.FlagSet("deprecated"), &o.ComponentConfig) leaderelectionconfig.BindFlags(&o.ComponentConfig.LeaderElection.LeaderElectionConfiguration, nfs.FlagSet("leader election")) utilfeature.DefaultFeatureGate.AddFlag(nfs.FlagSet("feature gate")) return nfs }
上述代碼中有幾個點可以關注到:
- FlagSet 的含義,命令行輸出的分組和這里的分組是對應的;
- 除了認證授權、選舉等“非關鍵”配置外,其他配置基本 Deprecated 了,也就意味着建議使用 config file;
上面代碼中可以看到o.ConfigFile
接收了config配置,我們看看Option類型是什么樣子的~
2.1. options.Option 對象
Options
對象包含運行一個 Scheduler 所需要的所有參數
cmd/kube-scheduler/app/options/options.go:55
type Options struct { // 和命令行幫助信息的分組是一致的 ComponentConfig kubeschedulerconfig.KubeSchedulerConfiguration SecureServing *apiserveroptions.SecureServingOptionsWithLoopback CombinedInsecureServing *CombinedInsecureServingOptions Authentication *apiserveroptions.DelegatingAuthenticationOptions Authorization *apiserveroptions.DelegatingAuthorizationOptions Deprecated *DeprecatedOptions // config 文件的路徑 ConfigFile string // 如果指定了,會輸出 config 的默認配置到這個文件 WriteConfigTo string Master string }
前面的 flag 相關代碼中寫到配置文件的內容給了o.ConfigFile
,也就是Options.ConfigFile
,那這個屬性怎么使用呢?
我們來看下面這個 ApplyTo() 函數,這個函數要做的事情是把 options 配置 apply 給 scheduler app configuration(這個對象后面會講到):
cmd/kube-scheduler/app/options/options.go:162
// 把 Options apply 給 Config func (o *Options) ApplyTo(c *schedulerappconfig.Config) error { // --config 沒有使用的情況 if len(o.ConfigFile) == 0 { c.ComponentConfig = o.ComponentConfig // 使用 Deprecated 的配置 if err := o.Deprecated.ApplyTo(&c.ComponentConfig); err != nil { return err } if err := o.CombinedInsecureServing.ApplyTo(c, &c.ComponentConfig); err != nil { return err } } else { // 加載 config 文件中的內容 cfg, err := loadConfigFromFile(o.ConfigFile) if err != nil { return err } // 上面加載到的配置賦值給 Config中的 ComponentConfig c.ComponentConfig = *cfg if err := o.CombinedInsecureServing.ApplyToFromLoadedConfig(c, &c.ComponentConfig); err != nil { return err } } // …… return nil }
這個函數中可以看到用 --config 和不用 --config 兩種情況下 options 是如何應用到schedulerappconfig.Config
中的。那么這里提到的 Config 對象又是什么呢?
2.2. config.Config對象
Config 對象包含運行一個 Scheduler 所需要的所有 context
cmd/kube-scheduler/app/config/config.go:32
type Config struct { // 調度器配置對象 ComponentConfig kubeschedulerconfig.KubeSchedulerConfiguration LoopbackClientConfig *restclient.Config InsecureServing *apiserver.DeprecatedInsecureServingInfo InsecureMetricsServing *apiserver.DeprecatedInsecureServingInfo Authentication apiserver.AuthenticationInfo Authorization apiserver.AuthorizationInfo SecureServing *apiserver.SecureServingInfo Client clientset.Interface InformerFactory informers.SharedInformerFactory PodInformer coreinformers.PodInformer EventClient v1core.EventsGetter Recorder record.EventRecorder Broadcaster record.EventBroadcaster LeaderElection *leaderelection.LeaderElectionConfig }
所以前面的c.ComponentConfig = o.ComponentConfig
這行代碼也就是把 Options 中的 ComponentConfig 賦值給了 Config 中的 ComponentConfig;是哪里的邏輯讓 Options 和 Config 對象產生了關聯呢?(也就是說前面提到的 ApplyTo()
方法是再哪里被調用的?)
繼續跟下去可以找到Config()
函數,從這個函數的返回值*schedulerappconfig.Config
可以看到它的目的,是需要得到一個 schedulerappconfig.Config,代碼不長:
cmd/kube-scheduler/app/options/options.go:221
func (o *Options) Config() (*schedulerappconfig.Config, error) { // …… c := &schedulerappconfig.Config{} // 前面我們看到的 ApplyTo() 函數 if err := o.ApplyTo(c); err != nil { return nil, err } // Prepare kube clients. // …… // Prepare event clients. eventBroadcaster := record.NewBroadcaster() recorder := eventBroadcaster.NewRecorder(legacyscheme.Scheme, corev1.EventSource{Component: c.ComponentConfig.SchedulerName}) // Set up leader election if enabled. // …… c.Client = client c.InformerFactory = informers.NewSharedInformerFactory(client, 0) c.PodInformer = factory.NewPodInformer(client, 0) c.EventClient = eventClient c.Recorder = recorder c.Broadcaster = eventBroadcaster c.LeaderElection = leaderElectionConfig return c, nil }
那調用這個Config()
函數的地方又在哪里呢?繼續跟就到 runCommand 里面了~
2.3. runCommand
runCommand 這個函數我們不陌生:
cmd/kube-scheduler/app/server.go:117
func runCommand(cmd *cobra.Command, args []string, opts *options.Options) error { // …… // 這個地方完成了前面說到的配置文件和命令行參數等讀取和應用工作 c, err := opts.Config() if err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) os.Exit(1) } stopCh := make(chan struct{}) // Get the completed config cc := c.Complete() // To help debugging, immediately log version klog.Infof("Version: %+v", version.Get()) // 這里有一堆邏輯 algorithmprovider.ApplyFeatureGates() // Configz registration. // …… return Run(cc, stopCh) }
runCommand 在最開始的時候我們有見到過,已經到 cobra 入口的 Run 中了:
cmd/kube-scheduler/app/server.go:85
Run: func(cmd *cobra.Command, args []string) { if err := runCommand(cmd, args, opts); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) os.Exit(1) } },
上面涉及到2個知識點:
- ApplyFeatureGates
- Run 中的邏輯
我們下面分別來看看~
3. ApplyFeatureGates
這個函數跟進去可以看到如下幾行簡單的代碼,這里很自然我們能夠想到繼續跟defaults.ApplyFeatureGates()
,但是不能只看到這個函數哦,具體來看:
pkg/scheduler/algorithmprovider/plugins.go:17
package algorithmprovider import ( "k8s.io/kubernetes/pkg/scheduler/algorithmprovider/defaults" ) // ApplyFeatureGates applies algorithm by feature gates. func ApplyFeatureGates() { defaults.ApplyFeatureGates() }
到這里分2條路:
- import defaults 這個 package 的時候有一個
init()
函數調用的邏輯 defaults.ApplyFeatureGates()
函數調用本身。
3.1. 默認算法注冊
pkg/scheduler/algorithmprovider/defaults/defaults.go:38
func init() { // …… registerAlgorithmProvider(defaultPredicates(), defaultPriorities()) // …… }
init()
函數中我們先關注 registerAlgorithmProvider() 函數,這里從字面上可以得到不少信息,大膽猜一下:是不是注冊了默認的預選算法和優選算法?
pkg/scheduler/algorithmprovider/defaults/defaults.go:222
func registerAlgorithmProvider(predSet, priSet sets.String) { // 注冊 algorithm provider. 默認使用 DefaultProvider factory.RegisterAlgorithmProvider(factory.DefaultProvider, predSet, priSet) factory.RegisterAlgorithmProvider(ClusterAutoscalerProvider, predSet, copyAndReplace(priSet, "LeastRequestedPriority", "MostRequestedPriority")) }
看到這里可以關注到 AlgorithmProvider 這個概念,后面會講到。
先看一下里面調用的注冊函數是怎么實現的:
pkg/scheduler/factory/plugins.go:387
func RegisterAlgorithmProvider(name string, predicateKeys, priorityKeys sets.String) string { schedulerFactoryMutex.Lock() defer schedulerFactoryMutex.Unlock() validateAlgorithmNameOrDie(name) // 很明顯,關鍵邏輯在這里 algorithmProviderMap[name] = AlgorithmProviderConfig{ FitPredicateKeys: predicateKeys, PriorityFunctionKeys: priorityKeys, } return name }
首先,algorithmProviderMap 這個變量是一個包級變量,在86行做的定義:algorithmProviderMap = make(map[string]AlgorithmProviderConfig)
這里的 key 有2種情況:
- "DefaultProvider"
- "ClusterAutoscalerProvider"
混合雲場景用得到 ClusterAutoscalerProvider,大家感興趣可以研究一下 ClusterAutoscaler 特性,這塊我們先不說。默認的情況是生效的 DefaultProvider,這塊邏輯后面還會提到。
然后這個 map 的 value 的類型是一個簡單的 struct:
pkg/scheduler/factory/plugins.go:99
type AlgorithmProviderConfig struct { FitPredicateKeys sets.String PriorityFunctionKeys sets.String }
接着看一下defaultPredicates()
函數
pkg/scheduler/algorithmprovider/defaults/defaults.go:106
func defaultPredicates() sets.String { return sets.NewString( // Fit is determined by volume zone requirements. factory.RegisterFitPredicateFactory( predicates.NoVolumeZoneConflictPred, func(args factory.PluginFactoryArgs) algorithm.FitPredicate { return predicates.NewVolumeZonePredicate(args.PVInfo, args.PVCInfo, args.StorageClassInfo) }, ), // …… factory.RegisterFitPredicate(predicates.NoDiskConflictPred, predicates.NoDiskConflict), // …… ) }
這個函數里面就2中類型的玩法,簡化一些可以理解成上面這個樣子,我們一個個來看。
先認識一下 sets.NewString()
函數要干嘛:
vendor/k8s.io/apimachinery/pkg/util/sets/string.go:27
type String map[string]Empty // NewString creates a String from a list of values. func NewString(items ...string) String { ss := String{} ss.Insert(items...) return ss } // …… // Insert adds items to the set. func (s String) Insert(items ...string) { for _, item := range items { s[item] = Empty{} } }
如上,很簡單的類型封裝。里面的Empty是:type Empty struct{}
,所以本質上就是要用map[string]struct{}
這個類型罷了。
因此上面defaultPredicates()
函數中sets.NewString()
內每一個參數本質上就是一個 string類型了,我們來看這一個個 string 是怎么來的。
pkg/scheduler/factory/plugins.go:195
func RegisterFitPredicateFactory(name string, predicateFactory FitPredicateFactory) string { schedulerFactoryMutex.Lock() defer schedulerFactoryMutex.Unlock() validateAlgorithmNameOrDie(name) // 唯一值的關注的邏輯 fitPredicateMap[name] = predicateFactory // 返回 name return name }
這個函數要返回一個 string 我們已經知道了,里面的邏輯也只有這一行需要我們關注:fitPredicateMap[name] = predicateFactory
,這個 map 類型也是一個包級變量:fitPredicateMap = make(map[string]FitPredicateFactory)
,所以前面講的注冊本質也就是在填充這個變量而已。理解fitPredicateMap[name] = predicateFactory
中 fitPredicateMap的 key 和 value,也就知道了這里的 Register 要做什么。
defaultPredicates()
中的第二種注冊方式 RegisterFitPredicate 區別不大,函數體也是調用的 RegisterFitPredicateFactory():
pkg/scheduler/factory/plugins.go:106
func RegisterFitPredicate(name string, predicate algorithm.FitPredicate) string { return RegisterFitPredicateFactory(name, func(PluginFactoryArgs) algorithm.FitPredicate { return predicate }) }
3.2. 特性開關
pkg/scheduler/algorithmprovider/defaults/defaults.go:183
func ApplyFeatureGates() { if utilfeature.DefaultFeatureGate.Enabled(features.TaintNodesByCondition) { factory.RemoveFitPredicate(predicates.CheckNodeConditionPred) factory.RemoveFitPredicate(predicates.CheckNodeMemoryPressurePred) factory.RemoveFitPredicate(predicates.CheckNodeDiskPressurePred) factory.RemoveFitPredicate(predicates.CheckNodePIDPressurePred) factory.RemovePredicateKeyFromAlgorithmProviderMap(predicates.CheckNodeConditionPred) factory.RemovePredicateKeyFromAlgorithmProviderMap(predicates.CheckNodeMemoryPressurePred) factory.RemovePredicateKeyFromAlgorithmProviderMap(predicates.CheckNodeDiskPressurePred) factory.RemovePredicateKeyFromAlgorithmProviderMap(predicates.CheckNodePIDPressurePred) factory.RegisterMandatoryFitPredicate(predicates.PodToleratesNodeTaintsPred, predicates.PodToleratesNodeTaints) factory.RegisterMandatoryFitPredicate(predicates.CheckNodeUnschedulablePred, predicates.CheckNodeUnschedulablePredicate) factory.InsertPredicateKeyToAlgorithmProviderMap(predicates.PodToleratesNodeTaintsPred) factory.InsertPredicateKeyToAlgorithmProviderMap(predicates.CheckNodeUnschedulablePred) } if utilfeature.DefaultFeatureGate.Enabled(features.ResourceLimitsPriorityFunction) { factory.RegisterPriorityFunction2("ResourceLimitsPriority", priorities.ResourceLimitsPriorityMap, nil, 1) factory.InsertPriorityKeyToAlgorithmProviderMap(factory.RegisterPriorityFunction2("ResourceLimitsPriority", priorities.ResourceLimitsPriorityMap, nil, 1)) } }
這個函數看着幾十行,實際上只在重復一件事情,增加或刪除一些預選和優選算法。我們看一下這里的一些邏輯:
utilfeature.DefaultFeatureGate.Enabled()
函數要做的事情是判斷一個 feature 是否開啟;函數參數本質只是一個字符串:
pkg/features/kube_features.go:25
const ( AppArmor utilfeature.Feature = "AppArmor" DynamicKubeletConfig utilfeature.Feature = "DynamicKubeletConfig" // …… )
這里定義了很多的 feature,然后定義了哪些 feature 是開啟的,處在 alpha 還是 beta 或者 GA 等:
pkg/features/kube_features.go:405
var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureSpec{ AppArmor: {Default: true, PreRelease: utilfeature.Beta}, DynamicKubeletConfig: {Default: true, PreRelease: utilfeature.Beta}, ExperimentalHostUserNamespaceDefaultingGate: {Default: false, PreRelease: utilfeature.Beta}, ExperimentalCriticalPodAnnotation: {Default: false, PreRelease: utilfeature.Alpha}, DevicePlugins: {Default: true, PreRelease: utilfeature.Beta}, TaintBasedEvictions: {Default: true, PreRelease: utilfeature.Beta}, RotateKubeletServerCertificate: {Default: true, PreRelease: utilfeature.Beta}, // …… }
所以回到前面ApplyFeatureGates()
的邏輯,utilfeature.DefaultFeatureGate.Enabled(features.TaintNodesByCondition)
要判斷的是 TaintNodesByCondition 這個特性是否開啟了,如果開啟了就把 predicates 中 "CheckNodeCondition", "CheckNodeMemoryPressure", "CheckNodePIDPressurePred", "CheckNodeDiskPressure" 這幾個算法去掉,把 "PodToleratesNodeTaints", "CheckNodeUnschedulable" 加上。接着對於特性 "ResourceLimitsPriorityFunction" 的處理也是同一個邏輯。
4. Scheduler 的創建
我們換一條線,從 Scheduler 對象的創建再來看另外幾個知識點。
前面分析到runCommand()
函數的時候我們說到了需要關注最后一行return Run(cc, stopCh)
的邏輯,在Run()
函數中主要的邏輯就是創建 Scheduler 和啟動 Scheduler;現在我們來看創建邏輯:
cmd/kube-scheduler/app/server.go:174
sched, err := scheduler.New(cc.Client, cc.InformerFactory.Core().V1().Nodes(), cc.PodInformer, cc.InformerFactory.Core().V1().PersistentVolumes(), cc.InformerFactory.Core().V1().PersistentVolumeClaims(), cc.InformerFactory.Core().V1().ReplicationControllers(), cc.InformerFactory.Apps().V1().ReplicaSets(), cc.InformerFactory.Apps().V1().StatefulSets(), cc.InformerFactory.Core().V1().Services(), cc.InformerFactory.Policy().V1beta1().PodDisruptionBudgets(), storageClassInformer, cc.Recorder, cc.ComponentConfig.AlgorithmSource, stopCh, scheduler.WithName(cc.ComponentConfig.SchedulerName), scheduler.WithHardPodAffinitySymmetricWeight(cc.ComponentConfig.HardPodAffinitySymmetricWeight), scheduler.WithEquivalenceClassCacheEnabled(cc.ComponentConfig.EnableContentionProfiling), scheduler.WithPreemptionDisabled(cc.ComponentConfig.DisablePreemption), scheduler.WithPercentageOfNodesToScore(cc.ComponentConfig.PercentageOfNodesToScore), scheduler.WithBindTimeoutSeconds(*cc.ComponentConfig.BindTimeoutSeconds))
這里調用了一個New()
函數,傳了很多參數進去。New()
函數的定義如下:
pkg/scheduler/scheduler.go:131
func New(client clientset.Interface, nodeInformer coreinformers.NodeInformer, podInformer coreinformers.PodInformer, pvInformer coreinformers.PersistentVolumeInformer, pvcInformer coreinformers.PersistentVolumeClaimInformer, replicationControllerInformer coreinformers.ReplicationControllerInformer, replicaSetInformer appsinformers.ReplicaSetInformer, statefulSetInformer appsinformers.StatefulSetInformer, serviceInformer coreinformers.ServiceInformer, pdbInformer policyinformers.PodDisruptionBudgetInformer, storageClassInformer storageinformers.StorageClassInformer, recorder record.EventRecorder, schedulerAlgorithmSource kubeschedulerconfig.SchedulerAlgorithmSource, stopCh <-chan struct{}, opts ...func(o *schedulerOptions)) (*Scheduler, error)
這里涉及到的東西有點小多,我們一點點看:
options := defaultSchedulerOptions
這行代碼的 defaultSchedulerOptions 是一個 schedulerOptions 對象:
pkg/scheduler/scheduler.go:121
// LINE 67 type schedulerOptions struct { schedulerName string hardPodAffinitySymmetricWeight int32 enableEquivalenceClassCache bool disablePreemption bool percentageOfNodesToScore int32 bindTimeoutSeconds int64 } // …… // LINE 121 var defaultSchedulerOptions = schedulerOptions{ // "default-scheduler" schedulerName: v1.DefaultSchedulerName, // 1 hardPodAffinitySymmetricWeight: v1.DefaultHardPodAffinitySymmetricWeight, enableEquivalenceClassCache: false, disablePreemption: false, // 50 percentageOfNodesToScore: schedulerapi.DefaultPercentageOfNodesToScore, // 100 bindTimeoutSeconds: BindTimeoutSeconds, }
回到New()
函數的邏輯:
pkg/scheduler/scheduler.go:148
for _, opt := range opts { opt(&options) }
這里的 opts 定義在參數里:opts ...func(o *schedulerOptions)
,我們看一個實參來理解一下:scheduler.WithName(cc.ComponentConfig.SchedulerName)
:
pkg/scheduler/scheduler.go:80
// 這個函數能夠把給定的 schedulerName 賦值給 schedulerOptions.schedulerName func WithName(schedulerName string) Option { return func(o *schedulerOptions) { o.schedulerName = schedulerName } }
這種方式設置一個對象的屬性還是挺有意思的。
4.1. 調度算法源
我們繼續往后面看New()
函數的其他邏輯:
source := schedulerAlgorithmSource
這行代碼里的 schedulerAlgorithmSource 代表了什么?
形參中有這個變量的定義:schedulerAlgorithmSource kubeschedulerconfig.SchedulerAlgorithmSource
,跟進去可以看到:
pkg/scheduler/apis/config/types.go:97
// 表示調度算法源,兩個屬性是互相排斥的,也就是二選一的意思 type SchedulerAlgorithmSource struct { // Policy is a policy based algorithm source. Policy *SchedulerPolicySource // Provider is the name of a scheduling algorithm provider to use. Provider *string }
這兩個屬性肯定得理解一下了,目測挺重要的樣子:
Policy
pkg/scheduler/apis/config/types.go:106
type SchedulerPolicySource struct { // 文件方式配置生效的調度算法 File *SchedulerPolicyFileSource // ConfigMap 方式配置生效的調度算法 ConfigMap *SchedulerPolicyConfigMapSource } // 下面分別是2個屬性的結構定義: // …… type SchedulerPolicyFileSource struct { // Path is the location of a serialized policy. Path string } // …… type SchedulerPolicyConfigMapSource struct { // Namespace is the namespace of the policy config map. Namespace string // Name is the name of hte policy config map. Name string }
大家還記得我們在講調度器設計的時候提到的 Policy 文件不?大概長這個樣子:
{ "kind" : "Policy", "apiVersion" : "v1", "predicates" : [ {"name" : "PodFitsHostPorts"}, {"name" : "HostName"} ], "priorities" : [ {"name" : "LeastRequestedPriority", "weight" : 1}, {"name" : "EqualPriority", "weight" : 1} ], "hardPodAffinitySymmetricWeight" : 10, "alwaysCheckAllPredicates" : false }
所以啊,這個 Policy原來是通過代碼里的 SchedulerPolicySource 去配置的~
4.2. policy / provider 如何生效
前面講到調度算法從何而來(源頭),現在我們看一下這些算法配置如何生效的:
pkg/scheduler/scheduler.go:173
source := schedulerAlgorithmSource switch { // 如果 Provider 配置了,就不用 policy 了 case source.Provider != nil: // 根據給定的 Provider 創建 scheduler config sc, err := configurator.CreateFromProvider(*source.Provider) if err != nil { return nil, fmt.Errorf("couldn't create scheduler using provider %q: %v", *source.Provider, err) } config = sc // 如果 Policy 提供了,就沒有上面的 provider 的事情了 case source.Policy != nil: // 根據給定的 Policy 創建 scheduler config policy := &schedulerapi.Policy{} switch { // 是 File 的情況 case source.Policy.File != nil: if err := initPolicyFromFile(source.Policy.File.Path, policy); err != nil { return nil, err } // 是 ConfigMap 的情況 case source.Policy.ConfigMap != nil: if err := initPolicyFromConfigMap(client, source.Policy.ConfigMap, policy); err != nil { return nil, err } } sc, err := configurator.CreateFromConfig(*policy) if err != nil { return nil, fmt.Errorf("couldn't create scheduler from policy: %v", err) } config = sc default: return nil, fmt.Errorf("unsupported algorithm source: %v", source) }
上面代碼涉及到的2個類型我們再來關注一下:
- schedulerapi.Policy
- factory.Config
這個 Policy 就是具體用於存放我們配置的 policy 的載體,對照着這個結構我們可以判斷自己在配置 policy 的時候應該按照什么格式:
pkg/scheduler/api/types.go:47
type Policy struct { metav1.TypeMeta Predicates []PredicatePolicy Priorities []PriorityPolicy ExtenderConfigs []ExtenderConfig HardPodAffinitySymmetricWeight int32 AlwaysCheckAllPredicates bool }
這個結構內部封裝的一層層結構我就不繼續貼了,大家感興趣可以點開看一下,跟到底的落點都是基礎類型的,string啊,int啊,bool啊這些~
關於 factory.Config
可能大家有印象,這個結構就是 Scheduler 對象的唯一屬性:
pkg/scheduler/scheduler.go:58
type Scheduler struct { config *factory.Config }
Config 結構體的屬性不外乎 Scheduler 在落實調度、搶占等動作時所需要的一系列方法(或對象);在New()
函數的最后有一行sched := NewFromConfig(config)
,實現是簡單地實例化 Scheduler,然后將 config 賦值給 Scheduler 的 config 屬性,然后返回 Scheduler 對象的地址。
5. 默認生效的算法
我們最后還是單獨拎出來強調一下生效了哪些算法的具體邏輯吧,前面有提到一些了,我相信肯定有人很關注這個知識點。
前面提到 Scheduler 創建的時候使用的 New()
函數,函數中 switch 判斷 schedulerAlgorithmSource 是 Provider 還是 Policy,然后做了具體的初始化邏輯,我們具體看其中一個初始化, 串一下這些點:
sc, err := configurator.CreateFromProvider(*source.Provider)
如果我們配置的是 Provider,這時候代碼邏輯調用的是這樣一行,這個函數的實現如下:
pkg/scheduler/factory/factory.go:1156
func (c *configFactory) CreateFromProvider(providerName string) (*Config, error) { // 比如說我們配置的 name 是 DefaultProvider,這個函數要獲取一個 AlgorithmProviderConfig 類型的對象 provider, err := GetAlgorithmProvider(providerName) if err != nil { return nil, err } // 下面詳細看 return c.CreateFromKeys(provider.FitPredicateKeys, provider.PriorityFunctionKeys, []algorithm.SchedulerExtender{}) }
這個函數里有2個點需要關注,第一個是GetAlgorithmProvider()
函數返回了什么:
pkg/scheduler/factory/plugins.go:99
type AlgorithmProviderConfig struct { FitPredicateKeys sets.String PriorityFunctionKeys sets.String }
看到這個返回值類型,心里就明朗了。
我們繼續看比較重要的CreateFromKeys()
方法調用的具體邏輯,這個函數的實參中 provider.FitPredicateKeys, provider.PriorityFunctionKeys 很明顯和具體的 provider 相關,不同 provider 定義的預置算法不同。繼續來看函數實現:
pkg/scheduler/factory/factory.go:1255
func (c *configFactory) CreateFromKeys(predicateKeys, priorityKeys sets.String, extenders []algorithm.SchedulerExtender) (*Config, error) { // …… // 根據 predicateKeys 得到 predicateFuncs predicateFuncs, err := c.GetPredicates(predicateKeys) // 根據 priorityKeys 得到 priorityConfigs priorityConfigs, err := c.GetPriorityFunctionConfigs(priorityKeys) // …… // 創建一個 genericScheduler,這個對象我們很熟悉。algo 也就是 Algorithm 的簡寫; algo := core.NewGenericScheduler( c.schedulerCache, c.equivalencePodCache, c.podQueue, predicateFuncs, // 和 predicateKeys 對應 predicateMetaProducer, priorityConfigs, // 和 priorityKeys 對應 priorityMetaProducer, // …… ) podBackoff := util.CreateDefaultPodBackoff() return &Config{ // …… Algorithm: algo, // 很清晰了 // …… }, nil }
上面的NewGenericScheduler()
函數接收了這些參數之后丟給了 genericScheduler 對象,這個對象中 predicates 屬性對應參數 predicateFuncs,prioritizers 屬性對應參數 priorityConfigs;
從這里的代碼可以看出來我們配置的算法源可以影響到 Scheduler 的初始化,最終體現在改變了 Scheduler 對象的 config 屬性的 Algorithm 屬性的 prioritizers 和 prioritizers 上。我們最后回顧一下這2個屬性的類型,就和以前的預選、優選過程分析的時候關注的點對上了:
- predicates --> map[string]algorithm.FitPredicate
- prioritizers --> []algorithm.PriorityConfig
是不是很熟悉呢?
行,今天就講到這里~