本篇的主要内容是关于docker daemon
的启动流程。其主要内容均包含在
github.com/docker/docker/docker/daemon.go
文件中的mainDaemon
函数中,本文即按
其执行流程分析源码。因为所涉源码较多,所以所涉部分多是点到为止,详细分析会在后续
分专篇讲述。
Engine
在mainDaemon
的开始处,在确认参数解析无误后,首先便生成了一个Engine
的实例:
1
2
|
eng := engine.New()
signal.Trap(eng.Shutdown)
|
Engine
可以说是docker
的核心。它用来执行docker
的各种操作(统一为job
的形式),
管理container
的存储。其结构定义为 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
type Engine struct {
handlers map[string]Handler
catchall Handler
hack Hack // data for temporary hackery (see hack.go)
id string
Stdout io.Writer
Stderr io.Writer
Stdin io.Reader
Logging bool
tasks sync.WaitGroup
l sync.RWMutex // lock for shutdown
shutdown bool
onShutdown []func() // shutdown handlers
}
|
大部分均可见名知意,下面对部分字段进行详细解析。
Handler
Engine
结构体中中最关键的便是handlers
映射表,docker daemon
启动时会向其中注册各种功
能的handler
,比如关于网络设置的、web server
、版本等等,然后就可以通过名字调用进行
初始化:
1
|
type Handler func(*Job) Status
|
各个模块在初始化时只要设置好相应环境变量并注册一个job
即可。统一的函数接口能够
让docker
内部各组件在代码结构和执行流程上更加清晰一致。
Job
job
是Engine
最为基本的执行单元。所有的docker
操作,比如启动一个
container
,在container
内部执行一个程序,从网络pull
一个镜像等等,都可以用
job
来表示。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
type Job struct {
Eng *Engine
Name string
Args []string
env *Env
Stdout *Output
Stderr *Output
Stdin *Input
handler Handler
status Status
end time.Time
closeIO bool
}
|
1
2
3
4
5
|
const (
StatusOK Status = 0
StatusErr Status = 1
StatusNotFound Status = 127
)
|
从结构体的定义来看,job
与unix
上的进程的结构表示非常类似:名字、参数、环境变
量、标准输入输出、退出状态(0 表示成功,其他表示错误)……我们完全可以将其当作像进程一样的概念
来看待。
Initializes
New
函数用来初始化一个Engine
,基本上只是对各变量进行简单的初始化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
func New() *Engine {
eng := &Engine{
handlers: make(map[string]Handler),
id: utils.RandomString(),
Stdout: os.Stdout,
Stderr: os.Stderr,
Stdin: os.Stdin,
Logging: true,
}
eng.Register("commands", func(job *Job) Status {
for _, name := range eng.commands() {
job.Printf("%s\n", name)
}
return StatusOK
})
// Copy existing global handlers
for k, v := range globalHandlers {
eng.handlers[k] = v
}
return eng
}
|
注意点:
Engine id
是一个随机字符串
- 注册了一个
commands
的handler
,用来返回Engine
所支持的commands
(handlers
表中的key
)列表。
- 如果已经有预定义好的
globalHandlers
,也添加到Engine
的handlers
表中.
Shutdown
Engine
关闭的流程大概如下:
- 不再接受新的执行
job
的请求
- 等待所有正在执行中的
job
结束
- 并发调用已经注册的各个
shutdown handlers
- 所有
handlers
结束或者等待 15 秒后返回
具体可参考github.com/docker/docker/engine/engine.go#Shutdown()
。
上面mainDaemon
中Trap
的设置可以让Engine
像大多数unix
程序一样在接收到信号时做
一些指定的操作:
SIGINT
或者 SIGTERM
, 直接调用eng.Shutdown
,然后程序结束
SIGINT
或者 SIGTERM
在eng.Shutdown
执行完成之前重复了 3 次,那么就直接停止执行并且直接结束程序
- 如果
DEBUG
环境变量被设置,SIGQUIT
会直接让程序退出而不调用eng.Shutdown
Builtins
1
2
3
|
if err := builtins.Register(eng); err != nil {
log.Fatal(err)
}
|
builtins
包主要用来给Engine
注册一些内部使用的handlers
: 网络设置、
apiserver
、Events
设置和version
信息 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func Register(eng *engine.Engine) error {
if err := daemon(eng); err != nil {
return err
}
if err := remote(eng); err != nil {
return err
}
if err := events.New().Install(eng); err != nil {
return err
}
if err := eng.Register("version", dockerVersion); err != nil {
return err
}
return nil
}
|
因为只是注册,具体的执行还在后面,所以这里暂时不深入探讨各个handler
的详细内容,
等分析到实际执行的时候再结合运行时信息详细探讨,理解起来应该更容易一些。这里只列
出注册的handlers
映射信息:
Name |
Handler |
init_networkdriver |
bridge.InitDriver |
serveapi |
apiserver.ServeApi |
acceptconnections |
apiserver.AcceptConnections |
version |
dockerVersion |
Version
因为dockerVersion
的实现比较简单,所以就直接写在了
github.com/docker/docker/builtins/builtins.go
里面:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func dockerVersion(job *engine.Job) engine.Status {
v := &engine.Env{}
v.SetJson("Version", dockerversion.VERSION)
v.SetJson("ApiVersion", api.APIVERSION)
v.SetJson("GitCommit", dockerversion.GITCOMMIT)
v.Set("GoVersion", runtime.Version())
v.Set("Os", runtime.GOOS)
v.Set("Arch", runtime.GOARCH)
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
v.Set("KernelVersion", kernelVersion.String())
}
if _, err := v.WriteTo(job.Stdout); err != nil {
return job.Error(err)
}
return engine.StatusOK
}
|
我们可以直接通过执行docker version
命令来查看其大概效果:

