1. 程式人生 > 其它 >[原始碼分析-kubernetes]8. 排程器初始化

[原始碼分析-kubernetes]8. 排程器初始化

排程器初始化

概述

今天我們要做一些瑣碎的知識點分析,比如排程器啟動的時候預設配置是怎麼來的?預設生效了哪些排程演算法?自定義的演算法是如何注入的?諸如這些問題,我們順帶會看一下排程器相關的一些資料結構的含義。看完前面這些節的分析後再看完本篇文章你可能會有一種醍醐灌頂的感覺哦~

從 --config 開始

如果我們編譯出來一個 kube-scheduler 二進位制檔案,執行./kube-scheduler -h後會看到很多的幫助資訊,這些資訊是分組的,比如第一組 Misc,差不多是“大雜燴”的意思,不好分類的幾個 flag,其實也是最重要的幾個 flag,如下:

很好理解,第一個紅框框圈出來的--config

用於指定配置檔案,老版本的各種引數基本都不建議使用了,所以這個 config flag 指定的 config 檔案中基本包含了所有可配置項,我們看一下程式碼中獲取這個 flag 的相關程式碼:

!FILENAME 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
}

上述程式碼中有幾個點可以關注到:

  1. FlagSet 的含義,命令列輸出的分組和這裡的分組是對應的;
  2. 除了認證授權、選舉等“非關鍵”配置外,其他配置基本 Deprecated 了,也就意味著建議使用 config file;

上面程式碼中可以看到o.ConfigFile接收了config配置,我們看看Option型別是什麼樣子的~

options.Option 物件

Options物件包含執行一個 Scheduler 所需要的所有引數

!FILENAME 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(這個物件後面會講到):

!FILENAME 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 物件又是什麼呢?

config.Config物件

Config 物件包含執行一個 Scheduler 所需要的所有 context

!FILENAME 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;是哪裡的邏輯讓 OptionsConfig 物件產生了關聯呢?(也就是說前面提到的 ApplyTo() 方法是再哪裡被呼叫的?)

繼續跟下去可以找到Config()函式,從這個函式的返回值*schedulerappconfig.Config可以看到它的目的,是需要得到一個 schedulerappconfig.Config,程式碼不長:

!FILENAME 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 裡面了~

runCommand

runCommand 這個函式我們不陌生:

!FILENAME 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 中了:

!FILENAME 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 中的邏輯

我們下面分別來看看~

ApplyFeatureGates

這個函式跟進去可以看到如下幾行簡單的程式碼,這裡很自然我們能夠想到繼續跟defaults.ApplyFeatureGates(),但是不能只看到這個函式哦,具體來看:

!FILENAME 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() 函式呼叫本身。

預設演算法註冊

!FILENAME pkg/scheduler/algorithmprovider/defaults/defaults.go:38

func init() {
   // ……
   registerAlgorithmProvider(defaultPredicates(), defaultPriorities())
   // ……
}

init()函式中我們先關注 **registerAlgorithmProvider() **函式,這裡從字面上可以得到不少資訊,大膽猜一下:是不是註冊了預設的預選演算法和優選演算法?

!FILENAME 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 這個概念,後面會講到。

先看一下里面呼叫的註冊函式是怎麼實現的:

!FILENAME 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:

!FILENAME pkg/scheduler/factory/plugins.go:99

type AlgorithmProviderConfig struct {
   FitPredicateKeys     sets.String
   PriorityFunctionKeys sets.String
}

接著看一下defaultPredicates()函式

!FILENAME 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()函式要幹嘛:

!FILENAME 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 是怎麼來的。

!FILENAME 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] = predicateFactoryfitPredicateMapkeyvalue,也就知道了這裡的 Register 要做什麼。

defaultPredicates()中的第二種註冊方式 RegisterFitPredicate 區別不大,函式體也是呼叫的 RegisterFitPredicateFactory()

!FILENAME pkg/scheduler/factory/plugins.go:106

func RegisterFitPredicate(name string, predicate algorithm.FitPredicate) string {
   return RegisterFitPredicateFactory(name, func(PluginFactoryArgs) algorithm.FitPredicate { return predicate })
}

特性開關

!FILENAME 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 是否開啟;函式引數本質只是一個字串:

!FILENAME pkg/features/kube_features.go:25

