Kubernetes调度概念与工作流程

K8S集群调度原理及调度策略[转] 目录 k8s调度器Scheduler Scheduler工作原理 请求及Scheduler调度步骤: 而这一个创建Pod对象,在调度的过程当中有3个阶段:节点预选、节点优选、节点选定,从而筛选出最佳的节点 k8s的调用工作方式 常用预选策略 常用优先函数 节点亲和性调度 节点硬亲和性 requiredDuringSchedulingIgnoredDu... 阅读详情

kubernetes集群中的调度程序 kube-scheduler 会 watch 未分配节点的新创建的Pod,并未该Pod找到可运行的最佳(特定)节点。那么这些动作或者说这些原理是怎么实现的呢,让我们往下剖析下。

对于新创建的 pod 或其他未调度的 pod来讲,kube-scheduler 选择一个最佳节点供它们运行。但是,Pod 中的每个容器对资源的要求都不同,每个 Pod 也有不同的要求。因此,需要根据具体的调度要求对现有节点进行过滤。

在Kubernetes集群中,满足 Pod 调度要求的节点称为可行节点 ( feasible nodes FN ) 。如果没有合适的节点,则 pod 将保持未调度状态,直到调度程序能够放置它。也就是说,当我们创建Pod时,如果长期处于 Pending 状态,这个时候应该看你的集群调度器是否因为某些问题没有合适的节点了

调度器为 Pod 找到 FN 后,然后运行一组函数对 FN 进行评分,并在 FN 中找到得分最高的节点来运行 Pod。

调度策略在决策时需要考虑的因素包括个人和集体资源需求、硬件/软件/策略约束 ( constraints )、亲和性 ( affinity ) 和反亲和性( anti-affinity )规范、数据局部性、工作负载间干扰等。

如何为pod选择节点?

kube-scheduler 为pod选择节点会分位两部:

Filtering
Scoring

过滤也被称为预选 ( Predicates ),该步骤会找到可调度的节点集,然后通过是否满足特定资源的请求,例如通过 PodFitsResources 过滤器检查候选节点是否有足够的资源来满足 Pod 资源的请求。这个步骤完成后会得到一个包含合适的节点的列表(通常为多个),如果列表为空,则Pod不可调度。

打分也被称为优选( Priorities ),在该步骤中,会对上一个步骤的输出进行打分,Scheduer 通过打分的规则为每个通过 Filtering 步骤的节点计算出一个分数。

完成上述两个步骤之后, kube-scheduler 会将Pod分配给分数最高的 Node,如果存在多个相同分数的节点,会随机选择一个。

kubernetes的调度策略

Kubernetes 1.21之前版本可以在代码 kubernetes\pkg\scheduler\algorithmprovider\registry.go 中看到对应的注册模式,在1.22 scheduler 更换了其路径,对于registry文件更换到了 kubernetes\pkg\scheduler\framework\plugins\registry.go ;对于kubernetes官方说法为, 调度策略是用于“预选” ( Predicates )或 过滤( filtering ) 和 用于 优选( Priorities )或 评分 ( scoring )的

注:kubernetes官方没有找到预选和优选的概念,而Predicates和filtering 是处于预选阶段的动词,而Priorities和scoring是优选阶段的动词。后面用PF和PS代替这个两个词。

上面也提到了, filtering 的目的是为了排除(过滤)掉不满足 Pod 要求的节点。例如,某个节点上的闲置资源小于 Pod 所需资源,则该节点不会被考虑在内,即被过滤掉。在 “Predicates” 阶段实现的 filtering 策略,包括:

  • NoDiskConflict :评估是否有合适Pod请求的卷
  • NoVolumeZoneConflict :在给定zone限制情况下,评估Pod请求所需的卷在Node上是否可用
  • PodFitsResources :检查空闲资源(CPU、内存)是否满足Pod请求
  • PodFitsHostPorts :检查Pod所需端口在Node上是否被占用
  • HostName : 过滤除去, PodSpec 中 NodeName 字段中指定的Node之外的所有Node。
  • MatchNodeSelector :检查Node的 label 是否与 Pod 配置中 nodeSelector 字段中指定的 label 匹配,并且从 Kubernetes v1.2 开始, 如果存在 nodeAffinity 也会匹配。
  • CheckNodeMemoryPressure :检查是否可以在已出现内存压力情况节点上调度 Pod。
  • CheckNodeDiskPressure :检查是否可以在报告磁盘压力情况的节点上调度 Pod

具体对应得策略可以在 kubernetes\pkg\scheduler\framework\plugins\registry.go 看到

通过上面步骤过滤过得列表则是适合托管的Pod,这个结果通常来说是一个列表,如何选择最优Node进行调度,则是接下来打分的步骤步骤。

例如:Kubernetes对剩余节点进行优先级排序,优先级由一组函数计算;优先级函数将为剩余节点给出从 0~10 的分数,10 表示最优,0 表示最差。每个优先级函数由一个正数加权组成,每个Node的得分是通过将所有加权得分相加来计算的。设有两个优先级函数, priorityFunc1 和 priorityFunc2 加上权重因子 weight1 和 weight2 ,那么这个Node的最终得分为: \(finalScore = (w1 \times priorityFunc1) + (w2 \times priorityFunc2)\) 。计算完分数后,选择最高分数的Node做为Pod的宿主机,存在多个相同分数Node情况下会随机选择一个Node。