Events
我们可以先通过docker events
命令来看看docker
中Events
是干嘛用的。如图,启动一个
container
:

在另一个窗口的docker events
命令显示结果:

可见Events
是类似于 log 的一种东西,不过是一种结构化的记录方式,而且只记录特定的
运行时信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
const eventsLimit = 64
type listener chan<- *utils.JSONMessage
type Events struct {
mu sync.RWMutex
events []*utils.JSONMessage
subscribers []listener
}
func New() *Events {
return &Events{
events: make([]*utils.JSONMessage, 0, eventsLimit),
}
}
|
而前面提到的events.New().Install(eng)
也是向Engine
注册了一些handlers
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func (e *Events) Install(eng *engine.Engine) error {
// Here you should describe public interface
jobs := map[string]engine.Handler{
"events": e.Get,
"log": e.Log,
"subscribers_count": e.SubscribersCount,
}
for name, job := range jobs {
if err := eng.Register(name, job); err != nil {
return err
}
}
return nil
}
|
具体的函数实现则不再赘述。
Registry
1
2
3
|
if err := registry.NewService(daemonCfg.InsecureRegistries).Install(eng); err != nil {
log.Fatal(err)
}
|
registry
主要是给Engine
提供认证和搜索官方(dockerhub
)镜像的能力:
1
2
3
4
5
|
func (s *Service) Install(eng *engine.Engine) error {
eng.Register("auth", s.Auth)
eng.Register("search", s.Search)
return nil
}
|
如代码所示,Registry
注册了auth
和search
两个handler
。
Daemon
经过前面的那么多设置,Engine
算是配置的差不多了,下面就是对daemon
进行各项配置
的时候了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
go func() {
d, err := daemon.NewDaemon(daemonCfg, eng)
if err != nil {
log.Fatal(err)
}
log.Infof("docker daemon: %s %s; execdriver: %s; graphdriver: %s",
dockerversion.VERSION,
dockerversion.GITCOMMIT,
d.ExecutionDriver().Name(),
d.GraphDriver().String(),
)
if err := d.Install(eng); err != nil {
log.Fatal(err)
}
b := &builder.BuilderJob{eng, d}
b.Install()
if err := eng.Job("acceptconnections").Run(); err != nil {
log.Fatal(err)
}
}()
|
主要内容如下:
-
daemon
各个模块的设置,创建daemon
。这部分内容非常长,下面将详述。
-
打印一些关键日志信息。如下图所示:

-
向Engine
注册daemon
所提供的各种handlers
,主要就是docker client
各种命令
的后台实现:


