码迷,mamicode.com
首页 > 其他好文 > 详细

Hyper 源码分析-----创建hyper daemon

时间:2015-08-26 22:24:27      阅读:273      评论:0      收藏:0      [点我收藏+]

标签:源码   hyper   虚拟机   docker   daemon   

一、概述

hyper daemonhyper的后台守护进程,我们必须要通过输入hyperd命令先打开hyper daemon,然后才能执行hyper runhyper stop等一系列对于hyper虚拟机的操作命令。与hyper client不同的是,hyper daemon承担了整个hyper程序的大部分任务且主要由三部分组成:serverenginejob

其中server用于接收来自hyper client端的请求,然后将请求转发到engine中。engine则主要是路由的作用,通过一个handler对象将具体的请求分发到相应的job去执行。至于job则用于将任务转发的具体的函数去执行。其实从上面的叙述我们可以发现,这些任务的转发的确显得有点啰嗦,所以在dockerengjinejob这个两个概念已经被移除了。希望hyper在以后的代码重构时也能对这部分代码进行精简。下面我们就通过源码来观察hyper daemon的整个启动过程。

 

二、flag 参数解析

当你输入在shell中输入hyperd命令时,我们就进入hyperd.go文件中的main函数开始创建hyper daemon了!首先和hyper client中类似,我们需要对命令行参数进行解析。main函数中先是定义了三个flag参数flConfig,flHost,flHelp分别表示hyper daemon的配置信息,hostIP地址和端口号,以及是否输出帮助信息。当然,这些我们都没有,所以直接采用默认设置。接下来执行语句

mainDaemon(*flConfig, *flHost)正式进入hyper daemon的创建。

 

三、mainDaemon函数分析

因为我们并没有显式指定配置文件的地址,所以首先要做的就是指定默认的配置文件地址为/etc/hyper/config,其实里面指定的无非是hyper kernel,hyper initrd等等用于启动虚拟机的组件的地址而已。如果你想用自己的kernel启动虚拟机,可以试着修改里面的内容。

接下来是两条非常关键的语句,分别用于初始化enginedaemon 

eng := engine.New(config)

d, err := daemon.NewDaemon(eng)

因为它们都非常重要,所以在下面的小节中再进行详细的叙述。在初始化enginedaemon之后,通过err := d.Install(eng)函数将具体的方法注册到enginehandler中,其实做的不过就是初始化一个映射表map[string]engine.Handler而已。例如通过server传递给engine的是“podRun”请求,则将其映射得到daemon.CmdPodRun方法。由此完成了从server请求到具体执行函数的映射。此时,hyper daemon就已经完全准备好了,接下来就可以接收来自hyper client的请求。通过如下代码:

if err := eng.Job("acceptconnections").Run(); err != nil {

glog.Error("the acceptconnections job run failed!\n")

return

}

…....

job := eng.Job("serveapi", defaultHost...)

…....

go func() {

if err := job.Run(); err != nil {

glog.Errorf("ServeAPI error: %v\n", err)

serveAPIWait <- err

return

}

serveAPIWait <- nil

}()

先是创建了一个叫”acceptconnections”job用于通知hyper daemon可以接受来自hyper client的请求了。紧接着创建了一个名为”serveapi”job,然后创立了一个独立协程(goroutine)专门接受请求,也就是初始化前面所说的server,本质上来说,server也就是一个job。这样,在mainDaemon函数内,hyper daemon的各个组件都创建完备,接下来我们进入具体的函数,观察engine的启动,daemon数据结构的填充以及最后server的建立的详细过程。

 

四、engine初始化

engine.New(config)函数中首先做的就是填充Engine结构体,初始化了一个engine

eng := &Engine{

handlers: make(map[string]Handler),

id:       "test-1024",

Config:   config,

Stdout:   os.Stdout,

Stderr:   os.Stderr,

Stdin:    os.Stdin,

Logging:  true,

}

