client 模式
docker命令对应的源文件是docker/docker.go,
docker [options] command [arg...]
其中options参数为flag,任何时候执行一个命令docker命令都需要先解析flag,然后按照用户生命的command向指定的子命令执行对应的操作
如果子命令为daemom,docker都会创建一个运行在宿主机上的daemom进程,即执行daemom模式。其余子命令都会执行client模式。处于client模式命令工作流程包含几个步骤
1.解析flag信息
docker命令支持大量的option,或者说flag,列出对于client模式下的docker比较重要的一些flag
Debug,对应-D和--debug参数,他将向系统中添加DEBUG环境变量且赋值为1,并把日志显示级别调为DEBUG级,这个flag用于启动调试模式
LogLevel,对应-l和--log-level 参数。默认等级为info,即只输出普通的操作信息。用户可以指定的日志等级现在有panic、fatal、error、warn、info、DEBUG这几种
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():回调方法
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:docker client和daemom - Python技术站