目前kubernetes提供了一些在打分 Scoring 阶段算法:

  • LeastRequestedPriority :Node的优先级基于Node的空闲部分 \(\frac{capacity\ -\ Node上所有存在的Pod\ -\ 正在调度的Pod请求}{capacity}\) ,通过计算具有最高分数的Node是FN
  • BalancedResourceAllocation :该算法会将 Pod 放在一个Node上,使得在Pod 部署后 CPU 和内存的使用率为平衡的
  • SelectorSpreadPriority :通过最小化资源方式,将属于同一种服务、控制器或同一Node上的Replica的 Pod的数量来分布Pod。如果节点上存在Zone,则会调整优先级,以便 pod可以分布在Zone之上。
  • CalculateAntiAffinityPriority :根据label来分布,按照相同service上相同label值的pod进行分配
  • ImageLocalityPriority :根据Node上镜像进行打分,Node上存在Pod请求所需的镜像优先级较高。

在代码中查看上述的代码

以 PodFitsHostPorts 算法为例,因为是Node类算法,在 kubernetes\pkg\scheduler\framework\plugins\nodeports

调度框架 ( scheduling framework SF ) 是kubernetes为 scheduler设计的一个pluggable的架构。SF 将scheduler设计为 Plugin 式的 API,API将上一章中提到的一些列调度策略实现为 Plugin 。

在 SF 中,定义了一些扩展点 ( extension points EP ),而被实现为Plugin的调度程序将被注册在一个或多个 EP 中,换句话来说,在这些 EP 的执行过程中如果注册在多个 EP 中,将会在多个 EP 被调用。

每次调度都分为两个阶段,调度周期( Scheduling Cycel )与绑定周期( Binding Cycle )。

  • SC 表示为,为Pod选择一个节点; SC 是串行运行的。
  • BC 表示为,将 SC 决策结果应用于集群中; BC 可以同时运行。

调度周期与绑定周期结合一起,被称为 调度上下文 ( Scheduling Context ),下图则是调度上下文的工作流

注:如果决策结果为Pod的调度结果无可用节点,或存在内部错误,则中止 SC 或 BC 。Pod将重入队列重试

图1:Pod的调度上下文

Source: https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework

扩展点( Extension points )是指在 调度上下文 中的每个可扩展API,通过图提现为。其中 Filter 相当于 Predicate 而 Scoring 相当于 Priority 。

对于调度阶段会通过以下扩展点:

  • Sort :该插件提供了排序功能,用于对在调度队列中待处理 Pod 进行排序。一次只能启用一个队列排序。

  • preFilter :该插件用于在过滤之前预处理或检查 Pod 或集群的相关信息。这里会终止调度

  • filter :该插件相当于 调度上下文 中的 Predicates ,用于排除不能运行 Pod 的节点。Filter 会按配置的顺序进行调用。如果有一个filter将节点标记位不可用,则将 Pod 标记为不可调度(即不会向下执行)。

  • postFilter :当没有为 pod 找到 FN 时,该插件会按照配置的顺序进行调用。如果任何 postFilter 插件将 Pod 标记为 schedulable ,则不会调用其余插件。即 filter 成功后不会进行这步骤

  • preScore :可用于进行预Score工作(通知性的扩展点)。

  • score :该插件为每个通过 filter 阶段的Node提供打分服务。然后Scheduler将选择具有最高加权分数总和的Node。

  • reserve :因为绑定事件时异步发生的,该插件是为了避免Pod在绑定到节点前时,调度到新的Pod,使节点使用资源超过可用资源情况。如果后续阶段发生错误或失败,将触发 UnReserve 回滚(通知性扩展点)。这也是作为调度周期中最后一个状态,要么成功到 postBind ,要么失败触发 UnReserve 。

  • permit :该插件可以阻止或延迟 Pod 的绑定,一般情况下这步骤会做三件事:

    • appove :调度器继续绑定过程
    • Deny :如果任何一个Premit拒绝了Pod与节点的绑定,那么将触发 UnReserve ,并重入队列
    • Wait : 如果 Permit 插件返回 Wait ,该 Pod 将保留在内部 Wait Pod 列表中,直到被 Appove 。如果发生超时, wait 变为 deny ,将Pod放回至调度队列中,并触发 Unreserve 回滚 。
  • preBind :该插件用于在 bind Pod 之前执行所需的前置工作。如, preBind 可能会提供一个网络卷并将其挂载到目标节点上。如果在该步骤中的任意插件返回错误,则Pod 将被 deny 并放置到调度队列中。

  • bind :在所有的 preBind 完成后,该插件将用于将Pod绑定到Node,并按顺序调用绑定该步骤的插件。如果有一个插件处理了这个事件,那么则忽略其余所有插件。

  • postBind :该插件在绑定 Pod 后调用,可用于清理相关资源(通知性的扩展点)。

  • multiPoint :这是一个仅配置字段,允许同时为所有适用的扩展点启用或禁用插件。

kube-scheduler工作流分析

对于 kube-scheduler 组件的分析,包含 kube-scheduler 启动流程,以及scheduler调度流程。这里会主要针对启动流程分析,后面算法及二次开发部分会切入调度分析。

对于我们部署时使用的 kube-scheduler 位于 cmd/kube-scheduler ,在 Alpha (1.16) 版本提供了调度框架的模式,到 Stable (1.19) ,从代码结构上是相似的;直到1.22后改变了代码风格。

首先看到的是 kube-scheduler 的入口 cmd/kube-scheduler ,这里主要作为两部分,构建参数与启动 server ,这里严格来讲 kube-scheduer 是作为一个server,而调度框架等部分是另外的。

func main() {
	command := app.NewSchedulerCommand()
	code := cli.Run(command)
	os.Exit(code)
}
cli.Run 提供了cobra构成的命令行cli,日志将输出为标准输出

