为什么要阅读代码?怎么阅读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 调度算法的具体实现这里我就不详细叙述细节了,读者可以按照我的路子去自己寻找答案。

总结

现代的面向对象的代码结构,接口和实现分离,逻辑高度的离散在各个源代码中

人类的大脑适合阅读线性的单线程的故事

先自顶向下读,形成一颗代码的调用树,直到读不下去。分析法

再自底向上读,但不是泛读,而是在掌握这颗树的基础上在某个领域泛读,把事实拼接起来成为真相。归纳法

在单个源码文件中,调用过程依然还是一棵树,可以用树的观念去解析

对象拥有属性和方法,就像游戏人物拥有属性和技能一样。很多时候不需要深究这些属性和技能的细节。

回到戏剧的比喻,现代的代码和运行结构是构建对象——运行对象,就像戏剧中的角色化妆定型——上台演戏。戏台上有大大小小的主角配角,代码里也有主要对象次要对象,但剧本的运作让观众能第一时间锁定主角和主要剧情。看代码,就是看主要剧情和主角。配角的表演可以在后面第二遍第三遍的阅读代码中再去关注细节。

k8s源码分析 pdf_我是怎么阅读kubernetes源代码的?相关推荐

  1. k8s源码分析 pdf_如何高效阅读 Kubernetes 源码?

    IT 技术日新月异,想必每个 IT 人都会有类似的焦虑:我该学习什么?哪些知识学到就是赚到?怎样学习才能最有效提升编程能力? 阅读优秀的代码是提高编程能力万无一失的办法.诚然,提高编程能力的显著方法是 ...

  2. k8s源码分析 pdf_《k8s-1.13版本源码分析》上github

    要干嘛? 猪年新气象,今年开始,kubernetes源码分析系列文章主战场从微信公众号转至github,完全使用Markdown重写,使用gitbook生成web页面,支持在线阅读,导出pdf等各种玩 ...

  3. k8s源码分析--kube-scheduler源码(一)

    版本:v1.13.0 启动分析 kubernetes基础组件的入口均在cmd目录下,kube-schduler入口在scheduler.go下. kubernetes所有的组件启动采用的均是comma ...

  4. k8s源码分析 pdf_rook源码分析之一:rook架构解析

    rook简介 Rook是一款云原生环境下的开源分布式存储编排系统,目前支持 Ceph.NFS.Edegefs.Cassandra.CockroachDB等存储系统.它实现了一个自动管理的.自动扩容的. ...

  5. k8s源码分析 pdf_Spark Kubernetes 的源码分析系列 - features

    1 Overview features 包里的代码,主要是用于构建 Spark 在 K8S 中的各类资源所需要的特征,个人觉得可以理解成这些 features 就是帮你写各类 Kind 的 YAML ...

  6. 100 - k8s源码分析-准备工作

    今天我们开始讲kubernetes的源码! 之前的其他开源项目还没有说完,后续会陆陆续续更新,我们把主线先放到k8s的源码上. 之前我想详细讲解每一行k8s源码,但是越看越发现一个大型开源项目如果拘泥 ...

  7. 鸿蒙内核源码分析系列 | 读懂HarmonyOS内核源代码!

    本系列从HarmonyOS架构层视角整理成文, 并用生活场景及讲故事的方式试图去解构内核,一窥究竟.帮助你读懂并快速理解鸿蒙操作系统源码. 1.鸿蒙内核源码分析(调度机制篇) 2.鸿蒙内核源码分析(进 ...

  8. 【kubernetes/k8s源码分析】 kubelet cgroup 资源预留源码分析

    kubernetes 1.13 WHY 默认情况下 pod 能够使用节点全部可用资源.用户 pod 中的应用疯狂占用内存,pod 将与 node 上的系统守护进程和 kubernetes 组件争夺资源 ...

  9. 【kubernetes/k8s源码分析】kubelet cri源码分析

    CRI基本原理 早期的 kubernetes 使用 docker 作为默认的 runtime,后来又加入 rkt,每加入一种新运行时,k8s 都要修改接口.container runtime 加入,不 ...

最新文章

  1. 怎样在Razor中使用HtmlHelper(MvcHtmlString)
  2. Maven下载、安装和配置(转)
  3. SpringMVC框架的介绍
  4. 如何降低SQL语句复杂度
  5. UE/UX 设计师可临摹的应用模板!
  6. 由深圳的大树所想到的
  7. Mimics 21.0软件学习笔记(一)基本操作
  8. 学习Java必看的Java书籍(本本经典实用)
  9. 实现音乐播放器歌词显示效果
  10. 自定义View实现2048
  11. AI笔记: 数学基础之齐次与非齐次线性方程组解的结构定理
  12. java判断时间是否在某个时间段内
  13. 什么是外包公司?要不要去外包公司?
  14. fortran——实数和复数(矢量)运算
  15. 微信企业邮箱登陆入口在哪?企业邮箱忘记密码怎么修改?
  16. rgb到yuv的转换
  17. Guided Image Filtering
  18. 立法机关从83辆减0辆
  19. 机器学习(决策树四)——简述 剪枝
  20. 站群网站批量文章翻译发布插件

热门文章

  1. 翻过这道山,就有人听到你的故事。
  2. 笔记本在Win7/Win8/win8.1下安装OS X 10.9.3懒人版通用教程
  3. TNS-12535和ORA-3136 超时连接
  4. 安装cPanelWHM 技巧
  5. python学习笔记(二)--深入了解python函数
  6. Linux云计算虚拟化-使用rancher搭建k8s集群并发布电商网站
  7. html中用js格式化JSON输出
  8. 计算机英语句子及翻译,简单的常用英语句子带翻译
  9. Android 复杂的手势处理利用GestureOverView
  10. CentOS 7 Docker安装GVM-11