-
docker build
的后台handler
实现。因为这个命令实现比较复杂,所以单列。
-
在daemon
设置完成后即启动api server
准备接受请求。
Config
Config
定义了docker daemon
的各项配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
type Config struct {
Pidfile string
Root string
AutoRestart bool
Dns []string
DnsSearch []string
Mirrors []string
EnableIptables bool
EnableIpForward bool
EnableIpMasq bool
DefaultIp net.IP
BridgeIface string
BridgeIP string
FixedCIDR string
InsecureRegistries []string
InterContainerCommunication bool
GraphDriver string
GraphOptions []string
ExecDriver string
Mtu int
DisableNetwork bool
EnableSelinuxSupport bool
Context map[string][]string
TrustKeyPath string
Labels []string
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
type Daemon struct {
ID string
repository string
sysInitPath string
containers *contStore
execCommands *execStore
graph *graph.Graph
repositories *graph.TagStore
idIndex *truncindex.TruncIndex
sysInfo *sysinfo.SysInfo
volumes *volumes.Repository
eng *engine.Engine
config *Config
containerGraph *graphdb.Database
driver graphdriver.Driver
execDriver execdriver.Driver
trustStore *trust.TrustStore
}
|
从这些配置项也可以看出,很多都是与docker
启动时的参数一一对应的。NewDaemon
函
数即通过这些参数来进行daemon
的各项设置:
Settings
从上面Config
和Daemon
的定义也可以看出,二者包含了docker
运行时需要关注
的绝大部分内容及组件。而具体的设置由
github.com/docker/docker/daemon/daemon.go#NewDaemonFromDirectory
完成,因为比较
琐碎,所以将其归为以下几类介绍:
network args
因为网络参数比较多,有的还有冲突,所有还要进行一定的检测。关于网络的设置主要由以
下几项:
-
MTU
,容器网络的最大传输单元。未指定则使用默认值: 1500。如果网络环境的自定义程
度较高,则MTU
需要小心设置,不然可能因为额外的封包解包过程导致包大小超过MTU
而
被丢弃。
-
--bridge
和 --bip
参数不能同时指定。因为bridge
是用来创建自定义的
bridge
网络,而--bip
是用来给默认的docker0
指定其他地址和掩码的。
-
--iptables=false
和 --icc=false
不能同时指定。因为ICC
依赖于iptables
…
system
1
2
3
4
5
6
7
8
9
|
if config.Pidfile != "" {
if err := utils.CreatePidFile(config.Pidfile); err != nil {
return nil, err
}
eng.OnShutdown(func() {
// Always release the pidfile last, just in case
utils.RemovePidFile(config.Pidfile)
})
}
|
使用pid
文件可以说是linux
上大多数daemon
服务的一种通用模式了: 没有则创建,
并且在程序退出时删除(通过shutdown handler
来处理)。
- 操作系统及内核版本检测,要求 linux 3.8 以上的 kernel.
1
2
3
4
5
6
7
|
if runtime.GOOS != "linux" {
return nil, fmt.Errorf("The Docker daemon is only supported on linux")
}
if err := checkKernelAndArch(); err != nil {
return nil, err
}
|
1
2
3
|
if os.Geteuid() != 0 {
return nil, fmt.Errorf("The Docker daemon needs to be run as root")
}
|
这里的TempDir
是相对于docker
的目录而言的,并不是指系统的/tmp
目录。从参数设置
上可以看到默认的根目录为/var/lib/docker
:
1
|
flag.StringVar(&config.Root, []string{"g", "-graph"}, "/var/lib/docker", "Path to use as the root of the Docker runtime")
|
如果使用默认值,则TempDir
为/var/lib/docker/tmp
:
1
2
3
4
5
6
7
8
|
func TempDir(rootDir string) (string, error) {
var tmpDir string
if tmpDir = os.Getenv("DOCKER_TMPDIR"); tmpDir == "" {
tmpDir = filepath.Join(rootDir, "tmp")
}
err := os.MkdirAll(tmpDir, 0700)
return tmpDir, err
}
|
检测是否开启SELinux
支持。SELinux
和Apparmor
是 docker 支持的两种安全机制,SELinux
功能强大,架构也比较复
杂,AppArmor
则相反。
1
2
3
|
if !config.EnableSelinuxSupport {
selinuxSetDisabled()
}
|
Docker
所有文件存储的根目录,默认为/var/lib/docker
。
graphdriver
graph driver
是主要用来管理容器文件系统及镜像存储的组件,与宿主机对各文件系统的支持
相关。比如ubuntu
上默认使用的是AUFS
,Centos
上是devicemapper
,Coreos
上则是btrfs
。
graph driver
定义了一个统一的、抽象的接口,以一种可扩展的方式对各文件系统提供了支持。
1
2
3
4
5
6
7
8
9
10
11
|
// Set the default driver
graphdriver.DefaultDriver = config.GraphDriver
// Load storage driver
driver, err := graphdriver.New(config.Root, config.GraphOptions)
if err != nil {
return nil, err
}
log.Debugf("Using graph driver %s", driver)
|
因为config.GraphDriver
并没有设置(没有供用户指定的参数选项),所以graphDriver
会从其支持的文件系统列表中
一个一个检测系统是否支持,找到一个支持的即设为要用的 driver :
1
2
3
4
5
6
7
8
9
10
|
for _, name := range priority {
driver, err = GetDriver(name, root, options)
if err != nil {
if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS {
continue
}
return nil, err
}
return driver, nil
}
|
priority
列表为:
1
2
3
4
5
6
7
8
|
priority = []string{
"aufs",
"btrfs",
"devicemapper",
"vfs",
// experimental, has to be enabled manually for now
"overlay",
}
|
如果使用的是btrfs
,因为其与SELinux
的不兼容,所以还要进行一些检测:
1
2
3
|
if selinuxEnabled() && config.EnableSelinuxSupport && driver.String() == "btrfs" {
return nil, fmt.Errorf("SELinux is not supported with the BTRFS graph driver!")
}
|
之后检测/var/lib/docker/containers
目录是否存在,不存在则创建。我们来看看
containers
目录下的内容:

每个container
创建的时候,与网络有关的配置文件
(/etc/hosts
,/etc/resolv.conf
等)与其他文件的处理是不同的,他们是通过挂载的方式
供container
使用的,有点类似于docker container
本身的存储方式: 一个只读的层,
加上一些可写的层。containers
目录就是用来存储这些信息的。
graph
1
2
3
4
|
g, err := graph.NewGraph(path.Join(config.Root, "graph"), driver)
if err != nil {
return nil, err
}
|
Graph
是用来存储标记的文件系统镜像以及他们之间的关系的组件:
1
2
3
4
5
|
type Graph struct {
Root string
idIndex *truncindex.TruncIndex
driver graphdriver.Driver
}
|
其中idIndex
的作用是使我们可以使用长 id 的前缀来检索镜像,Root
为Graph
的根目录,一般为/var/lib/docker/graph
。NewGraph
即是用此目
录下的文件来重建镜像索引。我们可以查看此目录下的目录的结构:

每一个镜像一个目录,下面包含一个描述镜像信息的 json 文件,也包含了记录镜像大小的
layersize
文件。我们用一些实例来对比查看一下,下图是docker images --tree
的部分
结果:

我们选取487e08
镜像来对照,json 文件记录了其parent image
的 id、创建时间、大小等
等信息。这个大小与 layersize 文件中的相一致。


volumes
Volumes
是一种特殊的目录,其数据可以被一个或多个container
共享,它和创建它的
container
的生命周期分离开来,在container
被删去之后能继续存在。在实现上,使用
的依然是只读层和读写层结合(union file system
)的方式。
1
2
3
4
5
6
7
8
9
|
volumesDriver, err := graphdriver.GetDriver("vfs", config.Root, config.GraphOptions)
if err != nil {
return nil, err
}
volumes, err := volumes.NewRepository(path.Join(config.Root, "volumes"), volumesDriver)
if err != nil {
return nil, err
}
|
VFS
是一个中间层,下面是个各种文件系统实现,对外提供的则是统一的访问接口,这非常
类似我们之前提到的GraphDriver
的机制。刚开始看这段代码很难知道它是干嘛用的,但我们还可以
仿照之前Graph
部分先对/var/lib/docker/volumes
目录进行一番探究。
我们先用官方的例子创建一个包含Volumes
的container
:

然后通过docker inspect
查看与Volumes
相关的信息:

到获取到的目录去看下:

里面什么也没有。我们进到 container 内部在/webapp
目录下创建一个文件看看:

可以确定,/var/lib/docker/vfs
目录下的目录是用来存储Volumes
中实际数据的。我们
再来看看/var/lib/docker/volumes
目录下的内容:

可以看到,这个目录只用来存储关于Volumes
的关键信息的。
明白了这些之后,就会发现上面的代码和之前的与Graph
有关的代码是非常类似的 : 初始
化driver
,然后从相应目录里读取原有的关于image
或者container
的信息并加载。
repository
1
2
3
4
|
repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, config.Mirrors, config.InsecureRegistries)
if err != nil {
return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
}
|
我们依然先来查看下相关的文件: /var/lib/docker/repositories-aufs
:

整个 json 文件记录了所有的镜像的不同的 tag 及其对应的 id.从函数名NewTagStore
上也可
以看出,tag
信息的记录是其主要功能之一。
1
2
3
4
5
6
7
8
9
10
11
12
|
type TagStore struct {
path string
graph *Graph
mirrors []string
insecureRegistries []string
Repositories map[string]Repository
sync.Mutex
// FIXME: move push/pull-related fields
// to a helper type
pullingPool map[string]chan struct{}
pushingPool map[string]chan struct{}
}
|
pullingPool
记录有哪些镜像正在被下载,若某一个镜像正在被下载,则驳回其他Docker Client
发起下载该镜像的请求。pushingPool
记录有哪些镜像正在被上传,若某一个镜像
正在被上传,则驳回其他Docker Client
发起上传该镜像的请求;
trust
1
2
3
4
5
6
7
8
|
trustDir := path.Join(config.Root, "trust")
if err := os.MkdirAll(trustDir, 0700); err != nil && !os.IsExist(err) {
return nil, err
}
t, err := trust.NewTrustStore(trustDir)
if err != nil {
return nil, fmt.Errorf("could not create trust store: %s", err)
}
|
还是先看/var/lib/docker/trust
下的内容:

跟认证签名有关的一些信息。这个文件是从下面这个地方获取到的:
1
|
var baseEndpoints = map[string]string{"official": "https://dvjy3tqbc323p.cloudfront.net/trust/official.json"}
|
然后用其中的内容来初始化TrustStore
:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
type TrustStore struct {
path string
caPool *x509.CertPool
graph trustgraph.TrustGraph
expiration time.Time
fetcher *time.Timer
fetchTime time.Duration
autofetch bool
httpClient *http.Client
baseEndpoints map[string]*url.URL
sync.RWMutex
}
|
init_networkdriver
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
if !config.DisableNetwork {
job := eng.Job("init_networkdriver")
job.SetenvBool("EnableIptables", config.EnableIptables)
job.SetenvBool("InterContainerCommunication", config.InterContainerCommunication)
job.SetenvBool("EnableIpForward", config.EnableIpForward)
job.SetenvBool("EnableIpMasq", config.EnableIpMasq)
job.Setenv("BridgeIface", config.BridgeIface)
job.Setenv("BridgeIP", config.BridgeIP)
job.Setenv("FixedCIDR", config.FixedCIDR)
job.Setenv("DefaultBindingIP", config.DefaultIp.String())
if err := job.Run(); err != nil {
return nil, err
}
}
|
前面提到在Builtins
里注册了这个handler
,这里就利用启动参数进行了相关环境变
量的设置并真正开始启动这个 Job。主要内容如下:
bridge
及其 ip 设置,一般都是使用默认的docker0
。