// 这里是main中执行的Run
func Run(cmd *cobra.Command) int {
	if logsInitialized, err := run(cmd); err != nil {
		if !logsInitialized {
			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		} else {
			klog.ErrorS(err, "command failed")
		}
		return 1
	}
	return 0
}
// 这个run作为
func run(cmd *cobra.Command) (logsInitialized bool, err error) {
	rand.Seed(time.Now().UnixNano())
	defer logs.FlushLogs()

	cmd.SetGlobalNormalizationFunc(cliflag.WordSepNormalizeFunc)

	if !cmd.SilenceUsage {
		cmd.SilenceUsage = true
		cmd.SetFlagErrorFunc(func(c *cobra.Command, err error) error {
			// Re-enable usage printing.
			c.SilenceUsage = false
			return err
		})
	}

	// In all cases error printing is done below.
	cmd.SilenceErrors = true

	// This is idempotent.
	logs.AddFlags(cmd.PersistentFlags())

	// Inject logs.InitLogs after command line parsing into one of the
	// PersistentPre* functions.
	switch {
	case cmd.PersistentPreRun != nil:
		pre := cmd.PersistentPreRun
		cmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
			logs.InitLogs()
			logsInitialized = true
			pre(cmd, args)
		}
	case cmd.PersistentPreRunE != nil:
		pre := cmd.PersistentPreRunE
		cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
			logs.InitLogs()
			logsInitialized = true
			return pre(cmd, args)
		}
	default:
		cmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
			logs.InitLogs()
			logsInitialized = true
		}
	}

	err = cmd.Execute()
	return
}

可以看到最终是调用 command.Execute() 执行,这个是执行本身构建的命令,而真正被执行的则是上面的 app.NewSchedulerCommand() ,那么来看看这个是什么

app.NewSchedulerCommand() 构建了一个cobra.Commond对象, runCommand() 被封装在内,这个是作为启动scheduler的函数

func NewSchedulerCommand(registryOptions ...Option) *cobra.Command {
	opts := options.NewOptions()

	cmd := &cobra.Command{
		Use: "kube-scheduler",
		Long: `The Kubernetes scheduler is a control plane process which assigns
Pods to Nodes. The scheduler determines which Nodes are valid placements for
each Pod in the scheduling queue according to constraints and available
resources. The scheduler then ranks each valid Node and binds the Pod to a
suitable Node. Multiple different schedulers may be used within a cluster;
kube-scheduler is the reference implementation.
See [scheduling](https://kubernetes.io/docs/concepts/scheduling-eviction/)
for more information about scheduling and the kube-scheduler component.`,
		RunE: func(cmd *cobra.Command, args []string) error {
			return runCommand(cmd, opts, registryOptions...)
		},
		Args: func(cmd *cobra.Command, args []string) error {
			for _, arg := range args {
				if len(arg) > 0 {
					return fmt.Errorf("%q does not take any arguments, got %q", cmd.CommandPath(), args)
				}
			}
			return nil
		},
	}

	nfs := opts.Flags
	verflag.AddFlags(nfs.FlagSet("global"))
	globalflag.AddGlobalFlags(nfs.FlagSet("global"), cmd.Name(), logs.SkipLoggingConfigurationFlags())
	fs := cmd.Flags()
	for _, f := range nfs.FlagSets {
		fs.AddFlagSet(f)
	}

	cols, _, _ := term.TerminalSize(cmd.OutOrStdout())
	cliflag.SetUsageAndHelpFunc(cmd, *nfs, cols)

	if err := cmd.MarkFlagFilename("config", "yaml", "yml", "json"); err != nil {
		klog.ErrorS(err, "Failed to mark flag filename")
	}

	return cmd
}

下面来看下 runCommand() 在启动 scheduler 时提供了什么功能。

在新版中已经没有 algorithmprovider 的概念,所以在 runCommand 中做的也就是仅仅启动这个 scheduler ,而 scheduler 作为kubernetes组件,也是会watch等操作,自然少不了informer。其次作为和 controller-manager 相同的工作特性, kube-scheduler 也是 基于Leader选举的。

