Kubernetes原始碼之旅:從kubectl到API Server_Kubernetes中文社群
概述:
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命令框架
在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函式的實現展示如下:
這個Result物件由Do函式返回,擁有用來呼叫DecoratedVisitor Visit的函式Visit。這為我們找到了從create.go的RunCreate函式到實際最終呼叫的匿名函式,以及包含了API Server進行呼叫的createAndRefresh函式。這個在create.go的150行實現的Result Visit函式展示如下:
現在我們明白了Visit函式和DecoratedVisitor類如何把這一切連線起來。可以看到150行的inline visitor函式在165行有一個createAndRefresh函式:
這裡的程式碼返回了一個新的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成員。