kubernetes代碼閱讀-apiserver基礎篇

apiserver是整個kubernetes的核心模塊,作的事情多,代碼量也較大。市面上已經有很多apiserver代碼解讀的文章了,但問題在於,因爲k8s的代碼變化很快,想寫一篇長久能用的未必能作到。因此,我參照了《Kubernetes權威指南》和浙大SEL實驗室的一些文章,先把我看到的東西記下來,待後觀是否有用。git

kubernetes源代碼版本1.2.0github

代碼閱讀方法

先簡單講講整個代碼的目錄結構算法

目錄 說明
api 輸出接口文檔用
build 構建腳本
cluster 適配不一樣I層的雲,例如亞馬遜AWS,微軟Azure,谷歌GCE的集羣啓動腳本
cmd 全部的二進制可執行文件入口代碼,例如apiserver/scheduler/kubelet
contrib 項目貢獻者
docs 文檔,包括了用戶文檔、管理員文檔、設計、新功能提議
example 使用案例
Godeps 項目中依賴使用的Go第三方包,例如docker客戶端SDK,rest等
hack 工具箱,各類編譯、構建、測試、校驗的腳本都在這裏面
hooks git提交先後觸發的腳本
pkg 項目代碼主目錄,cmd的只是個入口,這裏是全部的具體實現
plugin 插件,k8s認爲調度器是插件的一部分,因此調度器的代碼在這裏
release 應該是Google發版本用的?
test 測試相關的工具
third_party 一些第三方工具,應該不是強依賴的?
www UI,不過已經被移動到新項目了

能夠看到,關鍵實現代碼都放在pkg這個目錄下。對於apiserver這種跨度很廣的組件而言,惟一有效的閱讀方式估計就是docker

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

0. apiserver主要實現了什麼?

輸入圖片說明

apiserver是k8s系統中全部對象的增刪查改盯的http/restful式服務端,其中盯是指watch操做。數據最終存儲在分佈式一致的etcd存儲內,apiserver自己是無狀態的,提供了這些數據訪問的認證鑑權、緩存、api版本適配轉換等一系列的功能。json

  • restful服務入門

對於http服務和使用go語言實現方式,能夠看go-restful的文檔例子,對這個有基本的瞭解,這個文檔對入門者和只知其一;不知其二者極爲有效!api

1. 對象的數據結構

輸入圖片說明

古人有言,程序就是算法+數據結構,搞懂了數據結構,整個程序的處理過程就明白了一半。對於apiserver的任何一個api請求來講,上圖說明了全部的數據結構關係。緩存

k8s放在etcd內的存儲對象是api.Pod對象(無版本),從不一樣版本的請求路徑標識來操做,例如api/v1,最後獲取到的是不一樣版本,例如v1.Pod的json文本。這裏就經歷了幾個過程,包括安全

  1. http client訪問/api/v1/pod/xyz,想要獲取這個Pod的數據
  2. 從etcd獲取到api.Pod對象
  3. api.Pod對象轉換爲v1.Pod對象
  4. v1.Pod對象序列化爲json或yaml文本
  5. 文本經過http的response體,返回給http client

其中用於處理業務數據的關鍵數據結構是APIGroupVersion,裏面的幾個成員變量的做用是:restful

成員 做用
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)

輸入圖片說明

k8s採用ApiGroup來管理全部的api分組和版本升級,目前有的API分組包括數據結構

  1. 核心組,REST路徑在 /api/v1 ,但這個路徑不是固定的,v1是當前的版本。與之相對應的代碼裏面的apiVersion 字段的值是 v1
  2. 擴展組,REST路徑在 /apis/extensions/$VERSION,相對應的代碼裏面的 apiVersion: extensions/$VERSION (例如當前的apiVersion: extensions/v1beta1)。這裏提供的API對象從此有可能會被移動到別的組內。
  3. "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服務的實現

k8s使用的一個第三方庫github.com/emicklei/go-restful,裏面提供了一組核心的對象,看例子

數據結構 功能 在k8s內的位置
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裏面去。

相關文章
相關標籤/搜索