func Run(ctx context.Context, cc *schedulerserverconfig.CompletedConfig, sched *scheduler.Scheduler) error {
	// To help debugging, immediately log version
	klog.InfoS("Starting Kubernetes Scheduler", "version", version.Get())

	klog.InfoS("Golang settings", "GOGC", os.Getenv("GOGC"), "GOMAXPROCS", os.Getenv("GOMAXPROCS"), "GOTRACEBACK", os.Getenv("GOTRACEBACK"))

	// Configz registration.
	if cz, err := configz.New("componentconfig"); err == nil {
		cz.Set(cc.ComponentConfig)
	} else {
		return fmt.Errorf("unable to register configz: %s", err)
	}

	// Start events processing pipeline.
	cc.EventBroadcaster.StartRecordingToSink(ctx.Done())
	defer cc.EventBroadcaster.Shutdown()

	// Setup healthz checks.
	var checks []healthz.HealthChecker
	if cc.ComponentConfig.LeaderElection.LeaderElect {
		checks = append(checks, cc.LeaderElection.WatchDog)
	}

	waitingForLeader := make(chan struct{})
	isLeader := func() bool {
		select {
		case _, ok := <-waitingForLeader:
			// if channel is closed, we are leading
			return !ok
		default:
			// channel is open, we are waiting for a leader
			return false
		}
	}

	// Start up the healthz server.
	if cc.SecureServing != nil {
		handler := buildHandlerChain(newHealthzAndMetricsHandler(&cc.ComponentConfig, cc.InformerFactory, isLeader, checks...), cc.Authentication.Authenticator, cc.Authorization.Authorizer)
		// TODO: handle stoppedCh and listenerStoppedCh returned by c.SecureServing.Serve
		if _, _, err := cc.SecureServing.Serve(handler, 0, ctx.Done()); err != nil {
			// fail early for secure handlers, removing the old error loop from above
			return fmt.Errorf("failed to start secure server: %v", err)
		}
	}

	// Start all informers.
	cc.InformerFactory.Start(ctx.Done())
	// DynInformerFactory can be nil in tests.
	if cc.DynInformerFactory != nil {
		cc.DynInformerFactory.Start(ctx.Done())
	}

	// Wait for all caches to sync before scheduling.
	cc.InformerFactory.WaitForCacheSync(ctx.Done())
	// DynInformerFactory can be nil in tests.
	if cc.DynInformerFactory != nil {
		cc.DynInformerFactory.WaitForCacheSync(ctx.Done())
	}

	// If leader election is enabled, runCommand via LeaderElector until done and exit.
	if cc.LeaderElection != nil {
		cc.LeaderElection.Callbacks = leaderelection.LeaderCallbacks{
			OnStartedLeading: func(ctx context.Context) {
				close(waitingForLeader)
				sched.Run(ctx)
			},
			OnStoppedLeading: func() {
				select {
				case <-ctx.Done():
					// We were asked to terminate. Exit 0.
					klog.InfoS("Requested to terminate, exiting")
					os.Exit(0)
				default:
					// We lost the lock.
					klog.ErrorS(nil, "Leaderelection lost")
					klog.FlushAndExit(klog.ExitFlushTimeout, 1)
				}
			},
		}
		leaderElector, err := leaderelection.NewLeaderElector(*cc.LeaderElection)
		if err != nil {
			return fmt.Errorf("couldn't create leader elector: %v", err)
		}

		leaderElector.Run(ctx)

		return fmt.Errorf("lost lease")
	}

	// Leader election is disabled, so runCommand inline until done.
	close(waitingForLeader)
	sched.Run(ctx)
	return fmt.Errorf("finished without leader elect")
}

上面看到了 runCommend 是作为启动 scheduler 的工作,那么通过参数构建一个 scheduler 则是在 Setup 中完成的。

// Setup creates a completed config and a scheduler based on the command args and options
func Setup(ctx context.Context, opts *options.Options, outOfTreeRegistryOptions ...Option) (*schedulerserverconfig.CompletedConfig, *scheduler.Scheduler, error) {
	if cfg, err := latest.Default(); err != nil {
		return nil, nil, err
	} else {
		opts.ComponentConfig = cfg
	}
	// 验证参数
	if errs := opts.Validate(); len(errs) > 0 {
		return nil, nil, utilerrors.NewAggregate(errs)
	}
	// 构建一个config对象
	c, err := opts.Config()
	if err != nil {
		return nil, nil, err
	}

	// 返回一个config对象,包含了scheduler所需的配置,如informer,leader selection
	cc := c.Complete()

	outOfTreeRegistry := make(runtime.Registry)
	for _, option := range outOfTreeRegistryOptions {
		if err := option(outOfTreeRegistry); err != nil {
			return nil, nil, err
		}
	}

	recorderFactory := getRecorderFactory(&cc)
	completedProfiles := make([]kubeschedulerconfig.KubeSchedulerProfile, 0)
	// 创建出来的scheduler
	sched, err := scheduler.New(cc.Client,
		cc.InformerFactory,
		cc.DynInformerFactory,
		recorderFactory,
		ctx.Done(),
		scheduler.WithComponentConfigVersion(cc.ComponentConfig.TypeMeta.APIVersion),
		scheduler.WithKubeConfig(cc.KubeConfig),
		scheduler.WithProfiles(cc.ComponentConfig.Profiles...),
		scheduler.WithPercentageOfNodesToScore(cc.ComponentConfig.PercentageOfNodesToScore),
		scheduler.WithFrameworkOutOfTreeRegistry(outOfTreeRegistry),
		scheduler.WithPodMaxBackoffSeconds(cc.ComponentConfig.PodMaxBackoffSeconds),
		scheduler.WithPodInitialBackoffSeconds(cc.ComponentConfig.PodInitialBackoffSeconds),
		scheduler.WithPodMaxInUnschedulablePodsDuration(cc.PodMaxInUnschedulablePodsDuration),
		scheduler.WithExtenders(cc.ComponentConfig.Extenders...),
		scheduler.WithParallelism(cc.ComponentConfig.Parallelism),
		scheduler.WithBuildFrameworkCapturer(func(profile kubeschedulerconfig.KubeSchedulerProfile) {
			// Profiles are processed during Framework instantiation to set default plugins and configurations. Capturing them for logging
			completedProfiles = append(completedProfiles, profile)
		}),
	)
	if err != nil {
		return nil, nil, err
	}
	if err := options.LogOrWriteConfig(opts.WriteConfigTo, &cc.ComponentConfig, completedProfiles); err != nil {
		return nil, nil, err
	}

	return &cc, sched, nil
}

上面了解到了 scheduler 是如何被构建出来的,下面就看看 构建时参数是如何传递进来的,而对象 option就是对应需要的配置结构,而 ApplyTo 则是将启动时传入的参数转化为构建 scheduler 所需的配置。

对于Deprecated flags可以参考官方对于kube-scheduler启动参数的说明

对于如何编写一个scheduler config请参考与

