1.3.2 Docker Daemon
Docker Daemon是Docker架构中一个常驻在后台的系统进程。所谓的“运行Docker”,即代表运行Docker Daemon。总之,DockerDaemon的作用主要有以下两方面:
□ 接收并处理Docker Client发送的请求。
□ 管理所有的Docker容器。
Docker Daemon运行时,会在后台启动一个Server,Server负责接收Docker Client发送的请求;接收请求后,Server通过路由与分发调度,找到相应的Handler来处理请求。
启动Docker Daemon所使用的可执行文件同样是docker,与Docker Client启动所使用的可执行文件docker相同。既然Docker Client与Docker Daemon都可以通过docker二进制文件创建,那么如何辨别两者就变得非常重要。实际上,执行docker命令时,通过传入的参数可以辨别Docker Daemon与Docker Client,如docker–d代表Docker Daemon的启动,dockerps则代表创建Docker Client,并发送ps请求。
Docker Daemon的架构大致可以分为三部分:Docker Server、Engine和Job。Daemon的架构如图1-2所示。
1.Docker Server
Docker Server在Docker架构中专门服务于Docker Client,它的功能是接收并调度分发Docker Client发送的请求。Docker Server的架构如图1-3所示。
图1-2 Docker Daemon架构示意图
图1-3 Docker Server架构示意图
在Docker Daemon的启动过程中,DockerServer第一个完成。Docker Server通过包gorilla/mux,创建了一个mux.Router路由器,提供请求的路由功能。在Go语言中,gorilla/mux是一个强大的URL路由器以及调度分发器。创建路由器之后,Docker Server为mux.Router中添加有效的路由项,每一个路由项由HTTP请求方法(PUT、POST、GET或DELETE)、URL和Handler三部分组成。
由于Docker Client通过HTTP协议访问Docker Daemon,故DockerServer创建完mux.Router之后,将Server的监听地址以及mux.Router作为参数,创建一个httpSrv=http.Server{},最终执行httpSrv.Serve()开始服务于外部请求。
在服务过程中,Docker Server在listener上接收Docker Client的访问请求。对于每一个Docker Client请求,DockerServer均会创建一个全新的goroutine来服务。在goroutine中,Docker Server首先读取请求内容,然后做请求解析工作,接着匹配相应的路由项,随后调用相应的Handler来处理,最后Handler处理完请求之后给Docker Client回复响应。
需要注意的是:Docker Server在Docker的启动过程中运行,通过一个名为“serveapi”的Job来实现。理论上,Docker Server的运行只是众多Job中的一个,但是为了强调Docker Server的重要性以及它为后续Job服务的重要特性,本书将“serveapi”的Job单独抽离出来分析,理解为Docker Server。
2.Engine
Engine是Docker架构中的运行引擎,同时也是Docker运行的核心模块。Engine存储着大量的容器信息,同时管理着Docker大部分Job的执行。换言之,Docker中大部分任务的执行都需要Engine协助,并通过Engine匹配相应的Job完成Job的执行。
在Docker源码中,有关Engine的数据结构定义中含有一个名为handlers的对象。该handlers对象存储的是关于众多特定Job各自的处理方式handler。举例说明,Engine的handlers对象中有一项为:{"create":daemon.ContainerCreate,},则说明当执行名为“create”的Job时,执行的是daemon.ContainerCreate这个handler。
除了容器管理之外,Engine还接管Docker Daemon的某些特定任务。当Docker Daemon遭遇到自身进程需要退出的情况时,Engine还负责完成DockerDaemon退出前的所有善后工作。
3.Job
Job可以认为是Docker架构中Engine内部最基本的工作执行单元。DockerDaemon可以完成的每一项工作都会呈现为一个Job。例如,在Docker容器内部运行一个进程,这是一个Job;创建一个新的容器,这是一个Job;在网络上下载一个文档,这是一个Job;包括之前在Docker Server部分谈及的,创建Server服务于HTTP协议的API,这也是一个Job,等等。
有关Job接口的设计,与UNIX进程非常相仿。比如说,Job有一个名称,有运行时参数,有环境变量,有标准输入与标准输出,有标准错误,还有返回状态等。
对于Job而言,定义完毕之后,运行才能完成Job自身真正的使命。Job的运行函数Run()则用以执行Job本身。