kube-scheduler调度任务的执行过程分析与源码解读(二)

news2024/9/25 19:22:10

概述

摘要: 上文我们对Kube-scheduler的启动流程进行了分析,本文继续探究kube-scheduler执行pod的调度任务的过程。

正文

说明:基于 kubernetes v1.12.0 源码分析

上文讲到kube-scheduler组件通过sched.Run() 启动调度器实例。在sched.Run() 中循环的执行sched.scheduleOne获取一个pod,并执行Pod的调度任务。

源码位置k8s.io/kubernetes/cmd/kube-scheduler/scheduler.go

// Run begins watching and scheduling. It waits for cache to be synced, then starts a goroutine and returns immediately.
func (sched *Scheduler) Run() {
	if !sched.config.WaitForCacheSync() {
		return
	}
	// 启动协程循环的执行调度任务
	go wait.Until(sched.scheduleOne, 0, sched.config.StopEverything)
}

(sched *Scheduler) scheduleOne

继续探究scheduleOne函数,scheduleOne函数非常关键。函数的第一步就是 getNextPod() 从调度队列podQueue中取出一个待调度 pod

// scheduleOne does the entire scheduling workflow for a single pod.  It is serialized on the scheduling algorithm's host fitting.
func (sched *Scheduler) scheduleOne() {
  // 从调度队列podQueue中取出一个待调度 pod
	pod := sched.config.NextPod()
	if pod.DeletionTimestamp != nil {
		sched.config.Recorder.Eventf(pod, v1.EventTypeWarning, "FailedScheduling", "skip schedule deleting pod: %v/%v", pod.Namespace, pod.Name)
		glog.V(3).Infof("Skip schedule deleting pod: %v/%v", pod.Namespace, pod.Name)
		return
	}

	glog.V(3).Infof("Attempting to schedule pod: %v/%v", pod.Namespace, pod.Name)

	// Synchronously attempt to find a fit for the pod.
	start := time.Now()
	suggestedHost, err := sched.schedule(pod)
	if err != nil {
		// schedule() may have failed because the pod would not fit on any host, so we try to
		// preempt, with the expectation that the next time the pod is tried for scheduling it
		// will fit due to the preemption. It is also possible that a different pod will schedule
		// into the resources that were preempted, but this is harmless.
		if fitError, ok := err.(*core.FitError); ok {
			preemptionStartTime := time.Now()
			sched.preempt(pod, fitError)
			metrics.PreemptionAttempts.Inc()
			metrics.SchedulingAlgorithmPremptionEvaluationDuration.Observe(metrics.SinceInMicroseconds(preemptionStartTime))
			metrics.SchedulingLatency.WithLabelValues(metrics.PreemptionEvaluation).Observe(metrics.SinceInSeconds(preemptionStartTime))
		}
		return
	}
	metrics.SchedulingAlgorithmLatency.Observe(metrics.SinceInMicroseconds(start))
	// Tell the cache to assume that a pod now is running on a given node, even though it hasn't been bound yet.
	// This allows us to keep scheduling without waiting on binding to occur.
	assumedPod := pod.DeepCopy()

	// Assume volumes first before assuming the pod.
	//
	// If all volumes are completely bound, then allBound is true and binding will be skipped.
	//
	// Otherwise, binding of volumes is started after the pod is assumed, but before pod binding.
	//
	// This function modifies 'assumedPod' if volume binding is required.
	allBound, err := sched.assumeVolumes(assumedPod, suggestedHost)
	if err != nil {
		return
	}

	// assume modifies `assumedPod` by setting NodeName=suggestedHost
	err = sched.assume(assumedPod, suggestedHost)
	if err != nil {
		return
	}
	// bind the pod to its host asynchronously (we can do this b/c of the assumption step above).
	go func() {
		// Bind volumes first before Pod
		if !allBound {
			err = sched.bindVolumes(assumedPod)
			if err != nil {
				return
			}
		}

		err := sched.bind(assumedPod, &v1.Binding{
			ObjectMeta: metav1.ObjectMeta{Namespace: assumedPod.Namespace, Name: assumedPod.Name, UID: assumedPod.UID},
			Target: v1.ObjectReference{
				Kind: "Node",
				Name: suggestedHost,
			},
		})
		metrics.E2eSchedulingLatency.Observe(metrics.SinceInMicroseconds(start))
		if err != nil {
			glog.Errorf("Internal error binding pod: (%v)", err)
		}
	}()
}