const (
   AppArmor utilfeature.Feature = "AppArmor"
   DynamicKubeletConfig utilfeature.Feature = "DynamicKubeletConfig"
   // ……
)

這裡定義了很多的 feature,然後定義了哪些 feature 是開啟的,處在 alpha 還是 beta 或者 GA 等:

!FILENAME 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" 的處理也是同一個邏輯。

Scheduler 的建立

我們換一條線,從 Scheduler 物件的建立再來看另外幾個知識點。

前面分析到runCommand()函式的時候我們說到了需要關注最後一行return Run(cc, stopCh)的邏輯,在Run()函式中主要的邏輯就是建立 Scheduler 和啟動 Scheduler;現在我們來看建立邏輯:

!FILENAME 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()函式的定義如下:

!FILENAME 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 物件:

!FILENAME 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()函式的邏輯:

!FILENAME pkg/scheduler/scheduler.go:148

for _, opt := range opts {
   opt(&options)
}

這裡的 opts 定義在引數裡:opts ...func(o *schedulerOptions),我們看一個實參來理解一下:scheduler.WithName(cc.ComponentConfig.SchedulerName)

!FILENAME pkg/scheduler/scheduler.go:80

// 這個函式能夠把給定的 schedulerName 賦值給 schedulerOptions.schedulerName
func WithName(schedulerName string) Option {
   return func(o *schedulerOptions) {
      o.schedulerName = schedulerName
   }
}

這種方式設定一個物件的屬性還是挺有意思的。

排程演算法源

我們繼續往後面看New()函式的其他邏輯:

source := schedulerAlgorithmSource 這行程式碼裡的 schedulerAlgorithmSource 代表了什麼?

形參中有這個變數的定義:schedulerAlgorithmSource kubeschedulerconfig.SchedulerAlgorithmSource,跟進去可以看到:

!FILENAME 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

!FILENAME 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 去配置的~

policy / provider 如何生效

前面講到排程演算法從何而來(源頭),現在我們看一下這些演算法配置如何生效的:

!FILENAME 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 的時候應該按照什麼格式:

!FILENAME 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 物件的唯一屬性:

!FILENAME pkg/scheduler/scheduler.go:58

type Scheduler struct {
   config *factory.Config
}

Config 結構體的屬性不外乎 Scheduler 在落實排程、搶佔等動作時所需要的一系列方法(或物件);在New()函式的最後有一行sched := NewFromConfig(config),實現是簡單地例項化 Scheduler,然後將 config 賦值給 Scheduler 的 config 屬性,然後返回 Scheduler 物件的地址。

預設生效的演算法

我們最後還是單獨拎出來強調一下生效了哪些演算法的具體邏輯吧,前面有提到一些了,我相信肯定有人很關注這個知識點。

前面提到 Scheduler 建立的時候使用的 New()函式,函式中 switch 判斷 schedulerAlgorithmSource 是 Provider 還是 Policy,然後做了具體的初始化邏輯,我們具體看其中一個初始化, 串一下這些點:

sc, err := configurator.CreateFromProvider(*source.Provider)

如果我們配置的是 Provider,這時候程式碼邏輯呼叫的是這樣一行,這個函式的實現如下:

!FILENAME 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()函式返回了什麼:

!FILENAME pkg/scheduler/factory/plugins.go:99

type AlgorithmProviderConfig struct {
   FitPredicateKeys     sets.String
   PriorityFunctionKeys sets.String
}

看到這個返回值型別,心裡就明朗了。

我們繼續看比較重要的CreateFromKeys()方法呼叫的具體邏輯,這個函式的實參中 provider.FitPredicateKeys, provider.PriorityFunctionKeys 很明顯和具體的 provider 相關,不同 provider 定義的預置演算法不同。繼續來看函式實現:

!FILENAME 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 屬性的 predicates 和 prioritizers 上。我們最後回顧一下這2個屬性的型別,就和以前的預選、優選過程分析的時候關注的點對上了:

  • predicates --> map[string]algorithm.FitPredicate
  • prioritizers --> []algorithm.PriorityConfig

引用連結:

gitbook:https://farmer-hutao.github.io/k8s-source-code-analysis/
github:https://hub.fastgit.org/daniel-hutao/k8s-source-code-analysis