天天看點

Kubernetes源碼之旅:從kubectl到API Server

概述:

Kubernetes項目目前依然延續着之前爆炸式的擴張。急需能夠了解Kubernetes原理并且貢獻代碼的軟體開發者。學習Kubernetes源碼并不容易。Kubernetes是使用相對年輕的Go語言編寫,并且擁有大量的源代碼。在這個系列的多篇文章裡,我将為大家深入分析Kubernetes的關鍵源碼,以及介紹那些幫助我了解源碼的技術。我的目标是提供一系列的文章,讓對于Kubernetes還較為陌生的開發者能夠快速學習

Kubernetes源碼

在第一篇文章裡,我會分析從運作一個簡單的kubectl指令到向API Server發送REST調用的源碼執行過程。在開始深入Kubernetes之前,我建議你先閱讀一下Julia Evans對Kubernetes架構的進階概述分析的文章。

Kubectl指令的基本運作

Kubernetes裡的指令行接口叫做kubectl。它用來控制Kubernetes叢集。閱讀這部分源碼實作是一個好的開始。我們要追蹤的指令是

kubectl create -f

——它會從檔案建立K8s資源。我們要建立的資源是使用了

Nginx

基礎鏡像的單副本

Pod

。下面是它的yaml描述:

apiVersion: v1
kind: ReplicationController
metadata:
 name: nginx
spec:
 replicas: 1
 selector:
 app: nginx
 template:
 metadata:
 name: nginx
 labels:
 app: nginx
 spec:
 containers: - name: nginx
 image: nginx
 ports: - containerPort: 80      

在一個Kubernetes 開發環境中我們可以用下面的方式調用kubectl:

現在我們知道該如何執行kubectl指令,下面來看看在Kubernetes源碼的哪裡能找到它的實作吧。

在源碼中尋找kubectl的實作

實作kubectl指令的源碼可以在

https://github.com/kubernetes/kubernetes/tree/master/pkg/kubectl/cmd

目錄找到。在這個目錄裡,名為kubectl對應指令的go檔案就是實作的地方。例如,kubectl create指令的起點在create.go。下圖展示了這個目錄和示例go檔案的多種多樣實作:

Kubernetes ️ Cobra指令架構

Kubernetes指令使用

Cobra指令架構

實作。Cobra提供了很多建構指令行接口的特性。基本的Cobra功能說明可以在

https://blog.gopheracademy.com/advent-2014/introducing-cobra/

找到。如圖所示,很容易就可以定位哪個檔案實作了哪個指令行選項。而且Cobra結構使得指令的使用說明、指令描述與運作的代碼相鄰。圖中所示的代碼可以在

https://github.com/kubernetes/kubernetes/blob/fd9a91e0b57face905c4225b8a6633b2ea9c832d/pkg/kubectl/cmd/create.go#L62-#76

找到。這種結構它的好處在于你可以閱讀并找到所有Kubernetes kubectl指令的描述,并且快速跳轉到這些指令的代碼實作。圖中62~76行的字元串Use、Short、Long和Example都包含了描述指令的資訊,和Run指向一個函數實際執行這條指令。

在74行調用的RunCreate函數是kubectl create指令的主要實作。這個函數的實作可以在

https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create.go

檔案找到。下圖列出了RunCreate函數。在132行,我添加了一句fmt.Println來確定這段代碼如我所料被調用了。在後面的

編譯運作Kubernetes

的部分我會展示當為kubectl源碼添加了一些用于調試的單獨語句等時,怎樣加速Kubernetes代碼的重新編譯過程。

Builders 和 Visitors

下面的133~140行是resource.NewBuilder的代碼。一些Go和Kubernetes的新手可能覺得特别害怕。這段代碼值得深入解釋一下。從高處看,這段代碼所做的事情是将指令行接收到的參數轉化為一個資源的列。它也負責建立一個可以用來疊代通路所有資源的Visitor結構。這個指令比較複雜,因為它使用了Builder模式的變種,使用獨立的函數做各自的資料初始化工作。函數Schema、ContinueOnError、NamespaceParam、DefaultNamespace、FilenameParam、SelectorParam和Flatten都引入了一個指向Builder結構的指針,執行一些對它的修改,并且将這個結構體傳回給調用鍊中的下一個方法來執行這些修改。所有的這些方法可以在這裡找到

https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/resource/builder.go

,但我在下面列出了一些你可以了解它如何運作的代碼:

func (b *Builder) Schema(schema validation.Schema) *Builder {
 b.schema = schema
 return b
}

func (b *Builder) ContinueOnError() *Builder {
 b.continueOnError = true return b
}

func (b *Builder) Flatten() *Builder {
 b.flatten = true return b
}      

一旦所有的初始化都完成,resource.NewBuilder函數會調用Do函數。這個Do函數很關鍵,它會傳回一個Result對象,并且将執行對資源的建立。Do函數還會建立一個Visitor對象,可以用來周遊所有關聯到resource.NewBuilder執行過程的資源。Do函數的實作展示如下:

就像816行所展示的,建立了一個新的DecoratedVisitor,并作為Builder Do函數傳回的Result的一部分。這個DecoratedVisitor有一個Visit函數将會調用傳給它的Visitor函數。它的實作在

https://github.com/kubernetes/kubernetes/blob/6b52d8f1383d3a4a769b403a04f812c99ed98815/pkg/kubectl/resource/visitor.go#L306

,如下:

這個Result對象由Do函數傳回,擁有用來調用DecoratedVisitor Visit的函數Visit。這為我們找到了從create.go的RunCreate函數到實際最終調用的匿名函數,以及包含了API Server進行調用的createAndRefresh函數。這個在

create.go

的150行實作的Result Visit函數展示如下:

現在我們明白了Visit函數和DecoratedVisitor類如何把這一切連接配接起來。可以看到150行的inline visitor函數在165行有一個createAndRefresh函數:

這個createAndRefresh函數調用了NewHelper函數,在

https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/resource/helper.go

,并且傳回了一個新的Helper對象:

這裡的代碼傳回了一個新的Helper對象,十分顯而易見

func NewHelper(client RESTClient, mapping *meta.RESTMapping) *Helper { return &Helper{ Resource: mapping.Resource, RESTClient: client, Versioner: mapping.MetadataAccessor, NamespaceScoped: mapping.Scope.Name() == meta.RESTScopeNameNamespace, } }      

在217行createAndRefresh裡Helper的建立和調用它的Create函數,我們最終可以看到Create函數調用了一個createResource函數。在119行的Helper Create函數裡,如下所示是這個Helper createResource函數,以及實際向API Server發送的用來建立yaml檔案描述的資源的REST調用。

編譯和運作Kubernetes

現在我們回顧了代碼,是時候了解如何編譯和運作這些代碼了。在上面的許多代碼示例中你都可以發現fmt.Println()調用。所有這些我添加的用來調試的語句,你也可以将它們加入源代碼。為了編譯這段代碼,我們将使用一個特殊的選項,以告知Kubernetes建構過程隻編譯kubectl這部分代碼。這樣可以極大地加快Kubernetes的編譯速度。為做這個優化的make指令為:

make WAHT='cmd/kubectl'      

并且指出了如何從指令行運作這個指令

一旦我們重新編譯了包含前面添加的print語句的這部分kubectl代碼,就可以用下面的指令啟動我們的Kubernetes開發環境:

PATH=$PATH KUBERNETES_PROVIDER=local hack/local-up-cluster.sh      

下面的圖檔說明了在指令行運作這條指令:

在另一個終端視窗裡我們來繼續執行kubectl指令,然後觀察它的fmt.Printlns的輸出。我們使用下面的指令:

cluster/kubectl.sh create -f ~/nginx_kube_example/nginx_pod.yaml      

下圖展示了我們的調試輸出應該有的樣子:

