如何阅读kubernetes源代码

70次阅读
没有评论

共计 4867 个字符,预计需要花费 13 分钟才能阅读完成。

这篇文章主要为大家展示了“如何阅读 kubernetes 源代码”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让丸趣 TV 小编带领大家一起研究并学习一下“如何阅读 kubernetes 源代码”这篇文章吧。

为什么要阅读代码?怎么阅读 k8s 源代码?

源代码中包含了所有信息。写开源软件,从文档和其他地方拿到的是二手的信息,代码就是最直接的一手信息。代码就是黑客帝国中 neo 看到的世界本源。

文本并不是代码本身。文本只是在人类可读的模式和编译器可解析之间做了一个折中。代码的本质是具有复杂拓扑的数据结构,就像树或者电路一样。所以读代码的过程是在脑中构建出这个世界,所谓脑补是也。

阅读好的代码是一种享受。我最喜欢阅读的是 redis 的代码,用 C 写的,极端简洁但又威力强大。几句话就把最高效、精妙的数据结构完成出来,就像一篇福尔摩斯的侦探小说。在看的时候我常常想,如果让我实现这个功能,是否能像他这么简单高效?

以阅读 k8s 其中的一个模块,scheduler 为例子,来讲讲我是怎么读代码的

从用户的角度出发,scheduler 模块是干什么的?

scheduler 是 k8s 的调度模块,做的事情就是拿到 pod 之后在 node 中寻找合适的进行适配这么一个单纯的功能。实际上,我已经多次编译和构建这个程序并运行起来。在我的脑中,sheduler 在整个系统中是这样的:

scheduler 作为一个客户端,从 apiserver 中读取到需要分配的 pod,和拥有的 node,然后进行过滤和算分,最后把这个匹配信息通过 apiserver 写入到 etcd 里面,供下一步的 kubelet 去拉起 pod 使用。这样,立刻有几个问题浮现出来

问 1.scheduler 读取到的数据结构是怎么样的?(输入)
问 2.scheduler 写出的的数据结构是怎么样的?(输出)
问 3. 在前面的测试中,scheduler 成为了系统的瓶颈,为什么?
问 4. 社区有人说增加缓存能有效提高 scheduler 的效率,他的思路是可行的吗?

读 scheduler 代码的整个经历层 1:cmd 入口

kubernetes\plugin\cmd\kube-scheduler\scheduler.go

这段代码比较短就全文贴出来了

