Docker源码走读

举报
huopodeajuan 发表于 2017/12/29 16:45:42 2017/12/29
【摘要】 Docker是用go语言写的,其源码可以使用LiteIDE或eclipse+goclipse插件阅读。Docker对使用者来讲是一个C/S模式的架构,用户使用Docker Client与Docker Daemon建立通信,并发送请求给后者。Docker总架构图如下图:在Docker的具体实现中,Docker Server与Docker Client均由可执行文件docker来完成创建并启动。那么,

Docker是用go语言写的,其源码可以使用LiteIDEeclipse+goclipse插件阅读。

Docker对使用者来讲是一个C/S模式的架构,用户使用Docker ClientDocker Daemon建立通信,并发送请求给后者。Docker总架构图如下图:

1.jpg

Docker的具体实现中,Docker ServerDocker Client均由可执行文件docker来完成创建并启动。那么,了解docker可执行文件通过何种方式区分两者,就显得尤为重要。

对于两者,首先举例说明其中的区别。Docker Server的启动,命令为docker -ddocker --daemon=true;而Docker Client的启动则体现为docker --daemon=false psdocker pull NAME等。

可以把以上Docker请求中的参数分为两类:第一类为命令行参数,即docker程序运行时所需提供的参数,如: -D--daemon=true--daemon=false等;第二类为docker发送给Docker Server的实际请求参数,如:pspull 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函数。

2.png

若是启动Docker Client,则flDaemon参数为假,不执行以上代码块,继续往下执行。

flags 解析传递给来自于api/client/cli.goDockerCli类型的Cmd方法。如果一个错误发生,它会被记录,然后程序退出。

3.png

下文主要围绕cmd执行相关的代码解析。

1.1      Docker Client

docker pull 命令为例,解析client代码运行流程。api/client/cli.goDockerCli类型Cmd函数的职责是通过使用 getMethod 函数把命令参数转换成一个函数。

4.png

注意 getMethod 是小写字母。这意味着它无法导出包外,因此它仅仅是在 cli 内可用的。getMethod首先建立一个以 Cmd 开始的由大写字母组合的 string。执行docker pull 时, methodName 变量将变为 CmdPull。使用来自于 Golang reflect 包的 MethodByName 函数,它检索到一个函数指针并返回它。

5.png

MethodByName返回的函数,找到api/client/commands.goCmdPull函数。pi/client/commands.go文件包含了所有的 Docker 命令。CmdPull函数解析自己运行的参数,比如 image和其他参数,并调用一个HTTP POSTDocker服务器。代码片段如下图:

6.png

以上便是pull请求的全部执行过程,其他请求的执行在流程上也是大同小异。总之,请求执行过程中,大多都是将命令行中关于请求的参数进行初步处理,并添加相应的辅助信息,最终通过指定的协议给Docker Server发送Docker ClientDocker Server约定好的API请求。

1.2      Docker Server

1.2.1        几个概念

之前已经介绍过,docker的服务端和客户端用的是同一个binarydocker在执行的时候通过-d参数判断执行的是服务端逻辑还是客户端逻辑。若是服务端逻辑则进入mainDaemon()函数。

在讲mainDaemon代码逻辑前先说明几个重要的概念术语:

1           Engine

Enginedocker的核心,中文引擎,它存储着containers,并通过执行Job维护和管理这些containers

./docker/engine/engine.go中,Engine结构体的定义如下:

7.png

其中,Engine结构体中最为重要的即为handlers属性。该handlers属性为map类型,keystring类型,valueHandler类型。

Handler为一个定义的函数。该函数传入的参数为Job指针,返回为Status状态。

2           Daemon

这儿的Daemon指的是Daemon StructDaemon是真正的容器管理者,它负责创建、删除、启动停止、commit等所有一切的容器管理功能。

8.png

3           Job

Jobdocker引擎中最基本的工作单元,docker中所有的所有操作都被封装为一个job,比如在容器中执行一个进程,创建一个容器,监听并服务API等等。

9.png

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,如下:

10.png

其中,remote(eng)的实现过程,主要为eng对象注册了两个Handler,分别为”serveapi””acceptconnections”,代码如下图:

11.png

”serveapi””acceptconnections”相应的执行方法分别为apiserver.ServeApiapiserver.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.gocreateRouter()函数,改函数注册路由(如下图)。docker-api会根据不同的路由把请求交由对应的HttpApiFunc处理,如创建容器的请求由postContainersCreate负责处理。

12.png

以“创建容器”为例,解释cmd到处理函数逻辑。上面已经说明了用户的请求如果路由被接收,请求处理讲进入postContainersCreate()函数 -》获取并生成“createJob -Jobpost数据解析为Env –》执行Job –daemon.ContainerCreate()函数。

那么,执行Job时是怎么找到相应的处理方法即daemon.ContainerCreate()函数的呢?

上面已经说过了,执行Job即执行Job.Run()函数时,会根据通过eng.Register()注册的处理方法处理请求。./daemon/daemon.go 文件中找到相应的map,并通过eng.Register()注册处理方法,如下图:

13.png

同样,在./graph/service.go 中,也看到关于image请求的处理方法:

14.png

若想要看某个cmd的详细处理,直接进入注册的对应方法就OK了。

1.2.4        Cmd详细处理示例

以“docker pull”命令处理为例,逻辑流程如下图:

15.png

由上面讲的映射找到第(4)步,进入s.CmdPull()函数,主要函数调用:

16.png

若要修改存储位置,可修改位于./graph/graph.go文件的ImageRoot()函数。

func (graph *Graph) ImageRoot(id stringstring {

    return path.Join(graph.Root, id)

}

其中graph.Root默认为“/var/lib/docker/graph”。


走读思路及图中流程图均来自于:http://www.infoq.com/cn/articles/docker-source-code-analysis-part1/


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。