-
iptables
及ipforward
设置。
-
fixed cidr
设置。这个可以用来限制contaienr
从docker0
获取到的ip
地址的范
围。
-
注册了一些供以后进行各个容器的网络设置的handlers
:
1
2
3
4
5
6
7
8
9
10
11
|
for name, f := range map[string]engine.Handler{
"allocate_interface": Allocate,
"release_interface": Release,
"allocate_port": AllocatePort,
"link": LinkContainers,
} {
if err := job.Eng.Register(name, f); err != nil {
return job.Error(err)
}
}
|
linkgraph.db
1
2
3
4
5
6
|
graphdbPath := path.Join(config.Root, "linkgraph.db")
graph, err := graphdb.NewSqliteConn(graphdbPath)
if err != nil {
return nil, err
}
|
/var/lib/docker/linkgraph.db
是一个 SQLITE3 的数据库文件。里面有两个表: edge
和
entity
(两个图理论中常用的概念)。查看其内容:


edge
里存储了容器的名字和 id,entity
只存储了容器的 id。daemon
通过这个数据库来重
建容器名称与 id 的关联。
execdriver
1
2
3
4
5
|
sysInfo := sysinfo.New(false)
ed, err := execdrivers.NewDriver(config.ExecDriver, config.Root, sysInitPath, sysInfo)
if err != nil {
return nil, err
}
|
docker
最开始使用的是linux
的lxc
作为其底层的容器执行引擎,后来自己开发了
libcontainer
,用来替代lxc
,所以我们现在看到docker info
里显示的Excution Driver
是native
:

