我是怎麼閱讀kubernetes源代碼的?

爲何要閱讀代碼?怎麼閱讀k8s源代碼?

  • 源代碼中包含了全部信息。寫開源軟件,從文檔和其餘地方拿到的是二手的信息,代碼就是最直接的一手信息。代碼就是黑客帝國中neo看到的世界本源。
  • 文本並非代碼自己。文本只是在人類可讀的模式和編譯器可解析之間作了一個折中。代碼的本質是具備複雜拓撲的數據結構,就像樹或者電路同樣。因此讀代碼的過程是在腦中構建出這個世界,所謂腦補是也。
  • 閱讀好的代碼是一種享受。我最喜歡閱讀的是redis的代碼,用C寫的,極端簡潔但又威力強大。幾句話就把最高效、精妙的數據結構完成出來,就像一篇福爾摩斯的偵探小說。在看的時候我經常想,若是讓我實現這個功能,是否能像他這麼簡單高效?

以閱讀k8s其中的一個模塊,scheduler爲例子,來說講我是怎麼讀代碼的java

從用戶的角度出發,scheduler模塊是幹什麼的?

scheduler是k8s的調度模塊,作的事情就是拿到pod以後在node中尋找合適的進行適配這麼一個單純的功能。實際上,我已經屢次編譯和構建這個程序並運行起來。在個人腦中,sheduler在整個系統中是這樣的:node

輸入圖片說明

scheduler做爲一個客戶端,從apiserver中讀取到須要分配的pod,和擁有的node,而後進行過濾和算分,最後把這個匹配信息經過apiserver寫入到etcd裏面,供下一步的kubelet去拉起pod使用。這樣,馬上有幾個問題浮現出來git

問1.scheduler讀取到的數據結構是怎麼樣的?(輸入)
問2.scheduler寫出的的數據結構是怎麼樣的?(輸出)
問3.在前面的測試中,scheduler成爲了系統的瓶頸,爲何?
問4.社區有人說增長緩存能有效提升scheduler的效率,他的思路是可行的嗎?
github

讀scheduler代碼的整個經歷

層1:cmd入口

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

這段代碼比較短就全文貼出來了redis

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

輸入圖片說明

進入這個文件後,重點看的就是數據結構和方法:api

  • 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產生關係的?

這是代碼目錄樹:

輸入圖片說明

從目錄樹中,能夠看出調度算法的目錄在algrorithemalgrorithemprovider裏面,而把對象組裝在一塊兒的關鍵源代碼是在:

文件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 調度算法的具體實現

這裏我就不詳細敘述細節了,讀者能夠按照個人路子去本身尋找答案。

總結

輸入圖片說明

  • 現代的面向對象的代碼結構,接口和實現分離,邏輯高度的離散在各個源代碼中
  • 人類的大腦適合閱讀線性的單線程的故事
  • 先自頂向下讀,造成一顆代碼的調用樹,直到讀不下去。分析法
  • 再自底向上讀,但不是泛讀,而是在掌握這顆樹的基礎上在某個領域泛讀,把事實拼接起來成爲真相。概括法
  • 在單個源碼文件中,調用過程依然仍是一棵樹,能夠用樹的觀念去解析
  • 對象擁有屬性和方法,就像遊戲人物擁有屬性和技能同樣。不少時候不須要深究這些屬性和技能的細節。
  • 回到戲劇的比喻,現代的代碼和運行結構是構建對象——運行對象,就像戲劇中的角色化妝定型——上臺演戲。戲臺上有大大小小的主角配角,代碼裏也有主要對象次要對象,但劇本的運做讓觀衆能第一時間鎖定主角和主要劇情。看代碼,就是看主要劇情和主角。配角的表演能夠在後面第二遍第三遍的閱讀代碼中再去關注細節。
相關文章
相關標籤/搜索