Docker源码走读
Docker是用go语言写的,其源码可以使用LiteIDE或eclipse+goclipse插件阅读。
Docker对使用者来讲是一个C/S模式的架构,用户使用Docker Client与Docker Daemon建立通信,并发送请求给后者。Docker总架构图如下图:
在Docker的具体实现中,Docker Server与Docker Client均由可执行文件docker来完成创建并启动。那么,了解docker可执行文件通过何种方式区分两者,就显得尤为重要。
对于两者,首先举例说明其中的区别。Docker Server的启动,命令为docker -d或docker --daemon=true;而Docker Client的启动则体现为docker --daemon=false ps、docker pull NAME等。
可以把以上Docker请求中的参数分为两类:第一类为命令行参数,即docker程序运行时所需提供的参数,如: -D、--daemon=true、--daemon=false等;第二类为docker发送给Docker Server的实际请求参数,如:ps、pull NAME等。
对于第一类,我们习惯将其称为flag参数,在go语言的标准库中,同时还提供了一个flag包,方便进行命令行参数的解析。
有了以上背景之后,找到整个Docker的代码入口:docker/docker.go中的main函数。当 Docker 启动,它通过 reexec 运行任何的初始化(initializers),如果有,这时它为 Docker executable 解析通过 mflag 包传递的参数。这个包在 pkg/mgflag 下面,别名为 flag。如果需要的话,在这一点它可以打印出版本信息或是开启 debug 模式并记录 debug 日志。
经过解析flag参数后,若flDaemon参数为真的话,则执行mainDaemon函数,实现Docker Daemon的启动,若mainDaemon函数执行完毕,则退出main函数。
若是启动Docker Client,则flDaemon参数为假,不执行以上代码块,继续往下执行。
flags 解析传递给来自于api/client/cli.go的DockerCli类型的Cmd方法。如果一个错误发生,它会被记录,然后程序退出。
下文主要围绕cmd执行相关的代码解析。
1.1 Docker Client
以docker pull 命令为例,解析client代码运行流程。api/client/cli.go的DockerCli类型Cmd函数的职责是通过使用 getMethod 函数把命令参数转换成一个函数。
注意 getMethod 是小写字母。这意味着它无法导出包外,因此它仅仅是在 cli 内可用的。getMethod首先建立一个以 Cmd 开始的由大写字母组合的 string。执行docker pull 时, methodName 变量将变为 CmdPull。使用来自于 Golang reflect 包的 MethodByName 函数,它检索到一个函数指针并返回它。
MethodByName返回的函数,找到api/client/commands.go的CmdPull函数。pi/client/commands.go文件包含了所有的 Docker 命令。CmdPull函数解析自己运行的参数,比如 image和其他参数,并调用一个HTTP POST给Docker服务器。代码片段如下图:
以上便是pull请求的全部执行过程,其他请求的执行在流程上也是大同小异。总之,请求执行过程中,大多都是将命令行中关于请求的参数进行初步处理,并添加相应的辅助信息,最终通过指定的协议给Docker Server发送Docker Client和Docker Server约定好的API请求。
1.2 Docker Server
1.2.1 几个概念
之前已经介绍过,docker的服务端和客户端用的是同一个binary,docker在执行的时候通过-d参数判断执行的是服务端逻辑还是客户端逻辑。若是服务端逻辑则进入mainDaemon()函数。
在讲mainDaemon代码逻辑前先说明几个重要的概念术语:
1 Engine
Engine是docker的核心,中文引擎,它存储着containers,并通过执行Job维护和管理这些containers。
在./docker/engine/engine.go中,Engine结构体的定义如下:
其中,Engine结构体中最为重要的即为handlers属性。该handlers属性为map类型,key为string类型,value为Handler类型。
Handler为一个定义的函数。该函数传入的参数为Job指针,返回为Status状态。
2 Daemon
这儿的Daemon指的是Daemon Struct,Daemon是真正的容器管理者,它负责创建、删除、启动停止、commit等所有一切的容器管理功能。
3 Job
Job是docker引擎中最基本的工作单元,docker中所有的所有操作都被封装为一个job,比如: 在容器中执行一个进程,创建一个容器,监听并服务API等等。
4 Serveapi
1.2.2 mainDaemon()具体实现
宏观来讲,mainDaemon()完成创建一个daemon进程,并使其正常运行。
从功能的角度来说,mainDaemon()实现了两部分内容:第一,创建Docker运行环境;第二,服务于Docker Client,接收并处理相应请求。
从实现细节来讲,mainDaemon()的实现过程主要包含以下步骤:
l daemon的配置初始化(这部分在init()函数中实现,即在mainDaemon()运行前就执行,但由于这部分内容和mainDaemon()的运行息息相关,故可认为是mainDaemon()运行的先决条件);
l 命令行flag参数检查;
l 创建engine对象;
l 设置engine的信号捕获及处理方法;
l 加载builtins;
l 使用goroutine加载daemon对象并运行;
l 打印Docker版本及驱动信息;
l Job之”serveapi”的创建与运行。
上述步骤中的加载builtins,主要工作是为:为engine注册多个Handler,以便后续在执行相应任务时,运行指定的Handler。这些Handler包括:网络初始化、web API服务、事件查询、版本查看、Docker Registry验证与搜索。代码实现位于./builtins/builtins.go,如下:
其中,remote(eng)的实现过程,主要为eng对象注册了两个Handler,分别为”serveapi”与”acceptconnections”,代码如下图:
”serveapi”与”acceptconnections”相应的执行方法分别为apiserver.ServeApi与apiserver.AcceptConnections,具体实现位于. /api/server/server.go。其中,ServeApi执行时,通过循环多种协议,创建出goroutine来配置指定的http.Server,最终为不同的协议请求服务;而AcceptConnections的实现主要是为了通知init守护进程,Docker Daemon已经启动完毕,可以让Docker Daemon进程接受请求。
1.2.3 Cmd和处理方法的映射
mainDaemon()实现中,最后创建并运行”serveapi”的Job。从上述得知,执行该Job会进入apiserver.ServeApi方法(执行Job即执行Job.Run()函数时,会根据通过eng.Register()注册的处理方法处理请求)。往下跟踪,一直进入位于. /api/server/server.go的createRouter()函数,改函数注册路由(如下图)。docker-api会根据不同的路由把请求交由对应的HttpApiFunc处理,如创建容器的请求由postContainersCreate负责处理。
以“创建容器”为例,解释cmd到处理函数逻辑。上面已经说明了用户的请求如果路由被接收,请求处理讲进入postContainersCreate()函数 -》获取并生成“create”Job -》Job把post数据解析为Env –》执行Job –》daemon.ContainerCreate()函数。
那么,执行Job时是怎么找到相应的处理方法即daemon.ContainerCreate()函数的呢?
上面已经说过了,执行Job即执行Job.Run()函数时,会根据通过eng.Register()注册的处理方法处理请求。./daemon/daemon.go 文件中找到相应的map,并通过eng.Register()注册处理方法,如下图:
同样,在./graph/service.go 中,也看到关于image请求的处理方法:
若想要看某个cmd的详细处理,直接进入注册的对应方法就OK了。
1.2.4 Cmd详细处理示例
以“docker pull”命令处理为例,逻辑流程如下图:
由上面讲的映射找到第(4)步,进入s.CmdPull()函数,主要函数调用:
若要修改存储位置,可修改位于./graph/graph.go文件的ImageRoot()函数。
func (graph *Graph) ImageRoot(id string) string {
return path.Join(graph.Root, id)
}
其中graph.Root默认为“/var/lib/docker/graph”。
走读思路及图中流程图均来自于:http://www.infoq.com/cn/articles/docker-source-code-analysis-part1/
- 点赞
- 收藏
- 关注作者
评论(0)