scheduleOne函数较长我们提炼出关键步骤:

  • sched.config.NextPod() 从调度队列podQueue中取出一个待调度 pod
  • sched.schedule(pod) 执行对pod的调度任务,调度完成会返回一个合适的host(suggestedHost)
  • sched.bind 执行 bind ,即将 pod 绑定到 node,并发送post请求给apiserver
// scheduleOne does the entire scheduling workflow for a single pod.  It is serialized on the scheduling algorithm's host fitting.
func (sched *Scheduler) scheduleOne() {
  // 从调度队列podQueue中取出一个待调度 pod
	pod := sched.config.NextPod()
	
  // 执行pod的调度任务,调度完成会返回一个合适的host
	suggestedHost, err := sched.schedule(pod)

	// bind the pod to its host asynchronously (we can do this b/c of the assumption step above).
	go func() {
		// 执行 bind ,将 pod 绑定到 node
		err := sched.bind(assumedPod, &v1.Binding{
	}()
}


接下来,我们分别对获取待调度pod执行pod调度任务pod的绑定详细探究

源码如何实现调度任务

(sched *Scheduler) schedule

(sched *Scheduler) schedule 的作用是给定一个待调度pod,利用调度算法,返回一个合适的Host。函数内部直接执行调度任务的是sched.config.Algorithm.Schedule.

// schedule implements the scheduling algorithm and returns the suggested host.
func (sched *Scheduler) schedule(pod *v1.Pod) (string, error) {
  // 给定一个pod 和 一个NodeLister,返回一个合适的host
	host, err := sched.config.Algorithm.Schedule(pod, sched.config.NodeLister)
	if err != nil {
		pod = pod.DeepCopy()
		sched.config.Error(pod, err)
		sched.config.Recorder.Eventf(pod, v1.EventTypeWarning, "FailedScheduling", "%v", err)
		sched.config.PodConditionUpdater.Update(pod, &v1.PodCondition{
			Type:          v1.PodScheduled,
			Status:        v1.ConditionFalse,
			LastProbeTime: metav1.Now(),
			Reason:        v1.PodReasonUnschedulable,
			Message:       err.Error(),
		})
		return "", err
	}
	return host, err
}

(g *genericScheduler) Schedule

sched.config.Algorithm.Schedule 是一个接口,具体实现是genericScheduler结构的Schedule()方法。

在这里插入图片描述

找到genericScheduler结构体的Schedule()方法的源码

// Schedule tries to schedule the given pod to one of the nodes in the node list.
// If it succeeds, it will return the name of the node.
// If it fails, it will return a FitError error with reasons.
func (g *genericScheduler) Schedule(pod *v1.Pod, nodeLister algorithm.NodeLister) (string, error) {
	trace := utiltrace.New(fmt.Sprintf("Scheduling %s/%s", pod.Namespace, pod.Name))
	defer trace.LogIfLong(100 * time.Millisecond)

  // 执行一些检查工作
	if err := podPassesBasicChecks(pod, g.pvcLister); err != nil {
		return "", err
	}
	// 获取 所有node信息
	nodes, err := nodeLister.List()
	if err != nil {
		return "", err
	}
	if len(nodes) == 0 {
		return "", ErrNoNodesAvailable
	}

	// Used for all fit and priority funcs.
  // 更新缓存中Node信息
	err = g.cache.UpdateNodeNameToInfoMap(g.cachedNodeInfoMap)
	if err != nil {
		return "", err
	}

	trace.Step("Computing predicates")
  // findNodesThatFit 执行预选算法,过滤出符合的node节点
	startPredicateEvalTime := time.Now()
	filteredNodes, failedPredicateMap, err := g.findNodesThatFit(pod, nodes)
	if err != nil {
		return "", err
	}

	if len(filteredNodes) == 0 {
		return "", &FitError{
			Pod:              pod,
			NumAllNodes:      len(nodes),
			FailedPredicates: failedPredicateMap,
		}
	}
  // 记录数据用于promethues监控采集
	metrics.SchedulingAlgorithmPredicateEvaluationDuration.Observe(metrics.SinceInMicroseconds(startPredicateEvalTime))
	metrics.SchedulingLatency.WithLabelValues(metrics.PredicateEvaluation).Observe(metrics.SinceInSeconds(startPredicateEvalTime))

	trace.Step("Prioritizing")
  // 接下来将执行优选算法(打分),选出最优node
	startPriorityEvalTime := time.Now()
	// When only one node after predicate, just use it.
  // 如果最优的节点只有一个,就直接返回该节点
	if len(filteredNodes) == 1 {
		metrics.SchedulingAlgorithmPriorityEvaluationDuration.Observe(metrics.SinceInMicroseconds(startPriorityEvalTime))
		return filteredNodes[0].Name, nil
	}

	metaPrioritiesInterface := g.priorityMetaProducer(pod, g.cachedNodeInfoMap)
  // PrioritizeNodes 执行优选算法(打分),选出最优的node
	priorityList, err := PrioritizeNodes(pod, g.cachedNodeInfoMap, metaPrioritiesInterface, g.prioritizers, filteredNodes, g.extenders)
	if err != nil {
		return "", err
	}
	metrics.SchedulingAlgorithmPriorityEvaluationDuration.Observe(metrics.SinceInMicroseconds(startPriorityEvalTime))
	metrics.SchedulingLatency.WithLabelValues(metrics.PriorityEvaluation).Observe(metrics.SinceInSeconds(startPriorityEvalTime))

	trace.Step("Selecting host")
  // selectHost 从最优的node列表中选出,最优的一个node节点
	return g.selectHost(priorityList)
}

代码较长,老规矩我们提炼出精华。(g *genericScheduler) Schedule的工作主要有3点:

  • g.findNodesThatFit(pod, nodes) 从nodes列表中过滤出适合pod调度的node列表,即所谓的“预选”。
  • PrioritizeNodes 对node列表中的node执行"打分",选出分数最高的node列表(因为可能出现一些node的分数最高且相同,所以返回的是多个node的列表),即所谓的"“优选”。
  • .selectHost(priorityList) 从最优列表中选出一个最最最合适的node节点。
// Schedule tries to schedule the given pod to one of the nodes in the node list.
// If it succeeds, it will return the name of the node.
// If it fails, it will return a FitError error with reasons.
func (g *genericScheduler) Schedule(pod *v1.Pod, nodeLister algorithm.NodeLister) (string, error) {
	
	// 1. 获取所有node信息
	nodes, err := nodeLister.List()

  // 2. findNodesThatFit 执行预选算法,过滤出符合的node节点
	filteredNodes, failedPredicateMap, err := g.findNodesThatFit(pod, nodes)

	// When only one node after predicate, just use it.
  // 2.1 如果最优的节点只有一个,就直接返回该节点
	if len(filteredNodes) == 1 { return filteredNodes[0].Name, nil}

  // 3. PrioritizeNodes 执行优选算法(打分),选出最优的node
	priorityList, err := PrioritizeNodes(pod, g.cachedNodeInfoMap, metaPrioritiesInterface, g.prioritizers, filteredNodes, g.extenders)

  // 4. selectHost 从最优的node列表中选出,最优的一个node节点
	return g.selectHost(priorityList)
}

g.findNodesThatFitPrioritizeNodes 内容较多后面写一个文章单独分析。

(g *genericScheduler) Schedule的执行流程如图所示

img

(图片来自网络,如有侵权,请联系作者删除)

(g *genericScheduler) selectHost

柿子挑软的捏。selectHost逻辑最简单,所以先分析它。 selectHost的任务是从优选列表中选出``一个最优node,注意如果有多个node最大且分数一样时,会通过索引累计方式,避免多次调度到同一个宿主`。


// selectHost takes a prioritized list of nodes and then picks one
// in a round-robin manner from the nodes that had the highest score.
func (g *genericScheduler) selectHost(priorityList schedulerapi.HostPriorityList) (string, error) {
	if len(priorityList) == 0 {
		return "", fmt.Errorf("empty priorityList")
	}
  // findMaxScores 从 priorityList 计算出最大分数的一个或多个 node 的索引
	maxScores := findMaxScores(priorityList)
  // 重要!!!通过索引累计方式,避免多次调度到同一个宿主
	ix := int(g.lastNodeIndex % uint64(len(maxScores)))
	g.lastNodeIndex++

	return priorityList[maxScores[ix]].Host, nil
}

findMaxScores

findMaxScores 对 priorityList 遍历,找出分数最大node的索引并放入一个列表,如果最大分数有相同的,就都会放入列表。


// findMaxScores returns the indexes of nodes in the "priorityList" that has the highest "Score".
func findMaxScores(priorityList schedulerapi.HostPriorityList) []int {
	maxScoreIndexes := make([]int, 0, len(priorityList)/2)
	maxScore := priorityList[0].Score
	for i, hp := range priorityList {
		if hp.Score > maxScore {
			maxScore = hp.Score
      // 如果 node 的 score大于 maxScore就把所有放入列表 maxScoreIndexes
			maxScoreIndexes = maxScoreIndexes[:0]
			maxScoreIndexes = append(maxScoreIndexes, i)
		} else if hp.Score == maxScore {
			maxScoreIndexes = append(maxScoreIndexes, i)
		}
	}
	return maxScoreIndexes
}

源码如何实现从调度队列中获取待调度pod

schduleOne函数执行的第一步sched.config.NextPod(),作用是从调度队列podQueue中取出一个待调度pod。那么问题来了,podQueue是如何初始化的呢?是如何将数据放到队列里面去的呢?

我们先将视线回到server.go中的run()函数,会执行NewSchedulerConfig(c) 获取调度配置。

Run

// Run runs the Scheduler.
func Run(c schedulerserverconfig.CompletedConfig, stopCh <-chan struct{}) error {
	// 代码省略

	// Build a scheduler config from the provided algorithm source.
	schedulerConfig, err := NewSchedulerConfig(c)
	if err != nil {
		return err
	}
	// 代码省略
}

NewSchedulerConfig

NewSchedulerConfig 调用了 CreateFromProvider 函数。进一步查找 CreateFromProvider 函数

// NewSchedulerConfig creates the scheduler configuration. This is exposed for use by tests.
func NewSchedulerConfig(s schedulerserverconfig.CompletedConfig) (*scheduler.Config, error) {
	// 代码省略

	source := s.ComponentConfig.AlgorithmSource
	var config *scheduler.Config
	switch {
	case source.Provider != nil:
		// Create the config from a named algorithm provider.
		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
	// 代码省略
}

(c *configFactory) CreateFromProvider

CreateFromProvider 调用了 CreateFromKeys 函数

// Creates a scheduler from the name of a registered algorithm provider.
func (c *configFactory) CreateFromProvider(providerName string) (*scheduler.Config, error) {
	glog.V(2).Infof("Creating scheduler from algorithm provider '%v'", providerName)
	provider, err := GetAlgorithmProvider(providerName)
	if err != nil {
		return nil, err
	}
	// 注意
	return c.CreateFromKeys(provider.FitPredicateKeys, provider.PriorityFunctionKeys, []algorithm.SchedulerExtender{})
}

(c *configFactory) CreateFromKeys

源码位置kubernetes/pkg/scheduler/factory/factory.go

CreateFromKeys函数中终于找到NextPod的定义,我们继续查找c.getNextPod()

// Creates a scheduler from a set of registered fit predicate keys and priority keys.
func (c *configFactory) CreateFromKeys(predicateKeys, priorityKeys sets.String, extenders []algorithm.SchedulerExtender) (*scheduler.Config, error) {
	glog.V(2).Infof("Creating scheduler with fit predicates '%v' and priority functions '%v'", predicateKeys, priorityKeys)

	if c.GetHardPodAffinitySymmetricWeight() < 1 || c.GetHardPodAffinitySymmetricWeight() > 100 {
		return nil, fmt.Errorf("invalid hardPodAffinitySymmetricWeight: %d, must be in the range 1-100", c.GetHardPodAffinitySymmetricWeight())
	}

	predicateFuncs, err := c.GetPredicates(predicateKeys)
	if err != nil {
		return nil, err
	}

	priorityConfigs, err := c.GetPriorityFunctionConfigs(priorityKeys)
	if err != nil {
		return nil, err
	}

	priorityMetaProducer, err := c.GetPriorityMetadataProducer()
	if err != nil {
		return nil, err
	}

	predicateMetaProducer, err := c.GetPredicateMetadataProducer()
	if err != nil {
		return nil, err
	}

	// Init equivalence class cache
	if c.enableEquivalenceClassCache {
		c.equivalencePodCache = equivalence.NewCache()
		glog.Info("Created equivalence class cache")
	}
	// 重要!!! 创建通用调度器
	algo := core.NewGenericScheduler(
		c.schedulerCache,
		c.equivalencePodCache,
		c.podQueue,
		predicateFuncs,
		predicateMetaProducer,
		priorityConfigs,
		priorityMetaProducer,
		extenders,
		c.volumeBinder,
		c.pVCLister,
		c.alwaysCheckAllPredicates,
		c.disablePreemption,
		c.percentageOfNodesToScore,
	)

	podBackoff := util.CreateDefaultPodBackoff()
	return &scheduler.Config{
		SchedulerCache: c.schedulerCache,
		Ecache:         c.equivalencePodCache,
		// The scheduler only needs to consider schedulable nodes.
		NodeLister:          &nodeLister{c.nodeLister},
		Algorithm:           algo,
		GetBinder:           c.getBinderFunc(extenders),
		PodConditionUpdater: &podConditionUpdater{c.client},
		PodPreemptor:        &podPreemptor{c.client},
		WaitForCacheSync: func() bool {
			return cache.WaitForCacheSync(c.StopEverything, c.scheduledPodsHasSynced)
		},
    // 重要!!!从调度队列中取出一个 待调度 pod
		NextPod: func() *v1.Pod {
			return c.getNextPod()
		},
		Error:           c.MakeDefaultErrorFunc(podBackoff, c.podQueue),
		StopEverything:  c.StopEverything,
		VolumeBinder:    c.volumeBinder,
    // 定义调度队列
		SchedulingQueue: c.podQueue,
	}, nil
}

getNextPod() 的逻辑很简单,就是从调度队列podQueue中Pop()出一个待调度的pod。podQueue是一个优先级队列,如果待调度pod在spec中配置了优先级,pop()会弹出优先级高的pod执行调度任务。

func (c *configFactory) getNextPod() *v1.Pod {
  // 从调度队列从弹出一个待调度的pod
	pod, err := c.podQueue.Pop()
	if err == nil {
		glog.V(4).Infof("About to try and schedule pod %v/%v", pod.Namespace, pod.Name)
		return pod
	}
	glog.Errorf("Error while retrieving next pod from scheduling queue: %v", err)
	return nil
}

到此,我们明白了,shed.run()启动s时会启动informer,informer中定义了pod事件handler回调函数,handler会将待调度的pod放入调度队列podQueue。 而消费调度队列里面的pod,则是sheduleOne方法通过c.podQueue.Pop()取出一个待调度pod。

源码如何实现bind操作

scheduleOne选出一个 node 之后,调度器会创建一个v1.Binding 对象, Pod 的 ObjectReference 字段的值就是选中的 suggestedHost 的名字

下面代码片段是scheduleOne中关于bind操作的内容

// 构建一个 v1.Binding 对象
err := sched.bind(assumedPod, &v1.Binding{
			ObjectMeta: metav1.ObjectMeta{Namespace: assumedPod.Namespace, Name: assumedPod.Name, UID: assumedPod.UID},
			Target: v1.ObjectReference{
				Kind: "Node",
				Name: suggestedHost,
			},
		})

v1.Binding资源类型的介绍

v1.Binding 是 Kubernetes API 的一个对象类型,用于将一个或多个 Pod 绑定到一个特定的节点上。为了将 Pod 绑定到特定的节点上,可以使用 v1.Binding 对象。该对象需要指定 Pod 的名称和命名空间,以及要绑定到的节点名称。

(b *binder) Bind 代码很多,但核心的就一行:

​ err := sched.config.GetBinder(assumed).Bind(b)

调用Binder接口的Bind方法。进一可以看到,Bind方法 是利用clientset向apiserver发起一个Bind的POST请求

// bind binds a pod to a given node defined in a binding object.  We expect this to run asynchronously, so we
// handle binding metrics internally.
func (sched *Scheduler) bind(assumed *v1.Pod, b *v1.Binding) error {
	bindingStart := time.Now()
	// If binding succeeded then PodScheduled condition will be updated in apiserver so that
	// it's atomic with setting host.
  // 调用Binder接口的Bind方法
	err := sched.config.GetBinder(assumed).Bind(b)
  // 检查cache中对应pod的状态,确认是否bind成功
	if err := sched.config.SchedulerCache.FinishBinding(assumed); err != nil {
		glog.Errorf("scheduler cache FinishBinding failed: %v", err)
	}
  // 如果没有bind绑定成功,则记录错误原因
	if err != nil {
		glog.V(1).Infof("Failed to bind pod: %v/%v", assumed.Namespace, assumed.Name)
		if err := sched.config.SchedulerCache.ForgetPod(assumed); err != nil {
			glog.Errorf("scheduler cache ForgetPod failed: %v", err)
		}
		sched.config.Error(assumed, err)
		sched.config.Recorder.Eventf(assumed, v1.EventTypeWarning, "FailedScheduling", "Binding rejected: %v", err)
		sched.config.PodConditionUpdater.Update(assumed, &v1.PodCondition{
			Type:          v1.PodScheduled,
			Status:        v1.ConditionFalse,
			LastProbeTime: metav1.Now(),
			Reason:        "BindingRejected",
		})
		return err
	}
	// 记录metrics监控数据
	metrics.BindingLatency.Observe(metrics.SinceInMicroseconds(bindingStart))
	metrics.SchedulingLatency.WithLabelValues(metrics.Binding).Observe(metrics.SinceInSeconds(bindingStart))
	sched.config.Recorder.Eventf(assumed, v1.EventTypeNormal, "Scheduled", "Successfully assigned %v/%v to %v", assumed.Namespace, assumed.Name, b.Target.Name)
	return nil
}

Bind 是调用了clientset向apiserver发起一个Bind的POST请求

// Bind just does a POST binding RPC.
func (b *binder) Bind(binding *v1.Binding) error {
	glog.V(3).Infof("Attempting to bind %v to %v", binding.Name, binding.Target.Name)
  // 通过clientset向apiserver发起一个Bind的POST请求
	return b.Client.CoreV1().Pods(binding.Namespace).Bind(binding)
}

补充内容: apiserver收到bind请求后

当apiserver 收到这个 Binding object 请求后,将会更新 Pod 对象的下列字段:

设置 pod.Spec.NodeName
添加 annotations
设置 PodScheduled status 为 True

// k8s.io/kubernetes/pkg/registry/core/pod/storage/storage.go

// assignPod assigns the given pod to the given machine.
// 处理 api 收到到binding RESTful 请求
func (r *BindingREST) assignPod(ctx context.Context, podID string, machine string, annotations map[string]string, dryRun bool) (err error) {
  // 设置pod的 host 和 Annotation 信息
	if _, err = r.setPodHostAndAnnotations(ctx, podID, "", machine, annotations, dryRun); err != nil {
		err = storeerr.InterpretGetError(err, api.Resource("pods"), podID)
		err = storeerr.InterpretUpdateError(err, api.Resource("pods"), podID)
		if _, ok := err.(*errors.StatusError); !ok {
			err = errors.NewConflict(api.Resource("pods/binding"), podID, err)
		}
	}
	return
}

// k8s.io/kubernetes/pkg/registry/core/pod/storage/storage.go

func (r *BindingREST) setPodHostAndAnnotations(ctx context.Context, podID, oldMachine, machine string,
    annotations map[string]string, dryRun bool) (finalPod *api.Pod, err error) {

    podKey := r.store.KeyFunc(ctx, podID)
    r.store.Storage.GuaranteedUpdate(ctx, podKey, &api.Pod{}, false, nil,
        storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) {

        pod, ok := obj.(*api.Pod)
        // 设置 pod.Spec.NodeName 
        pod.Spec.NodeName = machine
        if pod.Annotations == nil {
            pod.Annotations = make(map[string]string)
        }
        // 更新 pod 的 annotations
        for k, v := range annotations {
            pod.Annotations[k] = v
        }
        // 设置pod 的 pod.status.conditions中的status值为truez
        podutil.UpdatePodCondition(&pod.Status, &api.PodCondition{
            Type:   api.PodScheduled,
            Status: api.ConditionTrue,
        })

        return pod, nil
    }), dryRun, nil)
}

更新pod.spec.Nodename后,将数据写入到etcd,之后 被指定调度的node节点上的 kubelet监听到这个事件,kubelet接下来会在该机器上创建pod.

总结

经过分析scheduleOne函数会循环的从调度队列sheduling_Queue`,取出pop()待调度的pod, 之后利用`sched.schedule()执行调度任务,选出最佳的sugestHost。 最后sched.bind()通过clientset向apiserver发起绑定bind的POST请求。至此,完成了一个完成的pod调度过程,流程如图所示。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2089726.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

贵州大数据实验室建设案例分享

贵州大数据实验室建设与学科建设紧密结合&#xff0c;旨在为高校提供教学资源和实训环境&#xff0c;以支持大数据技术人才的培养。高校在实验室规划过程中&#xff0c;第一要务就是从学科定位出发、结合学校的特色和行业优势&#xff0c;定义人才培养目标和方向&#xff0c;并…

栈——有效的括号

在这道题中给出一个字符串包含三种不同的括号&#xff0c;需要判断这三个括号是否能相互匹配。因为方向需要保证不出错&#xff0c;所以我们可以想到如果指向字符的指针为左括号时&#xff0c;可以将它拿出&#xff0c;与下一个字符进行匹配若能匹配则继续匹配&#xff0c;如果…

一文搞懂Window、PhoneWindow、DercorView、WindowManage

戳蓝字“牛晓伟”关注我哦&#xff01; 用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章&#xff0c;技术文章也可以有温度。 本文摘要 通过本文您可以了解PhoneWindow&#xff0c;Window&#xff0c;DecorView&#xff0c;WindowManager&#xff0c;WindowManag…

虚拟机【linux】配置无线网络

虚拟机【linux】配置无线网络 在Linux系统中配置网卡是一个常见的任务&#xff0c;特别是当你需要手动设置IP地址、子网掩码、网关或DNS服务器等信息时。不同的Linux发行版可能有不同的配置方法&#xff0c;但以下是一些基本且通用的步骤来配置网卡。 1. 确定网卡名称 首先&…

【云原生】Kubernetes中如何通过Pod名称查询Docker容器ID,通过Docker容器ID查询Pod名称?

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…

中秋节不容错过的送礼指南,除了月饼,还有五大科技好物助力团圆

中秋节&#xff0c;作为中华民族的传统节日&#xff0c;承载着深厚的文化内涵和家庭团聚的美好愿望。在这个月圆人圆的时刻&#xff0c;送礼成为了表达情感和祝福的重要方式。虽然月饼作为传统的象征&#xff0c;总是节日礼单上的首选&#xff0c;但随着科技的发展&#xff0c;…

SpringCloud乐尚代驾学习笔记:司机端登录(四)

SpringCloud乐尚代驾学习笔记&#xff1a;司机端登录&#xff08;四&#xff09; 文章目录 SpringCloud乐尚代驾学习笔记&#xff1a;司机端登录&#xff08;四&#xff09;1、司机端微信小程序登录1.1、准备工作1.2、接口开发1.2.1、service-driver模块1.2.2、openFeign远程调…

bitmap(位图)的使用

零存零取&#xff0c;整存零取&#xff0c;整存整取, 零存整取 bitmap介绍 位图不是真正的数据类型&#xff0c;它是定义在字符串类型中,一个字符串类型的值最多能存储512M字节的内容, 位上限&#xff1a;2^(9(512)10(1024)10(1024)3(8b1B))2^32b 语句操作&#xff1a; s…

数据结构与算法 第四天(串、数组、广义表)

串&#xff08;String&#xff09; 任意字符组成的有限序列 串的类型定义 串的顺序存储结构 模式匹配算法 确定主串所含字串第一次出现的位置。 BF算法 穷举法&#xff0c;从每个字符开始依次匹配 KMP算法 链式存储 数组 基本操作 特殊矩阵存储 对称矩阵 三角矩阵 对角矩阵…

UDP英译汉网络词典

这里我们用UDP实现一个简单的英译汉小词典。我们还是仿照前一篇的UDP编程&#xff0c;将各自的组件封装起来&#xff0c;实现高内聚低耦合。 一. 字典翻译功能实现 首先我们将我们的字典知识库放在txt文本中。 apple: 苹果 banana: 香蕉 cat: 猫 dog: 狗 book: 书 pen: 笔 ha…

容器访问外网

目录 1 容器访问外网 1.1 容器访问外网的原理 1.2 容器与外网通讯原理解剖 2 docker跨主机网络 2.1 实现docker跨主机通讯方式 2.2 macvlan网络方式实现跨主机通信 2.2.1 macvlan网络方式 2.2.2 macvlan网络间的隔离和连通 2.2.3 实现方法如下&#xff1a; 1 容器访问外网 1.…

Django+Vue社区养老管理系统的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 需要的环境3.2 Django接口层3.3 实体类3.4 config.ini3.5 启动类3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质创作者&…

详解Spring AOP

前言&#x1f440;~ 上一章我们介绍了SpringBoot统一功能相关的知识点&#xff0c;今天来讲解Spring框架另外一个核心AOP&#xff0c;细&#xff01;&#xff01;&#xff01; 什么是AOP&#xff1f; 什么是面向切面编程呢&#xff1f; 什么是面向特定方法编程呢&#xff1…

Bahdanau注意力机制

介绍 在Bahadanu注意力机制中&#xff0c;本质上是序列到序列学习的注意力机制实现&#xff0c;在编码器-解码器结构中&#xff0c;解码器的每一步解码过程都依赖着整个上下文变量&#xff0c;通过Bahdanau注意力&#xff0c;使得解码器在每一步解码时&#xff0c;对于整个上下…

ET6框架(七)Excel配置工具

文章目录 一、Excel表的基本规则&#xff1a;二、特殊特殊标记三、编译路径说明四、动态获取数据五、可导表类型查看: 一、Excel表的基本规则&#xff1a; 在框架中我们的Excel配置表在ET > Excel文件夹中 1.在表结构中需要注意的是起始点必须在第三行第三列&#xff0c;且…

91.游戏的启动与多开-游戏启动

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;易道云信息技术研究院 上一个内容&#xff1a;90.游戏安全项目-项目搭建与解析 以90.游戏安全项目-项目搭建与解析它的代码为基础进行…

[java][代码] java中date格式化输出时间字符串

Date date new Date();//具备默认的风格//DateFormat dateFormDateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG);DateFormat dateFormnew SimpleDateFormat("yyyy-mm-dd");System.out.println(dateForm.format(date)); 这段Java代码演示了如何使用S…

YOLOv9改进策略【模型轻量化】| PP-LCnet

一、本文介绍 本文记录的是利用PP-LCNet中的DepSepConv模块优化YOLOv9中的RepNCSPELAN4。YOLOv9在使用辅助分支后&#xff0c;模型的参数量和计算量相对较大&#xff0c;本文利用DepSepConv模块改善模型结构&#xff0c;使模型在几乎不增加延迟的情况下提升网络准确度。 文章目…

海外新闻稿发布对区块链项目具有深远的影响

在知名媒体平台上发布新闻稿对区块链项目具有深远的影响&#xff0c;例如全球雅虎&#xff0c;彭博社这些全球知名财经媒体&#xff0c;能够显著提升项目的曝光度和信誉&#xff0c;吸引潜在投资者以及其他利益相关者的关注。以下是几个方面的详细分析&#xff1a; 1. 增强品牌…

区块链通证系统功能分析

区块链通证系统功能分析涉及多个关键方面&#xff0c;以确保系统能够满足不同的业务需求和合规性要求。 同质与非同质通证&#xff1a;区块链通证系统需要支持同质通证&#xff08;如ERC-20&#xff09;和非同质通证&#xff08;如ERC-721&#xff09;&#xff0c;以适应不同类…