guanjunjian

「三」DOCKER源码分析3 从serverapi到docker run的调用

2017-09-28
guanjunjian

上一篇介绍了docker daemon到serverapi的初始化过程,这一篇介绍从serverapi到docker run的调用;

上文分析到initRouter(api, d, c),它初始化了client发来的各种命令的路由,在其中可以追踪到对于create和start命令;

源码阅读基于docker version1.17.05.x

1. initRouter(api, d, c)路由初始化

1.1 源码

initRouter的实现位于moby/cmd/dockerd/daemon.go,代码的主要内容是:

func initRouter(s *apiserver.Server, d *daemon.Daemon, c *cluster.Cluster) {
	decoder := runconfig.ContainerDecoder{}//获取解码器
	routers := []router.Router{
		...
		//与container的路由,例如/containers/create,/containers/{name:.*}/start
		container.NewRouter(d, decoder),   
		...
	}
	//如果允许网络控制,则添加network相关的路由
	if d.NetworkControllerEnabled() {
		routers = append(routers, network.NewRouter(d, c))
	}
	//如果是experimental模式将所有路由数据项中的experimental模式下的api路由功能激活
	if d.HasExperimental() {
		for _, r := range routers {
			for _, route := range r.Routes() {
				if experimental, ok := route.(router.ExperimentalRoute); ok {
					experimental.Enable()
				}
			}
		}
	}
	//根据设置好的路由表routers来初始化apiServer的路由器
	s.InitRouter(debug.IsEnabled(), routers...)  
}

1.2 流程图

从图中可以看到,docker run发出的"/containers/create""/containers/{name:.*}/start"最后会分别路由到postContainersCreatepostContainersStart函数,在这两个函数中分别做容器创建和启动的工作,这两个函数将在后面的文章中分析,本文将进一步分析initRouter这个函数,了解清楚路由是如何分发的。 路由初始化过程分为以下三个步骤:

  • 向路由表routers中添加路由数据项router
  • 检验experimental模式,若为experimental模式则激活该模式下的api路由功能
  • 根据路由表初始化apiServer路由器

下面挑选步骤1和步骤3详细讲解。

2. 路由初始化步骤分析

2.1 路由表添加路由数据

对于docker run命令的路由表添加为container.NewRouter(d, decoder),代码为:

//从上文container.NewRouter(d, decoder)可以看出,传入的b=d,为daemon.Daemon实例
//这里初始化了一个container相关的路由器对象
func NewRouter(b Backend, decoder httputils.ContainerDecoder) router.Router {
	r := &containerRouter{
		backend: b,
		decoder: decoder,
	}
	r.initRoutes()
	return r
}

//初始化路由数据
func (r *containerRouter) initRoutes() {
	r.routes = []router.Route{
		...		
		// POST
		router.NewPostRoute("/containers/create", r.postContainersCreate), 
		router.NewPostRoute("/containers/{name:.*}/start", r.postContainersStart),
		...
	}
}

可以从上面的r.initRoutes()中看到,container相关的添加了create和start相关的路由,下面先看看router.NewPostRoute()函数:

//初始化一个以HTTP POST方式的路由,返回类型为Route
func NewPostRoute(path string, handler httputils.APIFunc) Route {
	return NewRoute("POST", path, handler)
}

//初始化一个local route
func NewRoute(method, path string, handler httputils.APIFunc) Route {
	return localRoute{method, path, handler}
}

type localRoute struct {
	method  string //该路由中方法名
	path    string //该路由中方法所在的路径
	handler httputils.APIFunc //该方法的handler
}

以create为例,那么它的localRoute的method=”POST”,path=”/containers/create”,handler=r.postContainersCreate,所以当daemon收到client发来的"/containers/create"时,会调用r.postContainersCreate

2.2 apiServer路由器的初始化

该部分由s.InitRouter(debug.IsEnabled(), routers...)完成,代码如下:

func (s *Server) InitRouter(enableProfiler bool, routers ...router.Router) {
	s.routers = append(s.routers, routers...)  //将创建好的路由表信息追加到apiServer对象中的routers。
	m := s.createMux()  //追加后再次初始化apiServer路由器进行更新,下文详细解释
	
	//这里设置好了mux.Route之后,将该route设置到apiServer的路由交换器中去,至此所有deamon.start()的相关工作处理完毕
	s.routerSwapper = &routerSwapper{
		router: m,
	}  
}

下面是s.createMux()的具体实现分析:

func (s *Server) createMux() *mux.Router {
	/*
	mux位于vendor/github.com/gorilla/mux,该函数新建一个mux.go中的Route(路由数据项)对象并追加到mux.Router结构体中的成员routes中去,然后返回该路由器mux.Route m
	*/
	m := mux.NewRouter()  
	
	//遍历所有apiserver中的api路由器如:container
	for _, apiRouter := range s.routers {
		//遍历每个apiRouter的子命令路由r如"/containers/create"  
		for _, r := range apiRouter.Routes() {
			//给每个r的路由handler包裹了一层中间件(这里还不是很清楚)
			f := s.makeHTTPHandler(r.Handler())
			/*
			在mux.Route路由结构中根据这个r.Path()路径设置一个适配器来匹配方法method和handler,当满足versionMatcher+r.Path()路径的正则表达式要求就可以适配到相应的方法名及该handler
			*/
			m.Path(versionMatcher + r.Path()).Methods(r.Method()).Handler(f)
			//同上
			m.Path(r.Path()).Methods(r.Method()).Handler(f) 
		}
	}
	...
	return m
}

结语

本文分析了从apiserver路由到具体的命令执行函数,相对于docker run就是到达r.postContainersCreater.postContainersStart,后面的文章会分别对这两个函数详细分析。

参考


Content