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
}
}