from https://www.gitbook.com/book/fanux/k8s-source-code/details
dashbodard採用前後端分離設計,前端是使用angular的單頁應用,用ES6編寫,後端使用go語言的go-restful框架當作http服務器。本文以介紹後端爲主。
架構圖
客戶端通過瀏覽器發送請求給Backend,Backend使用k8s的restful api操作kubernetes資源和獲取集羣信息。
目錄結構
</kubernetes/dashboard/
▸ build/
▸ docs/
▸ i18n/
▾ src/
▾ app/
▸ assets/
▸ backend/ --->後端的代碼目錄
▸ externs/
▸ frontend/
▸ deploy/
▸ test/
▸ vendor/
主要關心的幾個目錄:
▾ backend/
▾ client/ -->訪問k8s的客戶端
apiserverclient.go
heapsterclient.go
▾ handler/ -->http server handler
apihandler.go
confighandler.go
gziphandler.go
localehandler.go
▸ resource/ -->具體操作資源的方法,如獲取pods列表
▸ validation/
dashboard.go -->main入口
主要流程分析
func main() {
//......忽略忽略忽略
http.Handle("/api/", handler.CreateHTTPAPIHandler(apiserverClient, heapsterRESTClient, config))
//......忽略忽略忽略
log.Print(http.ListenAndServe(fmt.Sprintf(":%d", *argPort), nil))
}
main裏面啓動了一個http服務器。 (ps:go的http.Handle第一個參數是一個url,第二個參數是一個接口,任何實現了 ServeHTTP(ResponseWriter, *Request)方法的對象都可以作爲入參數)
我們看一下CreateHTTPAPIHandler的邏輯:
func CreateHTTPAPIHandler(client *clientK8s.Client, heapsterClient client.HeapsterClient,
clientConfig clientcmd.ClientConfig) http.Handler {
//......忽略忽略忽略
/*這裏以獲取pod列表爲例來看其流程*/
apiV1Ws.Route(
apiV1Ws.GET("/pod").
To(apiHandler.handleGetPods).
Writes(pod.PodList{}))
//......忽略忽略忽略
}
這個部分使用了go-restful框架,一個非常好的開發restful api的庫,kubernetes本身也是用的這個庫。 To方法給定一個處理前端請求的方法
func (apiHandler *APIHandler) handleGetPods(
request *restful.Request, response *restful.Response) {
result, err := pod.GetPodList(apiHandler.client, apiHandler.heapsterClient, namespace, dataSelect)
response.WriteHeaderAndEntity(http.StatusCreated, result)
}
這個handler裏面調用了resource下面的pod資源管理器,講結果寫到響應中。再看pod.GetPodList具體實現:
func GetPodList(client k8sClient.Interface, heapsterClient client.HeapsterClient,
nsQuery *common.NamespaceQuery, dsQuery *common.DataSelectQuery) (*PodList, error) {
channels := &common.ResourceChannels{
PodList: common.GetPodListChannelWithOptions(client, nsQuery, api.ListOptions{}, 1),
}
//從通道中獲取pod列表
return GetPodListFromChannels(channels, dsQuery, heapsterClient)
}
繼續看common.GetPodListChannelWithOptions:
func GetPodListChannelWithOptions(client client.PodsNamespacer, nsQuery *NamespaceQuery,
options api.ListOptions, numReads int) PodListChannel {
//創建了兩個管道,一個存放pod列表,一個存放錯誤信息。
channel := PodListChannel{
List: make(chan *api.PodList, numReads),
Error: make(chan error, numReads),
}
//創建一個協程,將獲取到的pod列表信息寫入通道中
go func() {
list, err := client.Pods(nsQuery.ToRequestParam()).List(options)
var filteredItems []api.Pod
for _, item := range list.Items {
if nsQuery.Matches(item.ObjectMeta.Namespace) {
filteredItems = append(filteredItems, item)
}
}
list.Items = filteredItems
for i := 0; i < numReads; i++ {
channel.List <- list
channel.Error <- err
}
}()
return channel
}
現在主要流程走完了,但是還是不知道在哪去請求kubernetes api server的。所以現在的關鍵點就在於協程中的client具體傳入的是哪個對象,最終執行的是它的Pods().List()方法。
先看一下Pod接口:
type PodsNamespacer interface {
Pods(namespace string) PodInterface
}
type PodInterface interface {
List(opts api.ListOptions) (*api.PodList, error)
Get(name string) (*api.Pod, error)
Delete(name string, options *api.DeleteOptions) error
Create(pod *api.Pod) (*api.Pod, error)
Update(pod *api.Pod) (*api.Pod, error)
Watch(opts api.ListOptions) (watch.Interface, error)
Bind(binding *api.Binding) error
UpdateStatus(pod *api.Pod) (*api.Pod, error)
GetLogs(name string, opts *api.PodLogOptions) *restclient.Request
}
所以,只要找到具體的client實例,並看它實現的Pods方法即可。
在APIHandler.handleGetPods()函數中我們可以看到傳入的client是apiHandler.client
,再看APIHanler結構體
type APIHandler struct {
client *clientK8s.Client
heapsterClient client.HeapsterClient
clientConfig clientcmd.ClientConfig
verber common.ResourceVerber
}
所以,最終的client是clientK8s.Client
type Client struct {
*restclient.RESTClient
*AutoscalingClient
*AuthenticationClient
*BatchClient
*ExtensionsClient
*AppsClient
*PolicyClient
*RbacClient
*discovery.DiscoveryClient
*CertificatesClient
}
我們猜想Client肯定有一個Pods方法,事實證明確實有:
func (c *Client) Pods(namespace string) PodInterface {
return newPods(c, namespace)
}
再看newPods方法:
type pods struct {
r *Client
ns string
}
func newPods(c *Client, namespace string) *pods {
return &pods{
r: c, //http客戶端
ns: namespace,
}
}
所以說,最最終調用的是pods結構體的List方法。
func (c *pods) List(opts api.ListOptions) (result *api.PodList, err error) {
result = &api.PodList{}
err = c.r.Get().Namespace(c.ns).Resource("pods").VersionedParams(&opts, api.ParameterCodec).Do().Into(result)
return
}
真相開始浮出水面:
err = c.r.Get()\ -->設置了請求的http方法爲GET
.Namespace(c.ns).\ -->設置請求的namespace
Resource("pods").\ -->設置資源爲pods
VersionedParams(&opts, api.ParameterCodec).\ -->添加版本參數
Do().\ -->發送http請求
Into(result) -->講結果寫入result
我們再找一下在哪構造的url和發送的http請求,着重看.Do()函數幹啥了:
func (r *Request) Do() Result {
r.tryThrottle()
var result Result
err := r.request(func(req *http.Request, resp *http.Response) {
result = r.transformResponse(resp, req)
})
//...
return result
}
//request函數主要邏輯:
func (r *Request) request(fn func(*http.Request, *http.Response)) error {
//設置http方法等
//這裏有個多次請求的邏輯,一次請求失敗了睡眠一下繼續請求,請求次數不超過maxRetries次默認爲10次
for {
url := r.URL().String() //URL方法根據之前設置的pods namespace等參數構造url
//其它邏輯忽略,最終將請求發送給api server
}
}