func (o *Options) ApplyTo(c *schedulerappconfig.Config) error {
	if len(o.ConfigFile) == 0 {
		// 在没有指定 --config时会找到 Deprecated flags:参数
        // 通过kube-scheduler --help可以看到这些被弃用的参数
		o.ApplyDeprecated()
		o.ApplyLeaderElectionTo(o.ComponentConfig)
		c.ComponentConfig = *o.ComponentConfig
	} else {
        // 这里就是指定了--config
		cfg, err := loadConfigFromFile(o.ConfigFile)
		if err != nil {
			return err
		}
		// 这里会将leader选举的参数附加到配置中
		o.ApplyLeaderElectionTo(cfg)

		if err := validation.ValidateKubeSchedulerConfiguration(cfg); err != nil {
			return err
		}

		c.ComponentConfig = *cfg
	}

	if err := o.SecureServing.ApplyTo(&c.SecureServing, &c.LoopbackClientConfig); err != nil {
		return err
	}
	if o.SecureServing != nil && (o.SecureServing.BindPort != 0 || o.SecureServing.Listener != nil) {
		if err := o.Authentication.ApplyTo(&c.Authentication, c.SecureServing, nil); err != nil {
			return err
		}
		if err := o.Authorization.ApplyTo(&c.Authorization); err != nil {
			return err
		}
	}
	o.Metrics.Apply()

	// Apply value independently instead of using ApplyDeprecated() because it can't be configured via ComponentConfig.
	if o.Deprecated != nil {
		c.PodMaxInUnschedulablePodsDuration = o.Deprecated.PodMaxInUnschedulablePodsDuration
	}

	return nil
}

Setup 后会new一个 schedueler , New 则是这个动作,在里面可以看出,会初始化一些informer与 Pod的list等操作。

func New(client clientset.Interface,
	informerFactory informers.SharedInformerFactory,
	dynInformerFactory dynamicinformer.DynamicSharedInformerFactory,
	recorderFactory profile.RecorderFactory,
	stopCh <-chan struct{},
	opts ...Option) (*Scheduler, error) {

	stopEverything := stopCh
	if stopEverything == nil {
		stopEverything = wait.NeverStop
	}

	options := defaultSchedulerOptions // 默认调度策略,如percentageOfNodesToScore
	for _, opt := range opts {
		opt(&options) // opt 是传入的函数,会返回一个schedulerOptions即相应的一些配置
	}

	if options.applyDefaultProfile { // 这个是个bool类型,默认scheduler会到这里
        // Profile包含了调度器的名称与调度器在两个过程中使用的插件
		var versionedCfg v1beta3.KubeSchedulerConfiguration
		scheme.Scheme.Default(&versionedCfg)
		cfg := schedulerapi.KubeSchedulerConfiguration{} // 初始化一个配置,这个是--config传入的类型。因为默认的调度策略会初始化
        // convert 会将in转为out即versionedCfg转换为cfg
		if err := scheme.Scheme.Convert(&versionedCfg, &cfg, nil); err != nil {
			return nil, err
		}
		options.profiles = cfg.Profiles
	}

	registry := frameworkplugins.NewInTreeRegistry() // 调度框架的注册
	if err := registry.Merge(options.frameworkOutOfTreeRegistry); err != nil {
		return nil, err
	}

	metrics.Register() // 指标类

	extenders, err := buildExtenders(options.extenders, options.profiles)
	if err != nil {
		return nil, fmt.Errorf("couldn't build extenders: %w", err)
	}

	podLister := informerFactory.Core().V1().Pods().Lister()
	nodeLister := informerFactory.Core().V1().Nodes().Lister()

	// The nominator will be passed all the way to framework instantiation.
	nominator := internalqueue.NewPodNominator(podLister)
	snapshot := internalcache.NewEmptySnapshot()
	clusterEventMap := make(map[framework.ClusterEvent]sets.String)

	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")
	}

	podQueue := internalqueue.NewSchedulingQueue(
		profiles[options.profiles[0].SchedulerName].QueueSortFunc(),
		informerFactory,
		internalqueue.WithPodInitialBackoffDuration(time.Duration(options.podInitialBackoffSeconds)*time.Second),
		internalqueue.WithPodMaxBackoffDuration(time.Duration(options.podMaxBackoffSeconds)*time.Second),
		internalqueue.WithPodNominator(nominator),
		internalqueue.WithClusterEventMap(clusterEventMap),
		internalqueue.WithPodMaxInUnschedulablePodsDuration(options.podMaxInUnschedulablePodsDuration),
	)

	schedulerCache := internalcache.New(durationToExpireAssumedPod, stopEverything)

	// Setup cache debugger.
	debugger := cachedebugger.New(nodeLister, podLister, schedulerCache, podQueue)
	debugger.ListenForSignal(stopEverything)

	sched := newScheduler(
		schedulerCache,
		extenders,
		internalqueue.MakeNextPodFunc(podQueue),
		MakeDefaultErrorFunc(client, podLister, podQueue, schedulerCache),
		stopEverything,
		podQueue,
		profiles,
		client,
		snapshot,
		options.percentageOfNodesToScore,
	)
	// 这个就是controller中onAdd等那三个必须的事件函数
	addAllEventHandlers(sched, informerFactory, dynInformerFactory, unionedGVKs(clusterEventMap))

	return sched, nil
}

接下来会启动这个 scheduler , 在上面我们看到 NewSchedulerCommand 构建了一个cobra.Commond对象, runCommand() 最终会返回个 Run,而这个Run就是启动这个 sche 的。

下面这个 run 是 sche 的运行,他运行并watch资源,直到上下文完成。

func (sched *Scheduler) Run(ctx context.Context) {
	sched.SchedulingQueue.Run()

	// We need to start scheduleOne loop in a dedicated goroutine,
	// because scheduleOne function hangs on getting the next item
	// from the SchedulingQueue.
	// If there are no new pods to schedule, it will be hanging there
	// and if done in this goroutine it will be blocking closing
	// SchedulingQueue, in effect causing a deadlock on shutdown.
	go wait.UntilWithContext(ctx, sched.scheduleOne, 0)

	<-ctx.Done()
	sched.SchedulingQueue.Close()
}

