Docker-client模式
docker命令对应的源文件是docker/docker.go(如果不说明,根路径是项目的根目录docker/),其使用方法如下: docker OPTIONS command [arg…] 其中OPTIONS参数称为flag,任何时候执行一个docker命令,Docker所有这些都需要先分析flag,然后根据用户声明COMMAND执行指定子命令的相应操作。若子命令为daemon, Docker创建一个在宿主机上运行的设备daemon进程(docker/daemon. go#mainDaemon),即执行daemon模式。其他子命令将执行client模式。处于client模式下的docker命令工作流程包括以下步骤。 1.解析flag信息 docker命令支持大量OPTION,或者说flag,列出运行在这里client模式下的docker更重要的是flag。 ? Debug,对应-D和–debug它将参数添加到系统中DEBUG将日志显示等级调整为环境变量和赋值1DEBUG级,这个flag用于启动调试模式。 ? LogLevel,对应-l和–log-level默认等级为参数info,也就是说,只输出普通的操作信息。用户现在可以指定的日志级别panic、fatal、error、warn、info、debug这几种。 ? Hosts,对应-H和–host=[]参数,对client模式是指需要连接的操作Docker daemon位置,而对daemon提供所需监控地址的模式。若Hosts变量或者系统环境变量DOCKER_HOST不是空的,说明用户指定了host对象;否则,在默认情况下使用默认设置Linux系统设置为unix:///var/run/docker.sock。 ? protoAddrParts,这个信息来自-H参数://前后两部分的组合,即与Docker daemon建立通信协议的方式和方式socket地址。 2.创建client实例 client创建是在现有参数信息配置的基础上调用api/client/cli.go#NewDockerCli,需要设置好proto(传输协议),addr(host目标地址)和tlsConfig除标准输入输出和错误输出外,还将配置安全传输层协议的配置。 3.执行具体的命令 Docker client对象创建成功后,剩余的具体命令执行过程将交给对象cli/cli.go来处理了。
- 从命令映射到相应的方法 cli从用户输入的命令(如反射机制)run)匹配的执行方法(如CmdRun),这也是所谓的协议大于配置方法命名规范。同时,cli根据参数列表的长度,判断是否用于多级列表Docker命令支持(例如,未来可能会增加命令,例如docker group run可指定一组Docker容器一起运行命令),然后根据发现的执行方法将剩余参数传输并执行。如果输入方法不合法或参数不正确,则返回docker并退出命令的帮助信息。在v1.10的Docker中间,每个类似api/client/commnds.go#CmdRun例如,读者想学习所有的方法都被剥离成单独的文件docker run这个命令的执行过程需要找到api/client/run.go这个文件
- 执行相应的方法,发起请求 在找到具体的执行方法后,开始执行。虽然要求的内容会有所不同,但执行过程大致相同。如下所示。? 分析介绍的参数,并配置参数。? 获取与Docker daemon通信所需的认证配置信息。? 根据命令业务类型,给出命令业务类型Docker daemon发送POST、GET等请求。? 读取来自Docker daemon返回结果。可以看出,在执行请求的过程中,大多数是初步处理命令行中的请求参数,并添加相应的辅助信息,最终通过指定的协议给出Docker daemon发送Docker client API请求和主要任务的执行由Docker daemon完成。
daemon模式
一旦docker进入了daemon模式,其余的初始化和启动工作都是由Docker的docker/daemon. go#CmdDaemon来完成。Docker daemon通过一个server模块(api/server/server.go)接收来自client请求,然后根据请求类型,交给具体方法。因此daemon首先需要启动和初始化server。另一方面,启动server后,Docker一个过程需要初始化daemon对象(daemon/daemon.go)来负责处理server收到的请求。下面是Docker daemon详细分析启动和初始化过程。 API Server配置和初始化过程 首先,在docker/daemon.go#CmdDaemon中,Docker将继续按照用户的配置完成server它的初始化和启动。API Server,顾名思义就是专门负责响应用户求交给用户daemon处理过程的具体方法。其启动过程如下。 (1) 分析用户指定的参数。 (2) 创建PID文件。 (3) 加载所需的server辅助配置包括日志、是否允许远程访问、版本和TLS认证信息等。 (4) 根据上述server配置,加上用户之前分析的指定server配置(比如Hosts),通过goro-utine的方式启动API Server。这个server监听的socket位置就是Hosts的值。 (5) 创建一个负责处理业务的人daemon对象(对应daemon/damone.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=信号,表示这一点Docker daemon正常工作已经开始。
那么shutdownDaemon如何关闭一个?daemon是的?这个过程包括以下步骤。 (1) 创建并设置一个channel,使用select监控数据。正确关闭daemon工作后将该channel关闭,识别工作完成情况;否则,在加班(15秒)后报错。 (2) 调用daemon/daemon.go#Shutdown方法如下。 ? 首先使用遍历所有操作容器SIGTERM如果容器过程不能在10秒内完成,则使用SIGKILL强制杀死。 ? 如果netController被初始化过,调用#libnetwork/controller.go#GC垃圾回收的方法。 ? 结束运行中的镜像存储驱动过程。
- daemon对象的创建和初始化过程
- 配置Docker容器所需的文件环境 一是创建容器配置文件目录。Docker daemon在创建Docker容器完成后,需要将容器中的配置文件放入本目录进行统一管理。目录默认位置为:/var/lib/docker/containers,以下配置文件将为每个特定容器保存如下:
[root@iZbp102fxl8duhse0avs3mZ containers]# ll 总用量 24 drwx--x--- 4 root root 4096 7月 5 23:21 0df99da2ff2e322163ee82f330f6981c9d06ab1022c7f4f159c4dad734dc70b7 drwx--x--- 4 root root 4096 7月 11 07:52 0e206ce345695a135ab6f31450e23613a988155cf4de1711eca3f98ed9de3a9
drwx--x--- 4 root root 4096 7月 14 14:38 790681bc9bcf13433af57f787700c64547aeaa33549cbc56a40d742aecfde89d
drwx--x--- 4 root root 4096 7月 13 00:06 9b651b43076f0470681222af452a8d1f3fabc44b88c5289783c206fe4debffa3
drwx--x--- 4 root root 4096 7月 14 15:04 ce9aa67407712aeb3a65792ca9f44606be6037722abf6aec98039fe75be3ea76
drwx--x--- 4 root root 4096 7月 5 23:21 ee95a0ae7e5fd500e6e704222a1ba471566112d3ebce374085c5507e560109d0
[root@iZbp102fxl8duhse0avs3mZ containers]# cd 0df99da2ff2e322163ee82f330f6981c9d06ab1022c7f4f159c4dad734dc70b7/
[root@iZbp102fxl8duhse0avs3mZ 0df99da2ff2e322163ee82f330f6981c9d06ab1022c7f4f159c4dad734dc70b7]# ll
总用量 40
-rw-r----- 1 root root 5831 7月 14 14:32 0df99da2ff2e322163ee82f330f6981c9d06ab1022c7f4f159c4dad734dc70b7-json.log
drwx------ 2 root root 4096 7月 5 23:20 checkpoints
-rw------- 1 root root 3142 7月 5 23:21 config.v2.json
-rw-r--r-- 1 root root 1491 7月 5 23:21 hostconfig.json
-rw-r--r-- 1 root root 13 7月 5 23:21 hostname
-rw-r--r-- 1 root root 218 7月 14 14:38 hosts
drwx--x--- 2 root root 4096 7月 5 23:20 mounts
-rw-r--r-- 1 root root 80 7月 5 23:21 resolv.conf
-rw-r--r-- 1 root root 71 7月 5 23:21 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(比如aufs)后,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和downloadManager。创建image/aufs/imagedb/目录用于存储镜像的元数据,并根据layerStore创建imageStore,用来管理镜像的元数据。
第四,调用volume/local/local.go#New创建volume驱动目录(默认为/var/lib/docker/volumes), Docker中volume是宿主机上挂载到Docker容器内部的特定目录。volumes目录下有一个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和referenceStore中。
第八,执行镜像迁移。由于Docker 1.10版本以后,镜像管理部分使用了基于内容寻址存储(content-addressable storage)。升级到1.10以上的新版本后,在第一次启动daemon时,为了将老版本中的graph镜像管理迁移到新的镜像管理体系中,这里会检查Docker根目录中是否存在graph文件夹,如果存在就会读取graph中的老版本镜像信息,计算校验和并将镜像数据写入到新版的imageStore和layerStore中。读者需要注意的是,迁移镜像中计算校验和是一个非常占用CPU的工作,并且在未完成镜像迁移时,Docker daemon是不会响应任何请求的,所以如果你本地的老版本镜像和容器比较多,或者是在对服务器负载和响应比较敏感的线上环境尝试升级Docker版本,那就要注意妥善安排时间了。Docker官方也提供了迁移工具让用户在老版本daemon运行的时候进行镜像的迁移[插图]。综上,这里Docker daemon需要在Docker根目录(/var/lib/docker)下创建并初始化一系列跟容器文件系统密切相关的目录和文件。这些文件和目录的具体作用我们会在讲解镜像和volume的时候做详细解释,这里先给读者进行一个简单的总结。