本文主要内容是了解Kubernetes
调度程序如何发现新Pod
并将其分配给节点。
Kubernetes
已经成为容器和容器化工作负载的标准编排引擎。它提供了跨越公共和私有云环境的通用平台,开放源代码抽象层。
对于那些已经熟悉Kubernetes
及其组件的人,讨论通常围绕最大化Kubernetes
的功能。但是,当您只是学习Kubernetes
时,明智的做法是先从一些有关Kubernetes
及其组件(包括Kubernetes
调度代码)的常识开始,如高级视图所示,然后再尝试在生产中使用它。
也称为主控节点,这些节点负责制定有关群集的全局决策,并检测或响应群集事件。控制平面组件为:
也称为工作程序节点,这些节点集是工作负载所在的位置。他们应该始终与控制平面对话,以获取工作负载运行以及在集群外部进行通信和连接所需的信息。工作节点的组件是:
希望通过这种背景可以帮助您了解Kubernetes
组件是如何协作的。
Kubernetes
调度器与其他主组件(例如APIServer
)一起作为进程运行。它与APIServer
的接口是监视没有被调度的Pod
,对于每个Pod
,它都会发布一个绑定,指示应该在何时调度Pod
。
我们将调度程序从高层分为三层:
cmd/kube-scheduler/scheduler.go
:这是main()
函数,它在调用调度程序框架之前进行初始化。pkg/scheduler/scheduler.go
:这是调度程序框架,用于处理调度算法以外的内容(例如绑定)。pkg/scheduler/core/generic_scheduler.go
:为Pod
分配节点的调度算法。Pod调度过程
调度器尝试为每个Pod
查找一个节点。
首先,它应用一组谓词
来过滤掉不适当的节点。例如,如果PodSpec
指定了资源请求,则调度程序将滤除那些没有至少可用资源的节点(计算为节点的容量减去已经运行的容器的资源请求的总和)在节点上。
其次,它应用了一组优先级函数
,这些函数对未被谓词检查滤除的节点进行排名。例如,它尝试将Pod
分布在节点和区域上,同时偏向于(理论上)负载最少的节点(理论上,负载
是作为在节点上运行的容器的资源请求的总和来衡量的)除以节点的容量。
最后,选择优先级最高的节点(或者,如果有多个这样的节点,则随机选择其中一个)。此主调度循环的代码Schedule()
在pkg/scheduler/core /generic_scheduler.go
中的函数中。
谓词是一组策略,一个一个地应用以筛选出不适当的节点。优先级是一组逐个应用以对节点进行排名的策略(通过谓词过滤器对其进行排序)。默认情况下,Kubernetes
提供内置的谓词和优先级策略,该策略记录在scheduler_algorithm.md
中。谓词和优先级代码分别在pkg/scheduler/algorithm/predicates/predicates.go
和pkg/scheduler/algorithm/priorities
中定义。
调度程序是可扩展的:集群管理员可以选择应用哪些预定义的调度策略,也可以自己添加新的。
可以通过以下两种方式之一来选择在调度时所应用的策略。
使用的默认策略由功能选择,defaultPredicates()
和defaultPriorities()
并在 pkg/scheduler/algorithmprovider/defaults/defaults.go
中进行选择。但是,可以通过将命令行标志--policy-config-file
传递给调度程序(在JSON
文件指定要使用哪些调度策略)来覆盖策略的选择。
有关示例配置文件(https://github.com/kubernetes/examples/blob/master/staging/scheduler-policy/scheduler-policy-config.json
),请参见examples/cheduler-policy-config.json
。(请注意,配置文件格式已版本化;API
在pkg/scheduler/ api
中定义)。
因此,如果要添加新的调度策略,您应该修改pkg/scheduler/algorithm/predicates/predicates.go
或将其添加到目录中pkg/scheduler/algorithm/priorities
,然后在defaultPredicates()
或中注册策略defaultPriorities()
,或使用策略配置文件。
Kubernetes
容器是由一个或多个具有共享存储和网络资源的容器组成。Kubernetes
调度程序的任务是确保将每个Pod
分配到一个并且在其上运行的节点。
如下所示正是Kubernetes
调度程序的工作方式:
1、需要调度的每个Pod
都添加到队列中 2、创建新Pod
后,它们也会添加到队列中 3、调度器连续将Pod
从该队列中移出并调度它们 该调度程序的代码(scheduler.go
)是大约9,000
行,且相当复杂,但需要解决重要问题有三个,下面会从代码层面说明。
监视pod
创建的代码是从第8970
行开始(scheduler.go
)。它无限期地等待新Pod
创建:
// 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)
负责对Pod
进行队列存储的代码从的第7360
行开始(scheduler.go
)。
// queue for pods that need scheduling
podQueue *cache.FIFO
触发事件处理程序用于指示有一个新Pod
可用时,此代码段自动将新Pod
放入队列:
func (f *ConfigFactory) getNextPod() *v1.Pod {
for {
pod := cache.Pop(f.podQueue).(*v1.Pod)
if f.ResponsibleForPod(pod) {
glog.V(4).Infof("About to try and schedule pod %v", pod.Name)
return pod
}
}
}
在pod
调度中,不可避免地会遇到调度错误。以下代码是调度程序处理错误的方式。它侦听podInformer
然后抛出一个错误,提示该Pod
尚未调度并终止:
// scheduled pod cache
podInformer.Informer().AddEventHandler(
cache.FilteringResourceEventHandler{
FilterFunc: func(obj interface{}) bool {
switch t := obj.(type) {
case *v1.Pod:
return assignedNonTerminatedPod(t)
default:
runtime.HandleError(fmt.Errorf("unable to handle object in %T: %T", c, obj))
return false
}
},
换句话说,Kubernetes
调度器主要负责:
Pod
安排在具有足够空间的节点上,以满足Pod
的资源需求kube-apiserver
和控制器是否存在新创建的Pod
,然后将它们调度到集群上的可用节点pod
,并使用/binding pod
子资源API
将其绑定到节点。例如,假设正在部署一个需要1GB
内存和两个CPU
内核的应用程序。因此,在具有足够可用资源的节点上创建该应用程序的容器。然后,调度器将继续永远运行,然后观察是否有需要调度的Pod
。
要使Kubernetes
集群正常工作,您需要使以上所有组件同步工作。调度器是非常复杂的模块,但是Kubernetes
是很重要的基础设施,目前,它是采用云原生部署应用程序时的默认选择。
学习Kubernetes
需要时间和精力,但是将其作为您的一项技能将为您带来应为您的职业带来回报的优势。有很多好的学习资源可供使用,而且文档也不错。如果您有兴趣了解更多信息,建议从以下内容开始:
https://github.com/kelseyhightower/kubernetes-the-hard-way
https://github.com/Praqma/LearnKubernetes/blob/master/kamran/Kubernetes-The-Hard-Way-on-BareMetal.md
https://github.com/Praqma/LearnKubernetes/blob/master/kamran/Kubernetes-The-Hard-Way-on-AWS.md
您最喜欢学习Kubernetes
的哪些方式?请在评论区给出分享。