而调用这个 Run 的部分则是作为server的 kube-scheduler 中的 run

// Run executes the scheduler based on the given configuration. It only returns on error or when context is done.
func Run(ctx context.Context, cc *schedulerserverconfig.CompletedConfig, sched *scheduler.Scheduler) error {
	// To help debugging, immediately log version
	klog.InfoS("Starting Kubernetes Scheduler", "version", version.Get())

	klog.InfoS("Golang settings", "GOGC", os.Getenv("GOGC"), "GOMAXPROCS", os.Getenv("GOMAXPROCS"), "GOTRACEBACK", os.Getenv("GOTRACEBACK"))

	// Configz registration.
	if cz, err := configz.New("componentconfig"); err == nil {
		cz.Set(cc.ComponentConfig)
	} else {
		return fmt.Errorf("unable to register configz: %s", err)
	}

	// Start events processing pipeline.
	cc.EventBroadcaster.StartRecordingToSink(ctx.Done())
	defer cc.EventBroadcaster.Shutdown()

	// Setup healthz checks.
	var checks []healthz.HealthChecker
	if cc.ComponentConfig.LeaderElection.LeaderElect {
		checks = append(checks, cc.LeaderElection.WatchDog)
	}

	waitingForLeader := make(chan struct{})
	isLeader := func() bool {
		select {
		case _, ok := <-waitingForLeader:
			// if channel is closed, we are leading
			return !ok
		default:
			// channel is open, we are waiting for a leader
			return false
		}
	}

	// Start up the healthz server.
	if cc.SecureServing != nil {
		handler := buildHandlerChain(newHealthzAndMetricsHandler(&cc.ComponentConfig, cc.InformerFactory, isLeader, checks...), cc.Authentication.Authenticator, cc.Authorization.Authorizer)
		// TODO: handle stoppedCh and listenerStoppedCh returned by c.SecureServing.Serve
		if _, _, err := cc.SecureServing.Serve(handler, 0, ctx.Done()); err != nil {
			// fail early for secure handlers, removing the old error loop from above
			return fmt.Errorf("failed to start secure server: %v", err)
		}
	}

	// Start all informers.
	cc.InformerFactory.Start(ctx.Done())
	// DynInformerFactory can be nil in tests.
	if cc.DynInformerFactory != nil {
		cc.DynInformerFactory.Start(ctx.Done())
	}

	// Wait for all caches to sync before scheduling.
	cc.InformerFactory.WaitForCacheSync(ctx.Done())
	// DynInformerFactory can be nil in tests.
	if cc.DynInformerFactory != nil {
		cc.DynInformerFactory.WaitForCacheSync(ctx.Done())
	}

	// If leader election is enabled, runCommand via LeaderElector until done and exit.
	if cc.LeaderElection != nil {
		cc.LeaderElection.Callbacks = leaderelection.LeaderCallbacks{
			OnStartedLeading: func(ctx context.Context) {
				close(waitingForLeader)
				sched.Run(ctx)
			},
			OnStoppedLeading: func() {
				select {
				case <-ctx.Done():
					// We were asked to terminate. Exit 0.
					klog.InfoS("Requested to terminate, exiting")
					os.Exit(0)
				default:
					// We lost the lock.
					klog.ErrorS(nil, "Leaderelection lost")
					klog.FlushAndExit(klog.ExitFlushTimeout, 1)
				}
			},
		}
		leaderElector, err := leaderelection.NewLeaderElector(*cc.LeaderElection)
		if err != nil {
			return fmt.Errorf("couldn't create leader elector: %v", err)
		}

		leaderElector.Run(ctx)

		return fmt.Errorf("lost lease")
	}

	// Leader election is disabled, so runCommand inline until done.
	close(waitingForLeader)
	sched.Run(ctx)
	return fmt.Errorf("finished without leader elect")
}

而上面的 server.Run 会被 runCommand 也就是在 NewSchedulerCommand 时被返回,在 kube-scheduler 的入口文件中被执行。

cc, sched, err := Setup(ctx, opts, registryOptions...)
if err != nil {
    return err
}

return Run(ctx, cc, sched)

至此,整个 kube-scheduler 启动流就分析完了,这个的流程可以用下图表示

图2:scheduler server运行流程