sysinfo
是cgroup
相关的一些系统信息,lxc exec driver
初始化时需要从其中获取关于系统中apparmor
的一些信息,但native exec driver
不需要。
1
2
3
4
5
6
|
type SysInfo struct {
MemoryLimit bool
SwapLimit bool
IPv4ForwardingDisabled bool
AppArmor bool
}
|
1
2
3
4
5
6
7
8
9
|
func NewDriver(name, root, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
switch name {
case "lxc":
return lxc.NewDriver(root, initPath, sysInfo.AppArmor)
case "native":
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath)
}
return nil, fmt.Errorf("unknown exec driver %s", name)
}
|
看看代码中提到的目录/var/lib/docker/execdriver/native
:

又是一堆container
或者镜像的id
,既然是执行引擎了,多半是关于container
的一些
运行时信息,挑一个进去查看一下:

state.json
主要描述了此container
所在cgroup
的相关目录,网络状态,以及主进程的
pid
及启动时间。container.json
包含信息较多,部分截图如下:

- 各个设备的访问权限,主要是
/dev
下面那些
- 一些特殊文件的信息。比如
/etc/hosts
,Volumes
,/etc/resolv.conf
等等
- 网络详细信息
- capabilites
- namespaces
- 环境变量
Restore
经过前面各个组件的设置及初始化,终于到了daemon
的创建了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
daemon := &Daemon{
ID: trustKey.PublicKey().KeyID(),
repository: daemonRepo,
containers: &contStore{s: make(map[string]*Container)},
execCommands: newExecStore(),
graph: g,
repositories: repositories,
idIndex: truncindex.NewTruncIndex([]string{}),
sysInfo: sysInfo,
volumes: volumes,
config: config,
containerGraph: graph,
driver: driver,
sysInitPath: sysInitPath,
execDriver: ed,
eng: eng,
trustStore: t,
}
if err := daemon.restore(); err != nil {
return nil, err
}
|
基本上用到了我们前面设置好的各个组件。之后的restore
便开始加载原有的container
,
将设为自启动的container
启动。
Shutdown
前面提到过Engine
在关闭时会调用各个注册好的handlers
,这里便是一个:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
eng.OnShutdown(func() {
if err := daemon.shutdown(); err != nil {
log.Errorf("daemon.shutdown(): %s", err)
}
if err := portallocator.ReleaseAll(); err != nil {
log.Errorf("portallocator.ReleaseAll(): %s", err)
}
if err := daemon.driver.Cleanup(); err != nil {
log.Errorf("daemon.driver.Cleanup(): %s", err.Error())
}
if err := daemon.containerGraph.Close(); err != nil {
log.Errorf("daemon.containerGraph.Close(): %s", err.Error())
}
})
|
主要进行daemon
自身的清理工作,端口的释放,挂载点的卸载,与graphdb
连接的关闭。
ServeApi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
job := eng.Job("serveapi", flHosts...)
job.SetenvBool("Logging", true)
job.SetenvBool("EnableCors", *flEnableCors)
job.Setenv("Version", dockerversion.VERSION)
job.Setenv("SocketGroup", *flSocketGroup)
job.SetenvBool("Tls", *flTls)
job.SetenvBool("TlsVerify", *flTlsVerify)
job.Setenv("TlsCa", *flCa)
job.Setenv("TlsCert", *flCert)
job.Setenv("TlsKey", *flKey)
job.SetenvBool("BufferRequests", true)
if err := job.Run(); err != nil {
log.Fatal(err)
}
|
查看之前在Builtins
中注册的handlers
表,可知serveapi
对应的是
apiserver.ServeApi
函数。ServeApi
即开始监听参数中指定的各种协议和端口,并准备
开始处理http
请求了(docker client
与 daemon
的交互都是通过REST API
来进行
的)。
参考链接
- VFS
- How Docker container volumes work even when they aren’t running?
- Advanced Docker Volumes
- Network Configuration
- Docker 源码分析(四):Docker Daemon 之 NewDaemon 实现