Docker的生态系统日趋完善,开发者群体也在日趋庞大,这让业界对Docker持续抱有极其乐观的态度。现在,对于广大开发者而言,使用Docker这项技术已然不是门槛,享受Docker带来的技术福利也再也不是困难。然而,如何探寻Docker适应的场景,如何发展Docker周边的技术,以及如何弥合Docker新技术与传统物理机或VM技术的鸿沟,已经占据Docker研究者们的思考与实践。html
本文为《Docker源码分析》第四篇——Docker Daemon之NewDaemon实现,力求帮助广大Docker爱好者更多得理解Docker 的核心——Docker Daemon的实现。linux
在Docker架构中有不少重要的概念,如:graph,graphdriver,execdriver,networkdriver,volumes,Docker containers等。Docker在实现过程当中,须要将以上实体进行统一化管理,而Docker Daemon中的daemon实例就是设计用来完成这一任务的实体。git
从源码的角度,NewDaemon函数的执行完成了Docker Daemon建立并加载daemon的任务,最终实现统一管理Docker Daemon的资源。github
本文从源码角度,分析Docker Daemon加载过程当中NewDaemon的实现,整个分析过程以下图:golang
图3.1 Docker Daemon中NewDaemon执行流程图sql
由上图可见,Docker Daemon中NewDaemon的执行流程主要包含12个独立的步骤:处理配置信息、检测系统支持及用户权限、配置工做路径、加载并配置graphdriver、建立Docker Daemon网络环境、建立并初始化graphdb、建立execdriver、建立daemon实例、检测DNS配置、加载已有container、设置shutdown处理方法、以及返回daemon实例。docker
下文会在NewDaemon的具体实现中,以12节分别分析以上内容。数据库
在《Docker源码分析》系列第三篇中,有一个重要的环节:使用goroutine加载daemon对象并运行。在加载并运行daemon对象时,所作的第一个工做即为:数组
d, err := daemon.NewDaemon(daemonCfg, eng)
该部分代码分析以下:安全
进入./docker/daemon/daemon.go中NewDaemon的具体实现,代码以下:
func NewDaemon(config *Config, eng *engine.Engine) (*Daemon, error) { daemon, err := NewDaemonFromDirectory(config, eng) if err != nil { return nil, err } return daemon, nil }
可见,在实现NewDaemon的过程当中,经过NewDaemonFromDirectory函数来实现建立Daemon的运行环境。该函数的实现,传入参数以及返回类型与NewDaemon函数相同。下文将大篇幅分析NewDaemonFromDirectory的实现细节。
在NewDaemonFromDirectory的实现过程当中,第一个工做是:如何应用传入的配置信息。这部分配置信息服务于Docker Daemon的运行,并在Docker Daemon启动初期就初始化完毕。配置信息的主要功能是:供用户自由配置Docker的可选功能,使得Docker的运行更贴近用户期待的运行场景。
配置信息的处理包含4部分:
config信息中的Mtu应用于容器网络的最大传输单元(MTU)特性。有关MTU的源码以下:
if config.Mtu == 0 { config.Mtu = GetDefaultNetworkMtu()
可见,若config信息中Mtu的值为0的话,则经过GetDefaultNetworkMtu函数将Mtu设定为默认的值;不然,采用config中的Mtu值。因为在默认的配置文件./docker/daemon/config.go(下文简称为默认配置文件)中,初始化时Mtu属性值为0,故执行GetDefaultNetworkMtu。
GetDefaultNetworkMtu函数的具体实现位于./docker/daemon/config.go:
func GetDefaultNetworkMtu() int { if iface, err := networkdriver.GetDefaultRouteIface(); err == nil { return iface.MTU } return defaultNetworkMtu }
GetDefaultNetworkMtu的实现中,经过networkdriver包的GetDefaultRouteIface方法获取具体的网络设备,若该网络设备存在,则返回该网络设备的MTU属性值;不然的话,返回默认的MTU值defaultNetworkMtu,值为1500。
处理完config中的Mtu属性以后,立刻检测config中BridgeIface和BridgeIP这两个信息。BridgeIface和BridgeIP的做用是为建立网桥的任务”init_networkdriver”提供参数。代码以下:
if config.BridgeIface != "" && config.BridgeIP != "" { return nil, fmt.Errorf("You specified -b & --bip, mutually exclusive options. Please specify only one.") }
以上代码的含义为:若config中BridgeIface和BridgeIP两个属性均不为空,则返回nil对象,并返回错误信息,错误信息内容为:用户同时指定了BridgeIface和BridgeIP,这两个属性属于互斥类型,只能至多指定其中之一。而在默认配置文件中,BridgeIface和BridgeIP均为空。
检测容器的通讯配置,主要是针对config中的EnableIptables和InterContainerCommunication这两个属性。EnableIptables属性的做用是启用Docker对iptables规则的添加功能;InterContainerCommunication的做用是启用Docker container之间互相通讯的功能。代码以下:
if !config.EnableIptables && !config.InterContainerCommunication { return nil, fmt.Errorf("You specified --iptables=false with --icc= false. ICC uses iptables to function. Please set --icc or --iptables to true.") }
代码含义为:若EnableIptables和InterContainerCommunication两个属性的值均为false,则返回nil对象以及错误信息。其中错误信息为:用户将以上两属性均置为false,container间通讯须要iptables的支持,需设置至少其中之一为true。而在默认配置文件中,这两个属性的值均为true。
接着,处理config中的DisableNetwork属性,以备后续在建立并执行建立Docker Daemon网络环境时使用,即在名为”init_networkdriver”的job建立并运行中体现。
config.DisableNetwork = config.BridgeIface == DisableNetworkBridge
因为config中的BridgeIface属性值为空,另外DisableNetworkBridge的值为字符串”none”,所以最终config中DisableNetwork的值为false。后续名为”init_networkdriver”的job在执行过程当中须要使用该属性。
处理PID文件配置,主要工做是:为Docker Daemon进程运行时的PID号建立一个PID文件,文件的路径即为config中的Pidfile属性。而且为Docker Daemon的shutdown操做添加一个删除该Pidfile的函数,以便在Docker Daemon退出的时候,能够在第一时间删除该Pidfile。处理PID文件配置信息的代码实现以下:
if config.Pidfile != "" { if err := utils.CreatePidFile(config.Pidfile); err != nil { return nil, err } eng.OnShutdown(func() { utils.RemovePidFile(config.Pidfile) }) }
代码执行过程当中,首先检测config中的Pidfile属性是否为空,若为空,则跳过代码块继续执行;若不为空,则首先在文件系统中建立具体的Pidfile,而后向eng的onShutdown属性添加一个处理函数,函数具体完成的工做为utils.RemovePidFile(config.Pidfile),即在Docker Daemon进行shutdown操做的时候,删除Pidfile文件。在默认配置文件中,Pidfile文件的初始值为” /var/run/docker.pid”。
以上即是关于配置信息处理的分析。
初步处理完Docker的配置信息以后,Docker对自身运行的环境进行了一系列的检测,主要包括三个方面:
系统支持与用户权限检测的实现较为简单,实现代码以下:
if runtime.GOOS != "linux" { log.Fatalf("The Docker daemon is only supported on linux") } if os.Geteuid() != 0 { log.Fatalf("The Docker daemon needs to be run as root") } if err := checkKernelAndArch(); err != nil { log.Fatalf(err.Error()) }
首先,经过runtime.GOOS,检测操做系统的类型。runtime.GOOS返回运行程序所在操做系统的类型,能够是Linux,Darwin,FreeBSD等。结合具体代码,能够发现,若操做系统不为Linux的话,将报出Fatal错误日志,内容为“Docker Daemon只能支持Linux操做系统”。
接着,经过os.Geteuid(),检测程序用户是否拥有足够权限。os.Geteuid()返回调用者所在组的group id。结合具体代码,也就是说,若返回不为0,则说明不是以root用户的身份运行,报出Fatal日志。
最后,经过checkKernelAndArch(),检测内核的版本以及主机处理器类型。checkKernelAndArch()的实现一样位于./docker/daemon/daemon.go。实现过程当中,第一个工做是:检测程序运行所在的处理器架构是否为“amd64”,而目前Docker运行时只能支持amd64的处理器架构。第二个工做是:检测Linux内核版本是否知足要求,而目前Docker Daemon运行所需的内核版本若太低,则必须升级至3.8.0。
配置Docker Daemon的工做路径,主要是建立Docker Daemon运行中所在的工做目录。实现过程当中,经过config中的Root属性来完成。在默认配置文件中,Root属性的值为”/var/lib/docker”。
在配置工做路径的代码实现中,步骤以下:
(1) 使用规范路径建立一个TempDir,路径名为tmp;
(2) 经过tmp,建立一个指向tmp的文件符号链接realTmp;
(3) 使用realTemp的值,建立并赋值给环境变量TMPDIR;
(4) 处理config的属性EnableSelinuxSupport;
(5) 将realRoot从新赋值于config.Root,并建立Docker Daemon的工做根目录。
加载并配置存储驱动graphdriver,目的在于:使得Docker Daemon建立Docker镜像管理所需的驱动环境。Graphdriver用于完成Docker容器镜像的管理,包括存储与获取。
这部份内容的源码位于./docker/daemon/daemon.go#L743-L790,具体细节分析以下:
graphdriver.DefaultDriver = config.GraphDriver driver, err := graphdriver.New(config.Root, config.GraphOptions)
首先,为graphdriver包中的DefaultDriver对象赋值,值为config中的GraphDriver属性,在默认配置文件中,GraphDriver属性的值为空;一样的,属性GraphOptions也为空。而后经过graphDriver中的new函数实现加载graph的存储驱动。
建立具体的graphdriver是至关重要的一个环节,实现细节由graphdriver包中的New函数来完成。进入./docker/daemon/graphdriver/driver.go中,实现步骤以下:
第一,遍历数组选择graphdriver,数组内容为os.Getenv(“DOCKER_DRIVER”)和DefaultDriver。若不为空,则经过GetDriver函数直接返回相应的Driver对象实例,若均为空,则继续往下执行。这部份内容的做用是:让graphdriver的加载,首先知足用户的自定义选择,而后知足默认值。代码以下:
for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} { if name != "" { return GetDriver(name, root, options) } }
第二,遍历优先级数组选择graphdriver,优先级数组的内容为依次为”aufs”,”brtfs”,”devicemapper”和”vfs”。若依次验证时,GetDriver成功,则直接返回相应的Driver对象实例,若均不成功,则继续往下执行。这部份内容的做用是:在没有指定以及默认的Driver时,从优先级数组中选择Driver,目前优先级最高的为“aufs”。代码以下:
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 }
第三,从已经注册的drivers数组中选择graphdriver。在”aufs”,”btrfs”,”devicemapper”和”vfs”四个不一样类型driver的init函数中,它们均向graphdriver的drivers数组注册了相应的初始化方法。分别位于./docker/daemon/graphdriver/aufs/aufs.go,以及其余三类driver的相应位置。这部份内容的做用是:在没有优先级drivers数组的时候,一样能够经过注册的driver来选择具体的graphdriver。
因为目前在btrfs文件系统上运行的Docker不兼容SELinux,所以当config中配置信息须要启用SELinux的支持而且driver的类型为btrfs时,返回nil对象,并报出Fatal日志。代码实现以下:
// As Docker on btrfs and SELinux are incompatible at present, error on both being enabled if config.EnableSelinuxSupport && driver.String() == "btrfs" { return nil, fmt.Errorf("SELinux is not supported with the BTRFS graph driver!") }
Docker Daemon在建立Docker容器以后,须要将容器放置于某个仓库目录下,统一管理。而这个目录即为daemonRepo,值为:/var/lib/docker/containers,并经过daemonRepo建立相应的目录。代码实现以下:
daemonRepo := path.Join(config.Root, "containers") if err := os.MkdirAll(daemonRepo, 0700); err != nil && !os.IsExist(err) { return nil, err }
当graphdriver的类型为aufs时,须要将现有graph的全部内容都迁移至aufs类型;若不为aufs,则继续往下执行。实现代码以下:
if err = migrateIfAufs(driver, config.Root); err != nil { return nil, err }
这部分的迁移内容主要包括Repositories,Images以及Containers,具体实现位于./docker/daemon/graphdriver/aufs/migrate.go。
func (a *Driver) Migrate(pth string, setupInit func(p string) error) error { if pathExists(path.Join(pth, "graph")) { if err := a.migrateRepositories(pth); err != nil { return err } if err := a.migrateImages(path.Join(pth, "graph")); err != nil { return err } return a.migrateContainers(path.Join(pth, "containers"), setupInit) } return nil }
migrate repositories的功能是:在Docker Daemon的root工做目录下建立repositories-aufs的文件,存储全部与images相关的基本信息。
migrate images的主要功能是:将原有的image镜像都迁移至aufs driver能识别并使用的类型,包括aufs所规定的layers,diff与mnt目录内容。
migrate container的主要功能是:将container内部的环境使用aufs driver来进行配置,包括,建立container内部的初始层(init layer),以及建立原先container内部的其余layers。
建立镜像graph的主要工做是:在文件系统中指定的root目录下,实例化一个全新的graph对象,做用为:存储全部标记的文件系统镜像,并记录镜像之间的关系。实现代码以下:
g, err := graph.NewGraph(path.Join(config.Root, "graph"), driver)
NewGraph的具体实现位于./docker/graph/graph.go,实现过程当中返回的对象为Graph类型,定义以下:
type Graph struct { Root string idIndex *truncindex.TruncIndex driver graphdriver.Driver }
其中Root表示graph的工做根目录,通常为”/var/lib/docker/graph”;idIndex使得检索字符串标识符时,容许使用任意一个该字符串惟一的前缀,在这里idIndex用于经过简短有效的字符串前缀检索镜像与容器的ID;最后driver表示具体的graphdriver类型。
在Docker中volume的概念是:能够从Docker宿主机上挂载到Docker容器内部的特定目录。一个volume能够被多个Docker容器挂载,从而Docker容器能够实现互相共享数据等。在实现volumes时,Docker须要使用driver来管理它,又因为volumes的管理不会像容器文件系统管理那么复杂,故Docker采用vfs驱动实现volumes的管理。代码实现以下:
volumesDriver, err := graphdriver.GetDriver("vfs", config.Root, config.GraphOptions) volumes, err := graph.NewGraph(path.Join(config.Root, "volumes"), volumesDriver)
主要完成工做为:使用vfs建立volumesDriver;建立相应的volumes目录,并返回volumes graph对象。
TagStore主要是用于存储镜像的仓库列表(repository list)。代码以下:
repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g)
NewTagStore位于./docker/graph/tags.go,TagStore的定义以下:
type TagStore struct { path string graph *Graph Repositories map[string]Repository sync.Mutex pullingPool map[string]chan struct{} pushingPool map[string]chan struct{} }
须要阐述的是TagStore类型中的多个属性的含义:
建立Docker Daemon运行环境的时候,建立网络环境是极为重要的一个部分,这不只关系着容器对外的通讯,一样也关系着容器间的通讯。
在建立网络时,Docker Daemon是经过运行名为”init_networkdriver”的job来完成的。代码以下:
if !config.DisableNetwork { job := eng.Job("init_networkdriver") job.SetenvBool("EnableIptables", config.EnableIptables) job.SetenvBool("InterContainerCommunication", config.InterContainerCommunication) job.SetenvBool("EnableIpForward", config.EnableIpForward) job.Setenv("BridgeIface", config.BridgeIface) job.Setenv("BridgeIP", config.BridgeIP) job.Setenv("DefaultBindingIP", config.DefaultIp.String()) if err := job.Run(); err != nil { return nil, err } }
分析以上源码可知,经过config中的DisableNetwork属性来判断,在默认配置文件中,该属性有过定义,却没有初始值。可是在应用配置信息中处理网络功能配置的时候,将DisableNetwork属性赋值为false,故判断语句结果为真,执行相应的代码块。
首先建立名为”init_networkdriver”的job,随后为该job设置环境变量,环境变量的值以下:
设置完环境变量以后,随即运行该job,因为在eng中key为”init_networkdriver”的handler,value为bridge.InitDriver函数,故执行bridge.InitDriver函数,具体的实现位于./docker/daemon/networkdriver/bridge/dirver.go,做用为:
建立Docker网络设备,属于Docker Daemon建立网络环境的第一步,实际工做是建立名为“docker0”的网桥设备。
在InitDriver函数运行过程当中,首先使用job的环境变量初始化内部变量;而后根据目前网络环境,判断是否建立docker0网桥,若Docker专属网桥已存在,则继续往下执行;不然的话,建立docker0网桥。具体实现为createBridge(bridgeIP),以及createBridgeIface(bridgeIface)。
createBridge的功能是:在host主机上启动建立指定名称网桥设备的任务,并为该网桥设备配置一个与其余设备不冲突的网络地址。而createBridgeIface经过系统调用负责建立具体实际的网桥设备,并设置MAC地址,经过libcontainer中netlink包的CreateBridge来实现。
建立完网桥以后,Docker Daemon为容器以及host主机配置iptables,包括为container之间所须要的link操做提供支持,为host主机上全部的对外对内流量制定传输规则等。代码位于./docker/daemon/networkdriver/bridge/driver/driver.go#L133-L137,以下:
// Configure iptables for link support if enableIPTables { if err := setupIPTables(addr, icc); err != nil { return job.Error(err) } }
其中setupIPtables的调用过程当中,addr地址为Docker网桥的网络地址,icc为true,即为容许Docker容器间互相访问。假设网桥设备名为docker0,网桥网络地址为docker0_ip,设置iptables规则,操做步骤以下:
(1) 使用iptables工具开启新建网桥的NAT功能,使用命令以下:
iptables -I POSTROUTING -t nat -s docker0_ip ! -o docker0 -j MASQUERADE
(2) 经过icc参数,决定是否容许container间通讯,并制定相应iptables的Forward链。Container之间通讯,说明数据包从container内发出后,通过docker0,而且还须要在docker0处发往docker0,最终转向指定的container。换言之,从docker0出来的数据包,若是须要继续发往docker0,则说明是container的通讯数据包。命令使用以下:
iptables -I FORWARD -i docker0 -o docker0 -j ACCEPT
(3) 容许接受从container发出,且不是发往其余container数据包。换言之,容许全部从docker0发出且不是继续发向docker0的数据包,使用命令以下:
iptables -I FORWARD -i docker0 ! -o docker0 -j ACCEPT
(4) 对于发往docker0,而且属于已经创建的链接的数据包,Docker无条件接受这些数据包,使用命令以下:
iptables -I FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
在Linux系统上,数据包转发功能是被默认禁止的。数据包转发,就是当host主机存在多块网卡的时,若是其中一块网卡接收到数据包,并须要将其转发给另外的网卡。经过修改/proc/sys/net/ipv4/ip_forward的值,将其置为1,则能够保证系统内数据包能够实现转发功能,代码以下:
if ipForward { // Enable IPv4 forwarding if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte{'1', '\n'}, 0644); err != nil { job.Logf("WARNING: unable to enable IPv4 forwarding: %s\n", err) } }
在网桥设备上建立一条名为DOCKER的链,该链的做用是在建立Docker container并设置端口映射时使用。实现代码位于./docker/daemon/networkdriver/bridge/driver/driver.go,以下:
if err := iptables.RemoveExistingChain("DOCKER"); err != nil { return job.Error(err) } if enableIPTables { chain, err := iptables.NewChain("DOCKER", bridgeIface) if err != nil { return job.Error(err) } portmapper.SetIptablesChain(chain) }
在建立完网桥,并配置完基本的iptables规则以后,Docker Daemon在网络方面还在Engine中注册了4个Handler,这些Handler的名称与做用以下:
因为在Docker架构中,网络是极其重要的一部分,所以Docker网络篇会安排在《Docker源码分析》系列的第六篇。
Graphdb是一个构建在SQLite之上的图形数据库,一般用来记录节点命名以及节点之间的关联。Docker Daemon使用graphdb来记录镜像之间的关联。建立graphdb的代码以下:
graphdbPath := path.Join(config.Root, "linkgraph.db") graph, err := graphdb.NewSqliteConn(graphdbPath) if err != nil { return nil, err }
以上代码首先肯定graphdb的目录为/var/lib/docker/linkgraph.db;随后经过graphdb包内的NewSqliteConn打开graphdb,使用的驱动为”sqlite3”,数据源的名称为” /var/lib/docker/linkgraph.db”;最后经过NewDatabase函数初始化整个graphdb,为graphdb建立entity表,edge表,并在这两个表中初始化部分数据。NewSqliteConn函数的实现位于./docker/pkg/graphdb/conn_sqlite3.go,代码实现以下:
func NewSqliteConn(root string) (*Database, error) { …… conn, err := sql.Open("sqlite3", root) …… return NewDatabase(conn, initDatabase) }
Execdriver是Docker中用来执行Docker container任务的驱动。建立并初始化graphdb以后,Docker Daemon随即建立了execdriver,具体代码以下:
ed, err := execdrivers.NewDriver(config.ExecDriver, config.Root, sysInitPath, sysInfo)
可见,在建立execdriver的时候,须要4部分的信息,如下简要介绍这4部分信息:
在执行execdrivers.NewDriver以前,首先经过如下代码,获取指望的目标dockerinit文件的路径localPath,以及系统中dockerinit文件实际所在的路径sysInitPath:
localCopy := path.Join(config.Root, "init", fmt.Sprintf(" dockerinit-%s", dockerversion.VERSION)) sysInitPath := utils.DockerInitPath(localCopy)
经过执行以上代码,localCopy为”/var/lib/docker/init/dockerinit-1.2.0”,而sysyInitPath为当前Docker运行时中dockerinit-1.2.0实际所处的路径,utils.DockerInitPath的实现位于./docker/utils/util.go。若localCopy与sysyInitPath不相等,则说明当前系统中的dockerinit二进制文件,不在localCopy路径下,须要将其拷贝至localCopy下,并对该文件设定权限。
设定完dockerinit二进制文件的位置以后,Docker Daemon建立sysinfo对象,记录系统的功能属性。SysInfo的定义,位于./docker/pkg/sysinfo/sysinfo.go,以下:
type SysInfo struct { MemoryLimit bool SwapLimit bool IPv4ForwardingDisabled bool AppArmor bool }
其中MemoryLimit经过判断cgroups文件系统挂载路径下是否均存在memory.limit_in_bytes和memory.soft_limit_in_bytes文件来赋值,若均存在,则置为true,不然置为false。SwapLimit经过判断memory.memsw.limit_in_bytes文件来赋值,若该文件存在,则置为true,不然置为false。AppArmor经过host主机是否存在/sys/kernel/security/apparmor来判断,若存在,则置为true,不然置为false。
执行execdrivers.NewDriver时,返回execdriver.Driver对象实例,具体代码实现位于./docker/daemon/execdriver/execdrivers/execdrivers.go,因为选择使用native做为exec驱动,故执行如下的代码,返回最终的execdriver,其中native.NewDriver实现位于./docker/daemon/execdriver/native/driver.go:
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath)
至此,已经建立完毕一个execdriver的实例ed。
Docker Daemon在通过以上诸多设置以及建立对象以后,整合众多内容,建立最终的Daemon对象实例daemon,实现代码以下:
daemon := &Daemon{ repository: daemonRepo, containers: &contStore{s: make(map[string]*Container)}, graph: g, repositories: repositories, idIndex: truncindex.NewTruncIndex([]string{}), sysInfo: sysInfo, volumes: volumes, config: config, containerGraph: graph, driver: driver, sysInitPath: sysInitPath, execDriver: ed, eng: eng, }
如下分析Daemon类型的属性:
属性名 |
做用 |
repository |
部署全部Docker容器的路径 |
containers |
用于存储具体Docker容器信息的对象 |
graph |
存储Docker镜像的graph对象 |
repositories |
存储Docker镜像元数据的文件 |
idIndex |
用于经过简短有效的字符串前缀定位惟一的镜像 |
sysInfo |
系统功能信息 |
volumes |
管理host主机上volumes内容的graphdriver,默认为vfs类型 |
config |
Config.go文件中的配置信息,以及执行产生的配置DisableNetwork |
containerGraph |
存放Docker镜像关系的graphdb |
driver |
管理Docker镜像的驱动graphdriver,默认为aufs类型 |
sysInitPath |
系统dockerinit二进制文件所在的路径 |
execDriver |
Docker Daemon的exec驱动,默认为native类型 |
eng |
Docker的执行引擎Engine类型实例 |
建立完Daemon类型实例daemon以后,Docker Daemon使用daemon.checkLocaldns()检测Docker运行环境中DNS的配置, checkLocaldns函数的定义位于./docker/daemon/daemon.go,代码以下:
func (daemon *Daemon) checkLocaldns() error { resolvConf, err := resolvconf.Get() if err != nil { return err } if len(daemon.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { log.Infof("Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : %v", DefaultDns) daemon.config.Dns = DefaultDns } return nil }
以上代码首先经过resolvconf.Get()方法获取/etc/resolv.conf中的DNS服务器信息。若本地DNS 文件中有127.0.0.1,而Docker container不能使用该地址,故采用默认外在DNS服务器,为8.8.8.8,8.8.4.4,并将其赋值给config文件中的Dns属性。
当Docker Daemon启动时,会去查看在daemon.repository,也就是在/var/lib/docker/containers中的内容。如有存在Docker container的话,则让Docker Daemon加载这部分容器,将容器信息收集,并作相应的维护。
加载完已有Docker container以后,Docker Daemon设置了多项在shutdown操做中须要执行的handler。也就是说:当Docker Daemon接收到特定信号,须要执行shutdown操做时,先执行这些handler完成善后工做,最终再实现shutdown。实现代码以下:
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()) } })
可知,eng对象shutdown操做执行时,须要执行以上做为参数的func(){……}函数。该函数中,主要完成4部分的操做:
当全部的工做完成以后,Docker Daemon返回daemon实例,并最终返回至mainDaemon()中的加载daemon的goroutine中继续执行。
本文从源码的角度深度分析了Docker Daemon启动过程当中daemon对象的建立与加载。在这一环节中涉及内容极多,本文概括总结daemon实现的逻辑,一一深刻,具体全面。
在Docker的架构中,Docker Daemon的内容是最为丰富以及全面的,而NewDaemon的实现而是涵盖了Docker Daemon启动过程当中的绝大部分。能够认为NewDaemon是Docker Daemon实现过程当中的精华所在。深刻理解NewDaemon的实现,即掌握了Docker Daemon运行的前因后果。
http://docs.studygolang.com/pkg/
http://www.iptables.info/en/iptables-matches.html
https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt