Kubernetes源碼之旅:從kubectl到API Server

概述:

Kubernetes項目目前依然延續着以前爆炸式的擴張。急需可以理解Kubernetes原理而且貢獻代碼的軟件開發者。學習Kubernetes源碼並不容易。Kubernetes是使用相對年輕的Go語言編寫,而且擁有大量的源代碼。在這個系列的多篇文章裏,我將爲你們深刻分析Kubernetes的關鍵源碼,以及介紹那些幫助我理解源碼的技術。個人目標是提供一系列的文章,讓對於Kubernetes還較爲陌生的開發者可以快速學習Kubernetes源碼nginx

在第一篇文章裏,我會分析從運行一個簡單的kubectl命令到向API Server發送REST調用的源碼執行過程。在開始深刻Kubernetes以前,我建議你先閱讀一下Julia Evans對Kubernetes架構的高級概述分析的文章。git

Kubectl命令的基本運行

Kubernetes裏的命令行接口叫作kubectl。它用來控制Kubernetes集羣。閱讀這部分源碼實現是一個好的開始。咱們要追蹤的命令是kubectl create -f——它會從文件建立K8s資源。咱們要建立的資源是使用了Nginx基礎鏡像的單副本Pod。下面是它的yaml描述:github

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:api

如今咱們知道該如何執行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指向一個函數實際執行這條命令。app

在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成員。

相關文章
相關標籤/搜索