docker client和daemom

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():回调方法

相关文章
相关标签/搜索