显然,其中最重要的就是handlers字段了,因为这相当与就是一个路由表,能够将相应的请求字符串映射到具体的处理函数。在这之后,因为handlers的注册主要是集中在d.Install(eng)中做的,在eng.New(config)中仅仅只是注册了键值为“commands”handler用于输出engine中已经注册的命令。最后,将Engine对象返回,eng.New(eng)的任务也就完成了。从以上的论述可以发现,engine实质上就是一个任务分发器,在整个hyper daemon中起到的作用非常有限。但是需要说明的是,以这种注册的方式添加对于不同请求的操作,的确具有非常好的扩展性,只要扩充一下server的接收请求,然后将相应的请求注册到engine的分发表中,即可增加一个新的操作。

 

五、NewDaemon的创建

从上文中我们了解到通过daemon.NewDaemon(eng)我们就创建了一个新的daemon。实际的情况是,NewDaemon函数仅仅只是一个封装,里面只有一步操作,那就是调用daemon, err := NewDaemonFromDirectory(eng),这个函数才真正完成了daemon的初始化。

首先,在NewDaemonFromDirectory函数中做的就是对主机环境的检查,其中运行的操作系统必须是Linux。其实hyper本身对操作系统并没有依赖,但是由于它需要调用docker为其创建容器,而docker目前是只能在Linux环境下运行的,所以hyper需要检查主机是否为Linux。之后要做的就是添加对daemon的配置,也就是填充daemon这个数据结构。其中Daemon这个数据结构的定义如下所示:

 

type Daemon struct {

ID                string

db                *leveldb.DB

eng               *engine.Engine

dockerCli         *docker.DockerCli

containerList     []*Container

podList           map[string]*Pod

vmList            map[string]*Vm

qemuChan          map[string]interface{}

qemuClientChan    map[string]interface{}

subQemuClientChan map[string]interface{}

kernel            string

initrd            string

bios              string

cbfs              string

BridgeIface       string

BridgeIP          string

Host              string

Storage           *Storage

}

 

可以发现Daemon中存在着大量的map[string]interface{}类型的变量。至于为什么会这样呢?其实仔细想一下我们就能知道,假设我现在通过hyper client要对一个虚拟机进行操作,那我怎么找到它呢?其实就是通过daemon里面的这些podList,vmList映射表。例如podList[“podID”]就能得到该podID对应的Pod数据结构。而podvmcontainer之间往往是相互关联的,因此就能非常容易得找到对方,而这些同时也体现了hyper daemon的调度作用。

还有就是dockerCli这个字段看起来感觉非常奇怪,为什么hyper中会出现dockerclient?等到我们分析hyper启动虚拟机的时候就可以发现,hyper本身是不创建容器的,它做的不过是调用docker clientdocker daemon发送一个create container的请求,让docker创建一个容器的镜像,然后将其挂载到虚拟机中去。明白了这一点之后,我们再来看看Storage这个字段,它其实是一个指向Storage类型的指针。而Storage保存的是容器镜像存储方面的内容,我们知道docker的镜像是支持多种文件系统的,例如:aufs,devicemapper,vfs等等。因此,这时hyper就需要获得docker daemon方面的信息,根据docker daemon使用的镜像的存储方式来确认自身的存储类别配置。而这个工作是通过body, _, err := dockerCli.SendCmdInfo()这个函数完成的。然后根据从docker daemon得到的配置信息对hyper daemonstorage字段进行配置。因为容器镜像的存储方式是个非常重要的内容,完全值得再开一篇专题进行论述,因此这里就不再详细分析了。到这里,hyper daemon的主体也就创建完成了,最后剩下的就是对http server的创建。

 

六、server的创建