k8s重要节点组件以及工作流程介绍 Kubernetes一.k8s概述1.K8S作用2.由来3.含义4.为什么要用K8S?5.k8s的特性二.k8s集群架构与组件1.核心组件2.k8s核心概念三.常见的k8s部署方式四.k8s工作流程 一.k8s概述 1.K8S作用 用于自动部署、扩展和管理“容器化( containerized) 应用程序”的开源系统 可以理解成K8S是负责自动化运维管理多个容器化程序(比如Docker)的集群,是–个生态极其丰富的容器编排框架工具 2.由来 k8S由google的Borg系统(博格系统,google内部使用 阅读详情

相关推荐

Kubernetes(k8s) 架构原理一文详解

K8S 的全称为 Kubernetes。用于自动部署、扩展和管理“容器化(containerized)应用程序”的开源系统。可以理解成 K8S 是负责自动化运维管理多个容器化程序(比如 Docker)的集群,是一个生态极其丰富的容器编排框架工具。K8S是Google开源的容器集群管理系统,在Docker等容器技术的基础上,为容器化的应用提供部署运行、资源调度、服务发现和动态伸缩等一系列完整功能,提高了大规模容器集群管理的便捷性。

wlc1213812138的博客 1600

kubernetes调度概念与工作流程

但是,Pod中的每个容器对资源的要求都不同,每个Pod也有不同的要求。注kubernetes官方没有找到预选和优选的概念,而Predicates和filtering是处于预选阶段的动词,而Priorities和scoring是优选阶段的动词。因为绑定事件时异步发生的,该插件是为了避免Pod在绑定到节点前时,调度到新的Pod,使节点使用资源超过可用资源情况。通过上面步骤过滤过得列表则是适合托管的Pod,这个结果通常来说是一个列表,如何选择最优Node进行调度,则是接下来打分的步骤步骤。...

期待幸福 755

K8S及pod相关介绍

K8S基本概念;Pod 概念;Kubernetes ⼯作流程;K8S 创建 Pod 流程;

m0_73907608的博客 2016

K8S里面的调度整理

K8S是分布式系统里面的操作系统,Pod更像是操作系统里面的进程组,如此以来当一个Pod想要运行的时候,就必须要依赖于K8S的调度策略来完成这些Pod的调度。本篇文章就是来整...

zhghost的专栏 5635

【K8S系列】深入解析K8S调度

Kubernetes调度是Kubernetes的核心功能之一,它能够自动将Pod分配到最合适的节点上,并确保Pod在所选节点上正常运行。调度器通过算法选择最佳的节点,并将Pod绑定到该节点上。调度器选择节点的依据包括节点的资源使用情况、Pod的资源需求、亲和性和反亲和性等。管理员可以根据自己的需求选择不同的调度算法,并监视调度器的日志以确保集群的正常运行。

颜淡慕潇 3万+

k8s 常用调度策略使用详解

k8s 常用调度策略

congge 4873

初步了解Kubernetes

还监控node节点上的资源信息,pod状态。从Master节点获取自己节点上Pod的期望状态(比如运行什么容器、运行的副本数量、网络或者存储如何配置等),直接跟容器引擎交互实现容器的生命周期管理,如果自己节点上Pod的状态与期望状态不一致,则调用对应的容器平台接口(即docker的接口)达到这个状态。在K8S的集群里,虽然每个Pod会被分配一个单独的IP地址,但由于Pod是有生命周期的(它们可以被创建,而且销毁之后不会再启动),随时可能会因为业务的变更,导致这个IP地址也会随着Pod的销毁而消失。

weixin_58376680的博客 1267

20250720-2-Kubernetes 调度-资源限制对Pod调度的影响(1)_笔记

默认调度器: 通过schedulerName: default-scheduler指定自定义调度器: Kubernetes支持多个调度器并存,可通过指定不同调度器名称使用自定义调度逻辑。

1286

Kubernetes概述

负责K8S集群的管理和资源调度等运维工作,组件有 apiserver、controller-manager、scheduler、etcdapiserver:是所有服务请求的统一访问入口controller-manager:控制器管理器,负责管理K8S各种资源对象的控制器;并通过apiserver监控整个K8S集群的资源状态,并确保资源始终处于预期的工作状态scheduler:资源调度器,负责Pod资源的调度,通过调度算法(预选/优选策略)为要部署的Pod选择最合适的node节点。

Ybaocheng的博客 1021

Matlab激活失败?手把手教你解决License checkout failed问题(附详细截图)

本文针对Matlab激活后出现“License checkout failed”错误的常见问题,提供了详细的解决方案。核心在于精准定位并正确填写Windows系统用户名,而非计算机名或账户全名。文章通过分步截图和命令指引,手把手教你使用`whoami`命令获取正确信息,并重新运行激活程序,从根本上解决因用户名填写错误导致的许可检查失败问题。

weixin_29062823的博客 576

【物联网原理与应用】实验三:RFID低频卡实验——门禁系统

【物联网原理与应用】实验三:RFID低频卡实验——门禁系统

m0_74164458的博客 4001

k8s介绍

例如,如果在同⼀台物理服务器上运⾏多个应⽤程序, 则可能会出。现⼀个应⽤程序占⽤⼤部分资源的情况,⽽导致其他应⽤程序的性。⽽⼀种解决⽅案是将每个应⽤程序都运⾏在不同的物理服务器上,添加或更新应⽤程序,⽽因此可以具有更⾼的可扩缩性,以及降低。容器,衍⽣于虚拟技术,但具有更宽松的隔离特性,使容器可以在。物理服务器中运⾏的应⽤程序资源使⽤,因此会导致资源分配问。和缩容应⽤程序实例,保证应⽤业务⾼峰并发时的⾼可⽤性;杀死健康检查失败的容器,并且在未。,⼀旦宕机或不可⽤,那么所有控制命令都将失效,可对主节点。

m0_68394388的博客 2249

DoubletFinder实战:如何优化参数提升scRNA-seq双细胞识别准确率?

本文深入探讨了如何通过优化DoubletFinder参数提升scRNA-seq双细胞识别准确率。详细解析了pK和nExp参数的影响机制,提供了系统化的参数优化流程和多样本整合策略,并结合真实数据集演示了验证方法与高级应用场景,帮助研究人员获得更可靠的分析结果。

weixin_28523493的博客 340

【云原生训练营】模块四 Kubernetes 架构原则和对象设计

Kubernetes 是谷歌开源的容器集群管理系统,是 Google 多年大规模容器管理技术 Borg 的开源版本,主要功能包括: 基于容器的应用部署、维护和滚动升级; 负载均衡和服务发现; 跨机器和跨地区的集群调度; 自动伸缩; 无状态服务和有状态服务; 插件机制保证扩展性。

果子哥丶的博客 1202

libfranka---joint_impendence_control例程分析

joint_impedance_control.cpp例程分析 部分内容来自链接,我个人以初学者的角度对代码进行了更加细致的解释。 核心部分: // Set and initialize trajectory parameters. const double radius = 0.05; const double vel_max = 0.25; const double acceleration_time = 2.0; const double run_time = 20.0; d

weixin_41567783的博客 1121

汇川PLC用户程序结构详解:如何像搭积木一样配置多任务?附EtherCAT最佳实践

本文深入解析汇川PLC用户程序结构,阐述如何像搭积木一样,通过POU、FB、FC等模块化组件高效配置多任务。文章重点结合EtherCAT通讯场景,分享了任务周期、优先级设置的最佳实践与关键避坑指南,帮助工程师构建稳定、高效的运动控制系统。

weixin_29184075的博客 541

k8s调度 原理_Kubernetes的调度机制

k8s的调度机制scheduler组件k8s调度器会将pod调度到资源满足要求并且评分最高的node上。我们可以使用多种规则比如:1.设置cpu、内存的使用要求;2.增加node的label,并通过pod.Spec.NodeSelector进行强匹配;3.直接设置pod的nodeName,跳过调度直接下发。k8s 1.2加入了一个实验性的功能:affinity。意为亲和性。这个特性的设计初衷是为了...

weixin_42557656的博客 622

FlowPic实战:从流量到图像的端到端加密应用识别

本文深入解析了FlowPic技术如何将网络流量转化为图像,并利用CNN模型进行端到端加密应用识别。该方法通过分析数据包大小和时序信息生成流量图像,有效绕过加密内容,实现对视频、聊天、文件传输等应用的精准分类。文章提供了从流量抓取、图像生成到轻量级CNN模型设计与训练的完整实战指南,并探讨了在VPN、Tor等复杂场景下的应用与挑战。

weixin_29046035的博客 151

Kubernetes—k8s集群调度

Scheduler 是 kubernetes 的调度器,主要的任务是把定义的 pod 分配到集群的节点上。用一个或多个 taint ,这表示对于那些不能容忍这些 taint 的 Pod,是不会被该节点接受的。在了一种相斥的关系,可以让 Node 拒绝 Pod 的调度执行,甚至将 Node 已经存在的 Pod 驱逐出。忍(Tolerations),意思是设置了容忍的 Pod 将可以容忍污点的存在,可以被调度到存在污点的。节点亲和性,是Pod的一种属性(偏好或硬性要求),它使Pod被吸引到一类特定的节点。

aran2002的博客 1654

小程序设计中的区块链应用:数字藏品实现

本文旨在为开发者提供在小程序中实现区块链数字藏品功能的完整技术方案。区块链与数字藏品的基础概念智能合约的设计与实现小程序与区块链的交互方式数字藏品的完整实现流程文章将从基础概念入手,逐步深入到技术实现层面,最后通过完整项目案例展示数字藏品在小程序中的实现方式。每个技术点都会配合代码示例和架构图进行说明。区块链:一种分布式数据库技术,通过加密算法保证数据不可篡改数字藏品:基于区块链技术的数字资产,具有唯一性和可验证性NFT。

小程序开发 1351

手把手实现STM32+ESP8266+原子云服务器+手机APP应用——第三节-编写STM32程序实现STM32+ESP8266+原子云服务器+手机APP互相通信—远程数据传输及控制灯

接上一篇,本节使用原子云服务器。编写STM32程序实现STM32+ESP8266+原子云服务器+手机APP互相通信—远程数据传输及控制灯。使用的硬件包括:STM32F103(任意其他款系列也可以的)、正点原子ATK-ESP8266、LED灯。实现编写STM32程序代码来传输单片机数据到原子云服务器和手机APP进行显示,同时通过手机APP和原子云服务器可以远程控制单片机,例如本节实现远程点/灭灯,以此可以扩展开发更多的功能。本专栏本系列的相关文章可查看下面链接:

嵌入式阿齐Archie 8415

kubernetes资源调度

kubernetes资源调度调度创建一个pod的工作流程pod中影响调度的主要属性资源限制对pod调度的影响nodeSelector&nodeAffinityTaint(污点)&Tolerations(污点容忍)nodeName资源调度设置节点label选择节点(nodeSelector)亲和性(Affinity)与非亲和性(anti-affinity)污点(Taints)与容忍(tolerations) 调度 创建一个pod的工作流程 kubernetes基于list-watch机制的控制

m0_46289146的博客 300

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Kubernetes快速实战与核心原理剖析

Kubernetes快速实战与核心原理剖析

持续学习,坚持原创,分享技术笔记及经验。 1787

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

Kubernetes资源调度

Kubernetes资源调度 1. 深入理解Pod对象:调度 创建一个Pod的工作流程 Pod中影响调度的主要属性 资源限制对Pod调度的英雄 nodeSelector & nodeAffinity Tain(污点)& Tolerations(污点容忍) nodeName Kubernetes Scheduler 是 Kubernetes 控制平面的核心组件之一。它在控制平面上运行,将 Pod 分配给节点,同时平衡节点之间的资源利用率。将 Pod 分配给新节点后,在该节点上运行的 kub

YangLuxxx123 850

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

k8s第二节 Kubernetes入门、安装、创建Deployment、Service、pod调度 污点taint

k8s的集群架构与组件 k8s也是一个Master,多个node节点 。 master组件介绍 组件名称 介绍 kube-apiserver kuber-controller-manager kuber-scheduler etcd node组件介绍

dazer的专栏 2250
上一篇: 并发开篇——带你从0到1建立并发知识体系的基石
下一篇: zookeeper实现分布式锁
JavaMonsterr
博客等级 码龄4年 211粉丝 481原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值