1. 程式人生 > >kubernetes原始碼版本1.2 程式碼解讀

kubernetes原始碼版本1.2 程式碼解讀

kubernetes原始碼版本1.2.0 (目前最新kubernetes版本1.4)

程式碼閱讀方法

先簡單講講整個程式碼的目錄結構

| 目錄 | 說明 |
| ———– | —————————————- |
| api | 輸出介面文件用 |
| build | 構建指令碼 |
| cmd | 所有的二進位制可執行檔案入口程式碼,例如apiserver/scheduler/kubelet |
| contrib | 專案貢獻者
| pkg | 專案程式碼主目錄,cmd的只是個入口,這裡是所有的具體實現 |
| plugin | 外掛,k8s認為排程器是外掛的一部分,所以排程器的程式碼在這裡 |
| release | 應該是Google發版本用的? |
| test | 測試相關的工具 |
| third_party | 一些第三方工具,應該不是強依賴的? |
| www | UI,不過已經被移動到新專案了 |
| docs | 文件,包括了使用者文件、管理員文件、設計、新功能提議 |
| example | 使用案例 |
| Godeps | 專案中依賴使用的Go第三方包,例如docker客戶端SDK,rest等 |
| hack | 工具箱,各種編譯、構建、測試、校驗的指令碼都在這裡面 |

可以看到,關鍵實現程式碼都放在pkg這個目錄下。對於apiserver這種跨度很廣的元件而言,唯一有效的閱讀方式估計就是

遍歷pkg下所有的目錄,概覽大概知道這個目錄是幹啥的
在上面這幾步的過程中可以看看別人的程式碼閱讀文件,能有效的節省時間
從cmd這個入口來看apiserver的程式碼,然後一點點由淺入深,看apiserver的大致實現
分特性,看具體某個大的特性是怎麼實現的,例如安全,例如和etcd儲存對接

apiserver主要實現了什麼?

apiserver是kubernetes系統中所有物件的增刪查改盯的http/restful式服務端,其中盯是指watch操作。資料最終儲存在分散式一致的etcd儲存內,apiserver本身是無狀態的,提供了這些資料訪問的認證鑑權、快取、api版本適配轉換等一系列的功能。

restful服務入門

對於http服務和使用go語言實現方式,可以看go-restful的文件和例子,對這個有基本的瞭解,這個文件對入門者和一知半解者極為有效!

1. 物件的資料結構

古人有言,程式就是演算法 資料結構,搞懂了資料結構,整個程式的處理過程就明白了一半。對於apiserver的任何一個api請求來說,上圖說明了所有的資料結構關係。

kubernetes放在etcd內的儲存物件是api.Pod物件(無版本),從不同版本的請求路徑標識來操作,例如api/v1,最後獲取到的是不同版本,例如v1.Pod的json文字。這裡就經歷了幾個過程,包括

http client訪問/api/v1/pod/xyz,想要獲取這個Pod的資料
v1.Pod物件序列化為json或yaml文字
從etcd獲取到api.Pod物件
api.Pod物件轉換為v1.Pod物件
文字通過http的response體,返回給http client
其中用於處理業務資料的關鍵資料結構是APIGroupVersion,裡面的幾個成員變數的作用是:

| 成員 | 作用 |
| ———— | —————————————- |
| GroupVersion | 包含 api/v1這樣的string,用於標識這個例項 |
| Serializer | 物件序列化和反序列化器 |
| Converter | 這是一個強大的資料結構,這裡放的是個介面,本體在/pkg/conversion/conversion.go,幾乎可以轉換任意一種物件到另一種,只要你事先注入了相應的轉換函式 |
| Storage | 這個map的key,用於物件的url,value是一個rest.Storage結構,用於對接etcd儲存,在初始化註冊時,會把這個map化開,化為真正的rest服務到儲存的一條龍服務 |

2. 入口和啟動

| 檔案 | 主要資料結構/函式 | 用途 |
| —————————————- | ——————– | ——————- |
| kubernetes/cmd/kube-apiserver/apiserver.go | | 入口 |
| kubernetes/cmd/kube-apiserver/app/options/options.go | struct APIServer | 啟動選項 |
| kubernetes/cmd/kube-apiserver/apiserver.go | func Run | 初始化一些客戶端、啟動master物件 |
| kubernetes/pkg/genericapiserver/genericapiserver.go | func Run | 啟動安全和非安全的http服務 |

3. API分組、多版本的初始化註冊(Rest)

