docker 服务端的代码,已经被单独划分为一个仓库:moby。此文将分析dockerd在启动过程中的重要步骤。为了节省篇幅,突出显示重要部分,在源码的截图中将以…代替次要的逻辑,仅保留重要的信息。
入口函数在/root/go/src/github.com/docker/docker/cmd/dockerd/docker.go
中。
在main() 函数中,除基本的设置外,包含两个重要的函数,分别为newDaemonCommand()和Execute()。newDaemonCommand()返回一个dockerd命令,Execute()则负责执行这个命令。
newDaemonCommand() 与 docker-cli中设置命令的方法类似,其中最主要的还是runE字段的对应的函数,后面在执行的过程中,会调用该函数。
除了runE字段外,还包括一些flag参数设置,这些都不涉及重要的流程。
Execute() 内部直接调用了ExecuteC()
ExecuteC()中先通过Find() 获取解析出实际的命令,此处如果是没有子命令的场景,其实c 和 cmd变量是同样的内容。相当于没有变化。具体的Find()执行过程,与docker-cli项目中的Find()是一样的,详情可以移步至:https://zhh0427.blog.csdn.net/article/details/127796562
获取到cmd之后,通过execute(), 执行该命令,
execute()中主要的就是RunE()的调用,当然还包括一些其他的前置和后置处理,这里就不赘述了。
此处调用的RunE,即是在newDaemonCommand()中设置的RunE字段,可以发现RunE中的runDaemon()其实才是真正开始运行服务,之前的都是为dockerd这个命令做的配置,都是铺垫此处的。
runDaemon()的整体逻辑也比较简单,就是先实例化一个DaemonCli(), 接着开启该Cli。
start()包含对DaemonCli的配置、初始化、以及对操作系统的适配、基础环境的创建、swarm、集群模式的配置等等一系列的操作,这里很难一一讲清楚。不过,其中与日常单机使用最相关的,当属apiServer部分的逻辑。
这部分逻辑中,主要分为三个步骤:1. 先创建apiServerConfig 配置 2. 根据配置初始化路由,即各个api对应函数 3.路由初始化完成后,通过开启协程开始监听客户端的发来的请求。其中配置部分的逻辑比较简单,更重要的是initRouter()和cli.api.Wait()。
在initRouter()中,可以发现通过NewRouter()各个模块的实例化路由的动作,包括 容器相关、镜像相关、挂载卷相关的等等。
在实例化完成后,最终调用InitRouter()对所有的路由作进一步处理。
这里仅对container.NewRouter()做分析,其他的模块都是相似的逻辑。NewRouter()中直接调用了initRoutes()。而initRoutes()中包含了大量的url注册,其中值得注意的是/containers/json
, 不知道是否还记得,docker ps -a
最终发起的http请求的路径,没错就是该路由(如果不清楚,请移步至:https://zhh0427.blog.csdn.net/article/details/127796562)。
由此可见,执行docker ps -a
获得的数据,其实是通过此处返回的,getContainersByName()具体的执行逻辑,这里就不赘述了,请自行查阅。
在InitRouter()中,似乎是仅仅将各个路由赋值给了s.routers,其他啥也没干,其实不然,请记住这里的s.routers。
在Wait()中,通过serverAPI()开启对各个路由的监听。
在serverAPI()中,通过createMux() 初始化服务器使用的各个路由,然后通过Server开启监听。
在createMux()中,可以发现前面被赋值的s.routers 在这里发挥了作用。通过遍历每一个routers中的所有route,并为其注册路由。
最终在Server() 中,监听路由,等待客户端发来的请求,并分发到对应的处理器中。
至此,整个docker-server的启动主要过程就结束了。除了上述这些我认为重要的步骤,还有很多很多其他的步骤。很难在短时间内完全搞清楚,也很难一一讲清楚,具体的实现细节,还需要读者自行去查阅源代码。这里提供一个搭建docker源码阅读环境的教程:https://zhh0427.blog.csdn.net/article/details/127797712。
如果上述有错误的地方,还请评论区指正,谢谢!