kubernetes/dashboard源碼分析

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
    }
}
發佈了41 篇原創文章 · 獲贊 9 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章