从前面对mainDaemon的分析我们已经知道,最后会创建一个叫做”serveapi”job用于接受来自hyper client的请求,其实也就是做了一个http server。然后,这个叫”serveapi”job最终会被分发到server.go文件的ServeApi(job *engine.Job)函数中。第一步,我们要做的就是根据hyper  daemon支持的不同协议,创建出不同的http server。这里,我们遍历Job中的Args字段,分离出不同的协议类型以及对应的通信地址,最后通过srv, err := NewServer(protoAddrParts[0], protoAddrParts[1], job)函数创建出相应的http 服务器。其实hyper 原生支持的是tcpunix domain两种形式协议的 。但由于我们默认使用的unix domain协议并且为了叙述方便,我们就以基于unix domainhttp server进行介绍。因此在NewSever()函数中自然是选择setupUnixHttp(addr, job)函数进行执行。在setupUnixHttp(addr, job)中首先执行r := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("CorsHeaders"), job.Getenv("Version"))创建一个路由分发表。通过分发表,将不同的http请求,分发到相应的handler进行执行。然后通过l, err := newListener("unix", addr, job.GetenvBool("BufferRequests")设立监听模块,其实整个函数也只是一个封装,内部实现就只是调用Go语言内置net库的net.Listen(proto, addr)而已。当请求路由和监听模块都配置完整了之后,就可以填充并返回http server相应的数据结构&HttpServer{&http.Server{Addr: addr, Handler: r}, l},回到前面的所说的serveApi函数之后再调用srv.Server()函数就架起了一个基于unix domain协议的http服务器了,可以用于接收来自hyper client的请求。

这里有必要再详细说明一下r := createRouter(...)这个函数,它的主要作用是创建并配置一个路由分发表。开始的时候,它先定义了一个变量r := mux.NewRouter(),其中NewRoutergorilla/mux包内置的一个函数,用于创建一个Router类型的对象。然后,函数中又定义了变量m:

m := map[string]map[string]HttpApiFunc{

"GET": {

"/info":     getInfo,

"/pod/info": getPodInfo,

"/version":  getVersion,

"/list":     getList,

"/checkpoint": getCheckpoint,

},

"POST": {

"/container/create": postContainerCreate,

"/image/create":     postImageCreate,

"/pod/create":       postPodCreate,

"/pod/start":        postPodStart,

"/pod/remove":       postPodRemove,

"/pod/run":          postPodRun,

"/pod/stop":         postStop,

"/vm/create":        postVmCreate,

"/vm/kill":          postVmKill,

"/exec":             postExec,

"/attach":           postAttach,

"/tty/resize":       postTtyResize,

},

"DELETE": {},

"OPTIONS": {

"": optionsHandler,

},

}

显然,从m的定义我们就可以看出,这就是一张完整的路由分发表。例如我们在需要启动虚拟机的时候,hyper client就会向server发送一个POST /pod/run的请求,这时候,通过整个路由表我们就能找到posPodRun函数进行执行,然后在postPodRun中再调用engine的相关操作,server的功能也就达到了。接下来我们要做的就是将路由表m添加到之前定义的router中就行了,所以就有了一下的一系列语句:

for method, routes := range m {

for route, fct := range routes {

glog.V(0).Infof("Registering %s, %s\n", method, route)

// NOTE: scope issue, make sure the variables are local and won‘t be changed

localRoute := route

localFct := fct

localMethod := method

 

// build the handler function

f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, corsHeaders, version.Version(dockerVersion))

 

// add the new route

if localRoute == "" {

r.Methods(localMethod).HandlerFunc(f)

} else {

r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)

r.Path(localRoute).Methods(localMethod).HandlerFunc(f)

}

}

}

显然,上述代码在做的工作就是遍历m中的各个表项,获取对应的http method ,执行路径,以及相应的http handler。而具体的http.HandlerFunc函数指针则是通过makeHttpHandler获得的,最后,将其注册到路由表r中。Http Server的路由转发模块完成。

 

七、总结

其实,由上文的分析我们可以看出hyper daemon不过就是两个路由转发表,先由http server接收来自http client的请求,通过http server的路由表转发到相应的函数执行。接着再相应函数中再用engine启动相应的job又完成了一次转发。最后又通过job去执行具体的函数,完成命令的具体实现。

版权声明:本文为博主原创文章,未经博主允许不得转载。

Hyper 源码分析-----创建hyper daemon

标签:源码   hyper   虚拟机   docker   daemon   

原文地址:http://blog.csdn.net/u011915301/article/details/48010757

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!