client 模式算法
docker命令对应的源文件是docker/docker.go,docker
docker [options] command [arg...]
其中options参数为flag,任什么时候候执行一个命令docker命令都须要先解析flag,而后按照用户生命的command向指定的子命令执行对应的操做数据库
若是子命令为daemom,docker都会建立一个运行在宿主机上的daemom进程,即执行daemom模式。其他子命令都会执行client模式。处于client模式命令工做流程包含几个步骤json
1.解析flag信息api
docker命令支持大量的option,或者说flag,列出对于client模式下的docker比较重要的一些flag数组
Debug,对应-D和--debug参数,他将向系统中添加DEBUG环境变量且赋值为1,并把日志显示级别调为DEBUG级,这个flag用于启动调试模式安全
LogLevel,对应-l和--log-level 参数。默认等级为info,即只输出普通的操做信息。用户能够指定的日志等级如今有panic、fatal、error、warn、info、DEBUG这几种bash
hosts,对应-h和--hosts=[]参数,对于client模式,就是指本次操做须要链接的docker daemom位置,而对于daemom模式,则提供所要监听的地址,若host变量或者系统环境变量DOCKER_HOST不为空,说明用户指定了host对象;不然使用默认设置,默认状况下Linux系统设置为unix:///var/run/docker.sock服务器
protAddrParts,这个参数来自-H参数中://先后的两部分的组合,即与docker daemom创建通讯的协议方式与socke地址网络
2建立client实例
client的建立就是在已有配置参数信息的基础上,调用api/client/cli.go#NewDockerCli,须要设置好proto(传输协议)、addr(host的目标地址)和tlsConfig(安全传输层协议的配置),另外还会配置标准输入输出及错误输出
3执行具体的命令
Docker client 对象建立成功后,剩下的执行具体命令的过程就交给cli/cli.go来处理
从命令到映射的方法
cli主要经过反射机制,从用户输入的命令(如run)获得匹配的执行方法(CmdRun),这就是所谓“约定大于配置”的方法命名规范。
同时,cli会根据参数列表的长度判断是否用于多级docker命令支持,而后根据找到的执行方法,把剩下的参数传入并执行。若参数传入的方法不正确或者错误,则返回docker的帮助并退出
每个相似api/client/commnds.go#CmdRun 的方法都剥离出来做为一个单独的文件存在。docker run 这个命令的执行过程,就须要寻找api/client/run.go这个文件
执行对应的方法,发起请求
1.解析传入的参数,并针对参数进行配置处理
2.获取与Docker daemon通讯所须要的认证配置信息
3.根据命令业务类型,给Docker daemon发送POST、GET等请求
4.读取来自Docker daemon
daemom 模式
一旦进入daemom模式,剩下的初始化工做都由docker的docker/daemon.go#CmdDaemon来完成;docker daemon经过一个server模块(api/server/server.go)接收来自client的请求,而后根据请求的类型,交由具体方法执行,所以daemom首先要启动并初始化这个server,另外一方面启动server后。docker 进程须要初始化一个daemon对象(daemon/daemon.go)来负责处理server的请求。
docker daemon 初始化启动过程
API server的配置和初始化过程
启动过程
(1)整理解析用户指定的各项参数
(2)建立PID文件
(3)加载所需的server辅助配置,包括日志、是否容许远程访问、版本以及TLS认证信等。
(4)根据上述server配置,加上以前解析出来的用户指定的server配置(好比Host),经过goro-utine的方式启动API server。这个server监听的socket位置就是Host的值
(5)建立一个负责处理业务的daemon对象(对应daemon/daemon.go)做为负责处理用户请求的逻辑实体
(6)对APIserver中的路由表进行初始化,即将用户的请求和对应的处理函数相对应起来。
(7)设置一个channel,保证上述goroutine只是在server出错的状况才会退出
(8)设置信号捕获,docker daemon进程收到INT、TERM、QUIT信号时,关闭API server,用shutdowndaemon中止这个daemon
(9)若是上面流程完成后,API server就会与daemon绑定,并接受client的链接。
(10)最后,docker daemon进程向宿主机的init守护进程发送“READY=1”信号,表示docker daemon已经开始工做
关闭过程
(1)建立并设置一个channel,使用select监听数据。在正确完成关闭daemon工做后将channel关闭,标识该工做的完成;不然在超时15秒后报错
(2)调用daemon/daemon.go#Shoutdown方法执行以下工做
遍历全部运行中的容器,先用SIGTERM软杀死容器进程,若是10秒不能完成,则使用SIGKILL强制杀死
若是netController被初始化过,调用#libnetwork/controler.go#GC 方法进行垃圾回收
结束运行中的镜像驱动程序
在docker1.6版本之前的早期和之前全部版本,server的启动和初始化使用了一种复杂的job机制(API server即被看做一种job),而且依赖于一个专门的docker Engine来管理和运行这些job。1.7版本,这个设计在整个社区的推进下呗重构,上述说的是新的server初始化过程,该server会经过与daemon对象绑定来接受并处理完成具体的请求(相似于一个API接受器绑定了一个业务逻辑处理器)
daemon对象的建立与初始化
对象建立过程至少包括功能有:docker容器配置信息、检测系统支持及用户权限、配置工做路径、加载并配置graphdriver、建立docker网络环境、建立并初始化镜像数据库、建立容器管理驱动、检测DNS配置和加载已有Docker容器等。
docker 容器配置信息
容器配置信息的主要功能有:提供用户自由配置的docker容器的可选功能,使得docker容器运行更贴近用户期待的运行场景;设置默认的网络最大传输单元:当用户没有对-mut参数进行指定是,将其设置为1500.不然,沿用用户指定参数值 ;检测网桥配置信息:此部分配置为进一步配置docker网络提供铺垫
检测系统支持及用户权限
初步处理完docker的配置信息后,docker自身运行的环境进行一系列检测,主要包括3个方面
* 操做系统类型对docker daemon的支持,目前docker daemon只能运行在Linux上
* 用户权限的级别,必须是root权限
* 内核版本与处理器支持,只支持amd64架构的处理,且内核版本必须升至3.10.0及以是上。
配置daemon工做路径
配置docker daemon的工做路径,主要是建立Docker daemon 运行中所在的工做目录,默认为/var/lib/docker.若该目录不存在,则会建立,并赋予0700权限
配置docker容器所需的文件环境
这一步docker daemon会在docker工做目录/var/lib/docker 下面初始化一些重要的目录文件,来构建docker容器工做所需的文件系统环境
这一,建立容器配置文件目录。docker daemon在出建立docker容器以后,须要将容器内的配置文件放到这个目录下统一管理。目录默认位置:/var/lib/docker/containers,它下面会为每一个具体容器保存以下几个配置文件,其中xxx为容器ID
[root@mast ~]# ls /var/lib/docker/containers/4d5464672680c97ed061b73e7d8336741b2971c2fb5a81fa5ac2ec8fac096cf9/ 4d5464672680c97ed061b73e7d8336741b2971c2fb5a81fa5ac2ec8fac096cf9-json.log checkpoints config.v2.json hostconfig.json hostname hosts mounts resolv.conf resolv.conf.hash
第二,配置graphdriver目录。它用于完成docker容器镜像管理所需的底层存储驱动层,因此在这一步的配置工做就是加载并配置镜像存储驱动graphdriver,建立存在驱动镜像管理层文件系统所需的目录和环境,初始化镜像层元数据存储。建立graphdriver时,首先会从环境变量DOCKER_DRIVER中读用户指定的驱动,若为空,则开始遍历优先级数组选择一个graphdriver,在Linux环境下,优先级从高到低依次为aufs、btrfs、zfs、devicemapper、overlay和vfs。 不一样操做系统下,优先级列表的内容和顺序都会不一样,并且随着内核的发展以及驱动的完善,会继续发生变化。
须要注意,目前vfs在docker中时用来管理volume的,并不做为镜像存储使用。另外,因为目前在overlay文件系统上运行的docker容器不兼容SELinux,所以当config中配置信息须要启动SELinux而且driver的类型为overlay时,该过程就会报错
当识别出对应的driver后,docker执行这个driver对应的初始化方法(位于daemon/graphdriver/aufs/aufs,go),这个初始化的主要工做包括:尝试加载内核aufs模块来肯定docker主机支持aufs,发送statfs系统调用获取当前docker主目录(/var/lib/docker)的文件系统信息,肯定aufs是否支持该文件系统;建立aufs驱动根目录(默认:/var/lib/docker/aufs)并将该目录配置为私有挂载,在根目录下建立mnt、diff和layers目录做aufs驱动的工做环境,工做完成后,graphdriver的配置工做就完成。
第三,配置镜像目录。主要工做是在docker主目录下建立一个image目录,来存储全部镜像和镜像层管理数据,默认目录“/var/lib/docker/image”.在image目录下,每一graphdriver都有一个具体的目录用于存储使用该graphdriver存储的镜像相关的元数据
根据上一步graphdriver的选择状况(以aufs为例)建立image/aufs/layerdb/目录做为镜像层元数据存储目录,并建立MetadataStore用来管理元数据。根据graphdriver与元数据存储结构建立layerStore,用来管理全部的镜像和容器层,将逻辑镜像层的操做映射到物理存储驱动层graphdriver的操做,建立用于registry的镜像上传下载的uploadManager和downloadMannger
建立image/aufs/imagedb/目录用于存储镜像的元数据,根据layerStore建立imageStore,用来管理镜像的元数据。
第四,调用volume/local/local.go#New建立volume驱动目录(默认为/var/lib/docker/volumes),docker中volume是宿主机上挂载到docker容器内的特定目录。volume目录下有一个metadata.db 数据库文件用于存储volume相关的元数据,其他以volume ID 命名的文件夹用于存储具体的volume内容。默认的volume驱动是local,用户也能够经过插件的形式使用其余volume驱动来存储
第五,准备“可信镜像”所需的工做目录。docker工做根目录下建立trust目录。这个存储目录能够根据用户给出的可信URL加载受权文件,用来处理可信镜像的受权和验证过程。
第六,建立distributionMetadataStore和referenceStore。referenceStore用于存储镜像仓库列表。记录镜像仓库的持久化文件位于docker根目录下的image/[graphdriver]/repositories.json中,主要记录镜像ID与镜像仓库之间的映射。distributionMetadataStore存储与第二版镜像仓库registry有关的元数据,主要用于作镜像层的diff_id与registry中镜像层元数据之间的映射
第七,将持久化在Docker根目录中的镜像、镜像层以及镜像仓库等的元数据内容恢复到daemon的imageStore、layerStore和reference中
第八,执行镜像迁移,docker1.10版本之后,镜像管理部分使用了基于内容寻址存储。在第一次启动daemon时,为了将老版本的graph镜像管理迁移到新的镜像管理体系中,这里会根据docker根目录中是否存在graph文件夹,若是存在就会读取graph中的老版本镜像信息,计算校验和并将镜像数据写入到新版本的imageStore和layerStore中,注意的是,迁移镜像中计算校验和是一项很是占CPU的工做,而且在未完成镜像迁移时,docker daemon是不会响应任何请求的,全部若是你本地的老版本镜像和容器比较多时,或者是在对服务器负载和响应比较敏感的线上环境尝试,docker版本升级,那就要注意妥善安排时间,docker提供了迁移工具让用户在老版本daemon运行的时候进行镜像迁移
这里docker daemon须要在docker根目录(/var/lib/docker)下建立并初始化一系列容器文件系统密切相关的目录和文件。
建立docker network
建立docker daemon运行环境的时候,建立网络环境是极为重要的一部分。这不只关系着容器对外通讯,一样也关乎着容器之间的通讯。网络部分早已被抽离出来做为一个单独的模块,称为libnetwork,libnetwork经过插件的形式为docker提供网络功能,使得用户能够根据本身需求实现本身的dirver来提供不一样的网络功能。截止docker1.10版本,libnetwork实现了host、null、birdge和overlay的驱动。其中,birdge driver 为默认驱动,和以前版本中的docker网络功能是基本等价的,须要注意的是,同以前的docker网络同样,bridge driver并不提供跨主机通讯的能力,overlay driver则是用于多主机环境
初始化execdriver
execdriver是docker中用来管理容器的驱动,docker会调用execdrivers中NewDriver()函数来建立新的execdriver
在建立execdriver的时候,须要注意一下5部分信息
运行时中指定使用的驱动类型,在默认配置文件中默认使用native,即其对应的容器运行时为libcontainer;
用户定义的execdirver选项,即-exec-opt参数值
用户定义的-exec-root参数值,docker execdriver运行的root路径,默认为/var/run/docker;
docker 运行时的root路径,默认为/var/lib//docker
系统功能的信息,包括容器的内存限制功能,交换分区内存限制功能、数据转发功能以及AppArel安全功能等;AppArel经过host主机是否存在/sys/kernel/security/apparmor来判断是否加入AppArel配置
最后,若是选择netive做为这个execdriver的驱动实现,上述driver的建立过程就会新建一个libcontainer,这个libcontainer会在后面建立和启动Linux容器时发挥做用
daemon对象诞生
docker daemon进程在通过以上诸多设置以及建立对象以后,最终建立出了daemon对象实例
ID | 根据传入的证书生成的容器ID,若没有传入则自动使用ECDSA算法生成 |
repository | 部署全部docker容器的路径 |
containers | 用于存储具体的docker容器信息的对象 |
execCommands | docker容器所执行的命令 |
referenceStore | 存储docker镜像仓库名和镜像ID的映射 |
distributionMetadataStore | v2版registry相关的元数据存储 |
trustkey | 可信任证书 |
IDInfo | 用于经过简短有效的字符串前缀定位惟一的镜像 |
sysInfo | docker所在宿主机的系统信息 |
configStore | docker所需配置信息 |
execDriver | docker 容器执行驱动,默认native类型 |
statsCollector | 收集容器网络以及cgroups的信息 |
dafaultLogConfig | 提供日志的默认配置信息 |
registryService | 镜像存储服务相关信息 |
EvenetsServer | 事件服务相关信息 |
volume | volume所使用的驱动,默认为local |
root | docker运行的工做根目录 |
uidMaps | uid的对应图 |
gidMaps | gid的对应图 |
seccompEnabled | 是否使用seccompute |
nameIndex | 记录建和其名字的对应关系 |
linkIndex | 容器的link目录,记录容器的link关系 |
恢复已有的docker容器
当docker daemon启动时,会去查看在daemon.repository也就是在/var/lib/docker/containers中的内容。如有已经存在的docker容器,则将相应信息收集并进行维护,同时重启restart policy 为always的容器
docker daemon的启动看起来很是复杂,这是docker在演进的过程当中不断增长功能点形成的,但无论从此docker的功能点增长多少,docker daemon进程的启动都将遵循3步
(1)首先建立一个API server,它工做在用户经过-H指定socket
(2)而后docker使用NewDaemon方法建立一个daemon对象来保存信息和处理业务逻辑
(3)最后将上述API server和daemon对象绑定起来,接受并处理client的请求
只不过,NewDaemon方法的长度会不断增长而已
从client到daemon
发起请求
(1)docker run命令开始运行,用户端的docker进入client模式
(2)通过初始化,新建出了一个client
(3)上述client经过反射机制找到了CmdRun方法
CmdRun在解析过程用户提供的容器参数等一系列操做后,最终发出了这样两个请求:
“POST”,“/containers/create?”+containerValues //建立容器
“POST” ,“/containers/”+createResponse.ID+"/start" //启动容器
至此,client 任务结束
建立容器
在这一步docker daemon并不须要建立一个真正的Linux容器,它只须要理解用户经过client提交的POST表单,而后使用这些参数在daemon中新建一个container对象出来便可,这个container实体就是container/container_unix.go,其中的commonContainer字段定义在平台为主。
启动容器
这个时候daemon这边的重点来了。API server接受到start请求后告诉docker daemon进行container启动容器操做,这个过程daemon/start.go
此时,因为container所需的各项参数,如NetworkSetings、ImageID等,都已经在容器过程当中赋好了值,docker daemon会在start.go 中直接执行daemon.ContainerStart,就可以宿主机上建立对应的容器了;建立容器过程是docker daemon,containerMonitor将daemon设置为本身的supervisor。因此通过一系列调用后。daemon.ContainerStart 实际上执行的操做是
即告诉daemon进程,请使用container相关的信息做参数,执行对应的execdriver的Run方法
最后一步
“万事俱备,只欠东风”。在docker daemon已经完成全部的准备工做,最后下达了执行Run操做的命令后,跟系统打交道的任务都交给ExecDriver.Run来完成;execdriver是docker的重要组成部分,它封装了对namespace、cgroups等全部对OS资源操做的方法,而在docker中。execdriver的默认实现(native)就是libcontainer了,到这一步。docker daemon只须要提供三大参数,接下来等着返回结果
* commandv:该容器须要的全部配置信息集合
* pipes:用于将容器stdin、stdout、stderr重定向到daemon
* startCallback():回调方法