########本系列无运维实践########
####想看运维相关建议直接划走####
##系列是基于Go源码浅层分析,不涉及进一步的编译##
#####Docker 1.2
(docker-ce集成了Client和Daemon,因为暂时找不到基于docker-ce的完整分析,所以文章夹带了大量作者的个人理解)
(最新版本的Docker-ce 不再将Client和Daemon集合到一个项目中,而是分散在了两个项目)
(最新版本的Clinent:https://github.com/docker/cli)
(最新版本的Daemon/engine:https://github.com/moby/moby)
一、Docker的模块
· Docker Client
用户通过Client与Daemon建立连接。
· Docker Daemon
1. 具备服务端的功能,有能力接收Docker Client发起的请求;
2. 具备Docer Client请求的处理能力;
3. Docker Daemon内部的所有任务都由Engine来完成,每一项工作都以一个Job的形式存在。
· Docker Registry
· Graph
· Driver
· libcontainer
· Docker Container
二、Docker Client (用户端)
1. 是Docker架构中用户与Docker Daemon建立通信的客户端;
在一台安装有Docker的机器上用户可以使用可执行文件 docker 作为Client,向众多容器发起请求。
2. Docker Client可以通过三种方法和Daemon建立通信,分别为:
tcp: //host:port 、unix: //path_to_socket 和fd: //socketfd。
通信方式确定后,Client与Daemon建立连接并传输请求时,
可以通过命令行flag参数的形式,设置TLS的有关参数,保证传输的安全性
3. Client发送容器管理请求后,请求由Docker Daemon接收并处理,
当Docker Client接收返回的请求响应并做简单处理后,Docker Client一次完整的生命周期就此结束。
若需要继续发送容器管理的请求,用户必须再次通过 docker创建Client
三、Docker Daemon (server端?)
1. Daemon是Docker架构中一个常驻在后台的系统进程;运行Docker就是运行Docker Daemon建立通信的客户端;
Daemon的作用主要有:
a. 接收并处理Docker Client发送的请求;
b. 管理所有的Docker容器。
2. Daemon运行时,会在后台启动一个server,server负责接收Client发送的请求;
接受请求后,Sever通过路由和方法调度,找到相应的Handler来处理请求。
3. 启动Daemon所使用的可执行文件也是docker。
通过传入的参数可以辨别Client还是Daemon,如docker -d代表Daemon的启动,docker ps则代表创建Client并发送ps请求;
4. Daemon的架构大致可以分为 Sever、Engine、Job/Api:
· Docker Sever:
a. Docker Sever在Docker架构中专门服务Docker Client,
它的功能是接收并调度分发Docker Client发送的请求。
b. 在Daemon的启动过程中,Server第一个完成。
Server通过gorilla/mux创建了一个mux.Router路由,提供请求的路由功能。
(gorilla/mux 是一个强大的URL路由器以及调度分发器)
创建Router之后,Sever为mux.Router中添加有效的的路由项,
每一个路由项由HTTP请求方法(PUT、POST、GET或DELETE)、URL和Handle三部分组成
c. 由于Client通过HTTP协议访问Daemon,所以Server创建完mux.Router之后,
将Server的监听地址以及mux.Router作为参数,创建一个httpSrv=http.Server{}开始服务外部请求。
d. 在服务过程中,Server在listener上接收Client的访问请求。
对于每一个请求Server都会创建一个全新的goroutine来服务;
在goroutine中,Docker Server首先读取请求内容,然后做请求解析工作,
接着匹配相应的路由项,随后调用相应的Handle来处理,随后Handle处理完请求之后给Client恢复响应。
注意点:Docker Server在Docker的启动过程中运行,通过一个名为 "serveapi"的Job来实现。
Docker Server实际上只是众多Job的一个,但是为了强调server的重要性以及它为后续Job服务的重要特性,把"serveapi"拿出来单独作为Docker Server来讲;
(components\engine\api\server\server.go)
· Docker Engine:
a. Engine是Docker架构中的运行引擎,同时也是Docker运行的核心模块。
Engine存储着大量的容器信息,同时管理着Docker的大部分Job的执行。
Docker中的大部分任务执行都需要Engine协助,并且通过Engine匹配相应的Job完成Job的执行。
b. 在Docker源码中,Engine的数据结构里有一个名为handles的字段;
该字段存储的是关于众多特定Job各自处理的方式handle。 (在docker-ce 18.09中叫做customHTTPHeaders,找半天。。。)
即用一个map结构存储string结构的handlename和handle的对应关系。
c. 除了容器之外,Engine还接管Docker Daemon中的某些特定任务;
当Docker Daemon遭遇到滋生进程需要退出的情况,Engine还负责完成Docker Daemon的退出善后工作。
· Job:
a. Job可以认为是Docker架构中Engine内部最基本的工作单元;
Docker Daemon可以完成的每一项任务都可以认为是一个Job;
例如,在Docker容器中运行一个进程,这是一个Job;/
创建一个新的容器,这是一个Job;/
在网络上下载一个文档,这是一个Job;/
包括之前Docker Server,创建Sever服务于HTTP协议的API也是一个Job
(个人认为,Job是为了抽象化Engine的工作设置的一个概念,方便管理和调用)
b. 有关于Job接口的设计,与UNIX进程非常相仿;
比如说Job有一个名称,运行时有参数,有环境变量,有标准输入和标准输出还有返回状态
(从这里可以看出容器技术和操作系统的相似性,希望可以从中获得操作系统的相关知识,discovery learning)
c. 对于Job而言,定义完毕之后,运行才能完成Job自身真正的使命;
Job的运行函数Run()则用以执行Job本身
四、Docker Registry (镜像库)
1. Dokcer Registry是以一个存储容器镜像(Docker Image)的仓库;
Image是容器创建时用来初始化容器rootfs的文件系统内容;
Registry将大量的容器汇集在一起,并为分散的Docker Daemon提供镜像服务;
(根据Image和容器的描述,感觉就像 Class和Object之间的关系一样)
2. Docker的运行过程,有三种情况可能会与Registry通信,分别为搜索镜像、下载镜像、上传镜像;
这三种情况所对应的Job名称分别为search、pull和push。 (使用过docker的人应该很熟悉吧hahah)
3. 不同场景下,Daemon可以使用不同的Registry下载Image。
公有Registry:Docker Hub (公用云平台呗....说这么高深)
私有Registry:构建本地私有Registry,使容器获取可以在内网完成 (不用我说也应该知道吧..)
五、Graph (存储镜像)
1. Graph在Docker架构中扮演的角色是Image的保管者;不管是Docker创建的Image还是下载的Image,都由Graph统一保管;
2. Docker支持多种不同的Image存储方式,如aufs、device mapper、btrfs等,所以Graph把同一种类型的Image成为一个repository;
一个repository下的Image按照不同的tag区分。
六、Driver (驱动)
1. Driver是Docker架构中的驱动模块;
通过Driver驱动,Docker可以实现对Docker容器运行环境的定制,定制的维度有网络环境、存储方式、执行方式等等;
在Docker运行的生命周期中,并非用户所有的操作都是正对Docker容器的管理,包括user堆Docker运行信息的获取、对Graph的存储、记录等;
为了将仅与Docker容器相关的管理从Dokcer Daemon的所有逻辑中区分开,设计了Driver来抽象不同类比各自的功能范畴。
2. Docker Driver的实现可以分为 graphdriver、networkdriver、execdriver三类驱动:
· graphdriver:
a. graphdriver用于完成容器存储的管理,包括从远程Docker Registry下载镜像并存储,也包括本地镜像构建后的存储。
当user下载只当的Image,graphdrive将Image分层存储在本地的指定目录下;
当user需要使用指定的Image来创建容器时,graphdrive从本地Image存储目录中获取指定的Image,并按照特定的规则准备rootfs;
当user需要通过Dockerfile构建新的Image,graphdrive会负责newImage的存储管理。
b. 当graphdrive的初始化过程之前,有4种文件系统或类文件系统的驱动在Docker Daemon中注册,分别为aufs、btrfs、devicemapper、vfs;
其中aufs、btrfd以及devicemapper用于Image的管理,vfs用于volume(文件系统)的管理;
Docker在初始化时,优先通过获取系统环境变量"DOCKER_DRVICE"来提取所使用driver的指定类型。
因此,之后所有的Graph操作,都使用该driver来执行。
· networkdriver:
networkdriver的作用是完成Docker容器网络环境的配置,其中包括Docker Daemon启动时为Docker环境创建网桥;
Docker容器创建前为其分配相应的网络接口资源,以及为Docker容器分配IP、端口并与宿主机做NAT端口映射,设置容器防火墙策略等;
· execdrvice:
execdrvice作为Docker容器的执行驱动,负责创建容器运行时的命名空间,负责容器资源使用的统计和限制,负责容器内部进程的真正运行;
七、libcontainer (调用操作系统API,个人认为是容器开发的核心)
1. libcontainer是Docker架构中一个使用Go语言设计实现的库,
设计初衷是希望库可以不依靠任何依赖,直接访问内核中容器相关的系统调用;
2. 正是因为libcontainer的存在,Driver可以直接调用libcontainer,
而最终操作容器的namespace、cgroup、apparmor、网络设备以及防火墙规则等;
这一系列操作在libcontainer的支持下,都不需要LXC或者其他包;
3. libcontainer提供了一整套标准的接口来满足上层对容器管理的需求;
或者说,容器屏蔽了Docker上层对容器的直接管理;
libcontainer承接Linux内核和Docker Daemon。
八、Docker Container (Docker 容器)
1. Docker container是Docker架构中服务交付的最后体现形式;
2. Docker通过Docker Deamon的管理,libcontainer的执行,最终创建Docker容器;
3. Docker容器,功能类似于虚拟机,具备资源受限、环境与外界隔离的特点;
但是手段和KVM、Xen等传统虚拟化技术大相径庭。
4. Docker容器可以根据用户自己的需求,通过Client向Daemon发送容器的创建与启动请求;
请求中携带容器的配置信息,从而达到定制相应的Docker容器的目的。
5. user对Docker Containner的配置有以下几点:
· 通过指定容器镜像,使Docker容器可以自定义rootfs等文件系统; (graphdriver)
· 通过指定物理资源分配,如CPU、内存等,使得Docker容器使用受限的物理资源; (execdrvice)
· 通过配置容器网络及其安全策略,使得Docke容器拥有独立且安全的网络环境; (networkdriver)
· 通过执行容器运行命令,使得Docker容器执行指定的任务 (execdrvice)
九、Docker常见指令分析
1. Docker pull (获取镜像)
docker pull 的作用是 Docker Daemon从Docker Registry中下载指定的Image;
并将Image存储到Graph中
· CLient处理docker pull命令,解析完请求和参数之后,
向Docker Sever发起一个POST请求获取对应的Image
· Docker Sever收到请求,将请求交给mux.Router,mux.Router根据请求方式和URL来确定执行的handle
· mux.Router将请求路由发给相应的handle (PostImageCreate)
· PostImageCreate创建一个pull的Job,之后触发这个Job
· pull在执行过程中从Docker Registry中下载指定的Image;
· pull将Image交给graphdrive存储在Graph;
2. Docker run (创建容器)
docker run 的作用是创建一个新的docker容器;
创建Docker Container对象,并为容器准备rootfs;
创建Docker Container的运行环境;
· CLient处理docker run命令,解析完请求语言与参数之后,向Docker Server发送一个HTTP请求,
HTTP请求方法为POST,希望创建一个Docker容器对象;
· Docker Server接收以上的请求,交给mux.Router,mux.Router通过URL以及请求方法找到对应的handle;
· mux.Router将请求路由分发给对应的handle (PostContainerCreate)
· PostContainCreate创建并初始化了一个create的Job,之后执行这个Job
· create执行过程中通过graphdevice找到创建容器rootfs需要的Images;
· graphdriver将Images加载到Docker容器对应的文件目录下
· Client收到Server成功执行的返回后,发起第二次POST请求,创建容器对象;
· Docker Server接收以上的请求,交给mux.Router,mux.Router通过URL以及请求方法找到对应的handle;
· mux.Router将请求路由分发给对应的handle (PostContainerStart)
· PostContainerStart创建并初始化了一个start的Job,之后执行这个Job;
· start执行之前调用networkdevice为容器配置网络资源 (设置IP、Port、设置防火墙规则...)
· start开始执行用户指令 调用execdriver;
· execdriver被调用,开始初始化Docker Container内部的运行环境,如命名空间、资源控制、用户命令执行,转交给libcontainer来完成;
· libcontainer被调用,执行结束
Docker从精通到入门(一)----Docker的大致架构
于 2022-05-20 04:23:47 首次发布