代碼學習工具

我知道你可能會想:

Brad

,你雖然在Kube和Go都是新手,但你可以快速搞定這一切。你一定是個天才!然而,我有很多的Twitter粉絲,都會積極地拿出證據來駁斥這句話。借助于别人的幫助,我發現了幾個可以真正有助于提升你閱讀Kubernetes源碼能力的工具和技術。在這部分裡,我會介紹我最喜歡的技術:Chrome Sourcegraph Plugin,正确地格式化列印語句,使用go panic來獲得所需要的stack trace,以及Github Blame來進行時空旅行。

Chrome Sourcegraph 插件

這是Morgan Bauer向我介紹了閱讀Kubernetes 源碼最酷炫的工具之一。Chrome Sourcegraph plugin提供了多種進階IDE特性,讓在浏覽Github倉庫時了解Kubernetes Go代碼變得非常容易。這裡是它的使用例子。當我首先開始閱讀Kubernetes 源碼時,我們發現下面的代碼片段非常難以分段和了解。它有數不清的函數,快要淹沒我了。

當在裝有Sourcegraph擴充插件的Chrome浏覽器裡看向這段代碼時,你可以把滑鼠移過每個函數,很快就得到了這個函數的描述,它接受了什麼參數,傳回了什麼結果。這幫助你節省了無比巨大的時間,你可以避免在代碼裡抓取對應的函數定義,來了解它的功能。下面的圖是一個示例:

Chrome Sourcegraph擴充還有一個進階視圖,提供深入被調用函數代碼的功能。這是非常有用的機制:

唯一的問題是有時候Chrome Sourcegraph插件會卡住,并且不能彈出代碼細節。我的經驗是隻要輕點頁面重新整理就可以修複。

列印語句從不過時

我在這篇文章中多次加入了列印語句,來幫助我們确定代碼是否按照預期執行。這個%#v格式選項展示了提供了最典型的調試資訊。不要忘了你可能需要添加“fmt”包:

fmt.Prinln("\n createAndRefresh Info = %#v", info)      

有疑問?PANIC!

我有一段時間非常難以了解Create.go裡createAndRefresh函數是如何被調用的。最後,我決定抛出一個異常來強行得到stack trace并列印到螢幕上。下面的代碼展示了我是怎麼添加這句Panic的。這幫助我最終決定了是哪種Visitor實際被用來調用createAndRefresh函數。

func createAndRefresh(info *resource.Info) error {
 fmt.Println("\n createAndRefresh Info = %#v", info)
 panic("Want Stack Trace")
 obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object) if err != nil { return err
 }
 info.Refresh(obj, true) return nil }      

檢視過去的源碼

有時你看到一些代碼,然後自己開始思考:這些人在送出代碼的時候是怎麼想的。感天謝地,Github浏覽器接口提供了一個blame選項作為使用者接口,下面展示了這個接口:

當我們按下blame按鈕,你會得到一份關于每一行代碼的commit的清單。這讓你可以穿越時空,看到某一特定行在添加的時候開發者試着完成的是什麼。下面的圖展示了blame選項的使用,左手邊列出了所有的commits:

總結

本文中我們試驗了Kubernetes關于運作一個簡單的kubectl指令的多個關鍵代碼,并且閱讀到它向API Server實際發送REST調用的代碼。我們也描述了如何在Kubernetes開發環境中編譯和運作指令。我們最後介紹了幾個有用的工具和技巧。在下篇文章裡,我們将會試驗Kubernetes代碼中另一段重要的代碼。同時,希望這篇文章能夠給你帶來學習Kubernetes源碼的勇氣:千裡之行始于足下。

原文作者:Dr. Brad Topol,IBM傑出工程師,專注于開源技術和開發推廣,同時他也是Kubernetes的貢獻者和Kubernetes Conformance Workgroup成員。

本文轉自中文社群-

Kubernetes源碼之旅:從kubectl到API Server

繼續閱讀