kubernetes採用ApiGroup來管理所有的api分組和版本升級,目前有的API分組包括

  • 核心組,REST路徑在 /api/v1 ,但這個路徑不是固定的,v1是當前的版本。與之相對應的程式碼裡面的apiVersion 欄位的值是v1。
  • 擴充套件組,REST路徑在 /apis/extensions/$VERSION,相對應的程式碼裡面的 apiVersion: extensions/$VERSION (例如當前的apiVersion: extensions/v1beta1)。這裡提供的API物件今
  • 後有可能會被移動到別的組內。
    “componentconfig”和 “metrics”這這些組。
    在這個文件裡面講述了實現ApiGroup的幾個目標,包括api分組演化,對舊版API的向後相容(Backwards compatibility),包括使用者可以自定義自己的api等。接下來我們看看他麼是怎麼初始化註冊的,這裡都是縮減版程式碼,去掉了其他部分。

kubernetes/pkg/master/master.go

api註冊入口

func New(c *Config) (*Master, error) { m.InstallAPIs(c)
}
– 根據Config往APIGroupsInfo內增加組資訊,然後通過InstallAPIGroups進行註冊

func (m *Master) InstallAPIs(c *Config) { if err := m.InstallAPIGroups(apiGroupsInfo); err != nil {
glog.Fatalf(“Error in registering group versions: %v”, err)
}
}

轉換為APIGroupVersion這個關鍵資料結構,然後進行註冊

func (s *GenericAPIServer) installAPIGroup(apiGroupInfo *APIGroupInfo) error {
 apiGroupVersion, err := s.getAPIGroupVersion(apiGroupInfo, groupVersion, apiPrefix)

if err := apiGroupVersion.InstallREST(s.HandlerContainer); err != nil {
 return fmt.Errorf("Unable to setup API %v: %v", apiGroupInfo, err)
 }
 }

關鍵資料結構

kubernetes/pkg/apiserver/apiserver.go
 type APIGroupVersion struct {
 Storage map[string]rest.Storage

Root string

// GroupVersion is the external group version
 GroupVersion unversioned.GroupVersion
 }

實際註冊的Storage的map如下:

kubernetes/pkg/master/master.go

m.v1ResourcesStorage = map[string]rest.Storage{
 "pods": podStorage.Pod,
 "pods/attach": podStorage.Attach,
 "pods/status": podStorage.Status,
 "pods/log": podStorage.Log,
 "pods/exec": podStorage.Exec,
 "pods/portforward": podStorage.PortForward,
 "pods/proxy": podStorage.Proxy,
 "pods/binding": podStorage.Binding,
 "bindings": podStorage.Binding,

那麼,這裡的map[string]rest.Storage最後是怎麼變成一個具體的API來提供服務的呢?例如這麼一個URL:

GET /api/v1/namespaces/{namespace}/pods/{name}

restful服務的實現

kubernetes使用的一個第三方庫github.com/emicklei/go-restful,裡面提供了一組核心的物件,看例子

| 資料結構 | 功能 | 在kubernetes內的位置 |
| —————— | —————————————- | —————————————- |
| restful.Container | 代表一個http rest服務物件,包括一組restful.WebService | genericapiserver.go – GenericAPIServer.HandlerContainer |
| restful.WebService | 由多個restful.Route組成,處理這些路徑下所有的特殊的MIME型別等 | api_installer.go – NewWebService() |
| restful.Route | 路徑——處理函式對映map | api_installer.go – registerResourceHandlers() |

實際註冊過程

kubernetes/pkg/apiserver/api_installer.go

func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService, proxyHandler http.Handler) (*unversioned.APIResource, error) { }

最終的API註冊過程是在這個函式中完成的,把一個rest.Storage物件轉換為實際的getter, lister等處理函式,並和實際的url關聯起來。

4.etcd儲存的操作(ORM)

上面已經基本釐清了從http請求 -> restful.Route -> rest.Storage這條線路,那rest.Storage僅僅是一個介面,有何德何能,可以真正的操作etcd呢?

這段也是牽涉到多個檔案,但還比較清晰,首先,所有的物件都有增刪改查這些操作,如果為Pod單獨搞一套,Controller單獨搞一套,那程式碼會非常重複,不可複用,所以儲存的關鍵目錄是在這裡:

kubernetes/pkg/registry/generic/etcd/etcd.go

這個檔案定義了所有的對etcd物件的操作,get,list,create等,但具體的物件是啥,這個檔案不關心;etcd客戶端地址,這個檔案也不關心。這些資訊都是在具體的PodStorage物件建立的時候注入的。以Pod為例子,檔案在:

kubernetes/pkg/registry/pod/etcd/etcd.go

這裡的NewStorage方法,把上述的資訊注入了etcd裡面去,生成了PodStorage這個物件。

// REST implements a RESTStorage for pods against etcd type REST struct {
*etcdgeneric.Etcd
proxyTransport http.RoundTripper
}

由於PodStorage.Pod是一個REST型別,而REST型別採用了Go語言的struct匿名內部成員,天然就擁有Get, List等方法。

kubernetes/pkg/apiserver/api_installer.go

最後在這裡把PodStorage轉換成了Getter物件,並最終註冊到ApiGroup裡面去。