package main
import (
 runtime 
 k8s.io/kubernetes/pkg/healthz 
 k8s.io/kubernetes/pkg/util 
 k8s.io/kubernetes/pkg/version/verflag 
 k8s.io/kubernetes/plugin/cmd/kube-scheduler/app 
 github.com/spf13/pflag 
func init() {healthz.DefaultHealthz() // 忽略……
func main() {runtime.GOMAXPROCS(runtime.NumCPU()) // 忽略……
 s := app.NewSchedulerServer() // 关注,实际调用的初始化
 s.AddFlags(pflag.CommandLine) // 忽略,命令行解析
 util.InitFlags() 
 util.InitLogs()
 defer util.FlushLogs() // 忽略,开日志等
 verflag.PrintAndExitIfRequested()
 s.Run(pflag.CommandLine.Args()) // 关注,实际跑的口子
}

可以看到,对于细枝末节我一概忽略掉,进入下一层,但是,我并不是不提出问题,提出的问题会写在这里,然后从脑子里面“忘掉”,以减轻前进的负担

kubernetes\plugin\cmd\kube-scheduler\app\server.go

进入这个文件后,重点看的就是数据结构和方法:

SchedulerServer 这个结构存放了一堆配置信息,裸的,可以看到里面几个成员变量都是基本类型,int, string 等

上一层调用的 2 个方法的主要目的是倒腾配置信息,从命令行参数和配置文件 kubeconfig 获取信息后

Run 方法启动一些性能、健康的信息在 http 接口,然后实际调用的是下一层。

kubeconfig 是为了 kubeclient 服务的。

还用了一个工厂模式,按照名称 AlgorithmProvider 来创建具体算法的调度器。

再下一层的入口在:

sched := scheduler.New(config)
sched.Run()

对于这层的问题是:
问 5. 几个限流是怎么实现的?QPS 和 Brust 有什么区别?
问 6. 算法提供者 AlgorithmProvider 是怎么被抽象出来的?需要完成什么事情?

答 5. 在翻了限流的代码后,发现来自于 kubernetes\Godeps\_workspace\src\github.com\juju\ratelimit, 实现的是一个令牌桶的算法,burst 指的是在 n 个请求内保持 qps 平均值的度量。详见这篇文章

层 2: pkg 外层接口

kubernetes\plugin\pkg\scheduler\scheduler.go

答 2:在这里我看到了输出的数据结构为:

b :=  api.Binding{ObjectMeta: api.ObjectMeta{Namespace: pod.Namespace, Name: pod.Name},
 Target: api.ObjectReference{
 Kind:  Node ,
 Name: dest,
 }

这个文件最重要的数据结构是:

type Config struct {
 // It is expected that changes made via modeler will be observed
 // by NodeLister and Algorithm.
 Modeler SystemModeler
 NodeLister algorithm.NodeLister
 Algorithm algorithm.ScheduleAlgorithm
 Binder Binder
 // Rate at which we can create pods
 // If this field is nil, we don t have any rate limit.
 BindPodsRateLimiter util.RateLimiter
 // NextPod should be a function that blocks until the next pod
 // is available. We don t use a channel for this, because scheduling
 // a pod may take some amount of time and we don t want pods to get
 // stale while they sit in a channel.
 NextPod func() *api.Pod
 // Error is called if there is an error. It is passed the pod in
 // question, and the error
 Error func(*api.Pod, error)
 // Recorder is the EventRecorder to use
 Recorder record.EventRecorder
 // Close this to shut down the scheduler.
 StopEverything chan struct{}}

数据结构是什么?数据结构就是舞台上的角色,而函数方法就是这些角色之间演出的一幕幕戏。对象是有生命的,从创建到数据流转,从产生到消亡。而作为开发者来说,首先是搞懂这些人物设定,是关公还是秦琼,是红脸还是黑脸?看懂了人,就看懂了戏。

这段代码里面,结合下面的方法,我可以得出这么几个印象:

Modeler 是个所有 node 节点的模型,但具体怎么做 pod 互斥还不懂

NodeLister 是用来列表节点的

Algorithm 是用来做调度的

Binder 是用来做实际绑定操作的

其他的,Ratelimiter 说了是做限流,其他的都不是很重要,略过

问 7. 结合观看了 modeler.go 之后,发现这是在绑定后处理的,所谓的 assuemPod,就是把绑定的 pod 放到一个队列里面去,不是很理解为什么这个互斥操作是放在 bind 之后做?

问 8.Binder 是怎么去做绑定操作的?

下一层入口:

dest, err := s.config.Algorithm.Schedule(pod, s.config.NodeLister)

层 3: pkg 内层实现

kubernetes\plugin\pkg\scheduler\generic_scheduler.go

在调到这一层的时候,我发现自己走过头了,上面 s.config.Algorithm.Schedule 并不会直接调用 generic_scheduler.go。对于一门面向对象的语言来说,最后的执行可能是一层接口套一层接口,而接口和实现的分离也造成了当你阅读到某个地方之后就无法深入下去。或者说,纯粹的自顶向下的阅读方式并不适合面向对象的代码。所以,目前我的阅读方法开始变成了碎片式阅读,先把整个代码目录树给看一遍,然后去最有可能解释我心中疑问的地方去寻找答案,然后一片片把真相拼合起来。

问 9.generic_scheduler.go 是怎么和 scehduler.go 产生关系的?

这是代码目录树:

从目录树中,可以看出调度算法的目录在 algrorithem 和 algrorithemprovider 里面,而把对象组装在一起的关键源代码是在:

文件 1:factory.go

答 8.Binder 的操作其实很简单,就是把 pod 和 node 的两个字段放到 http 请求中发送到 apiserver 去做绑定,这也和系统的整体架构是一致的

factory 的最大作用,就是从命令行参数中获取到 –algorithm 和 –policy-config-file 来获取到必要算法名称和调度策略,来构建 Config,Config 其实是调度程序的核心数据结构。schduler 这整个程序做的事情可以概括为:获取配置信息——构建 Config——运行 Config。这个过程类似于 java 中的 sping 组装对象,只不过在这里是通过代码显式进行的。从装配工厂中,我们看到了关键的一行

algo := scheduler.NewGenericScheduler(predicateFuncs, priorityConfigs, extenders, f.PodLister, r)

这样就把我上面的问 9 解答了

答 9.scheduler.go 是形式,generic_scheduler.go 是内容,通过 factory 组装

也解答了问 6

答 6.factoryProvider 仅仅是一个算法注册的键值对表达地,大部分的实现还是放在 generic_scheduler 里面的

文件 2:generic_scheduler.go

这就涉及到调度的核心逻辑,就 2 行

filteredNodes, failedPredicateMap, err := findNodesThatFit()....
priorityList, err := PrioritizeNodes()...

先过滤,寻找不引起冲突的合法节点

从合法节点中去打分,寻找分数最高的节点去做绑定

为了避免分数最高的节点被几次调度撞车,从分数高的随机找一个出来

层 4 调度算法的具体实现

这里我就不详细叙述细节了,读者可以按照我的路子去自己寻找答案。

以上是“如何阅读 kubernetes 源代码”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注丸趣 TV 行业资讯频道!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-16发表,共计4867字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)