文章目录
从上图可以看出,Docker 主要的模块有:
- Docker Client
- Docker Daemon
- Docker Registry
- Graph
- Driver
- Libcontainer
- Docker Container
用户使用 Client 与 Daemon 建立通信,并发送请求给后者 Daemon 作为 Docker 的核心,首先提供 Server 来接受 Client 的请求,而后通过 Engine 执行 Docker 内部的一系列工作,每一项工作都是以一个 Job 的形式的存在。
- 当需要为 Container 提供 Image 时,则从 Registry 中下载镜像,并通过镜像管理驱动 Graphdriver 将下载镜像以 Graph 的形式存储;
- 当需要为 Container 创建网络环境时,则通过网络管理驱动 Networkdriver 创建并配置 Container 网络环境;
- 当需要为 Container 限制运行资源或执行用户指令等操作时,则通过 Execdriver 来完成。
而 Libcontainer 则作为一个独立的 Container 管理模块,Networkdriver 以及 Execdriver 都是通过 Libcontainer 来完成对 Container 进行的操作。当执行 docker run 的一系列工作后,一个实际的 Container 就处于运行状态,该 Container 拥有独立的文件系统,独立并且安全的运行环境等。
Docker Client
Docker Client 在 Linux 上表现为一个 docker 可执行文件,当 Client 接收到 Daemon 返回的响应并进行简单处理后,Client 一次完整的生命周期就结束了。Client 可以通过 3 种方式和 Daemon 建立通信:
- tcp://host:port
- unix:path_to_socket;fd://socketfd
- 通过设置命令行参数设置 TLS 连接
Docker Daemon
Docker Daemon 在 Linux 上表现为一个常驻在后台的系统进程,可以通过 systemd 来进行管理,实际上跟 Docker Client 是同一个 docker 可执行文件。
Docker Daemon 可以细分为以下模块:
- API Server:Daemon 会在后台启动一个 Docker Server,是一个 API Server,基于 Golang 的 Gorilla/Mux 包,接受 Client 发送的请求并路由分发到不同的 Handler 进行处理。
值得注意的是:Docker Server 的启动是靠一个名为 serveapi 的 Job 运行来完成的。所以 Server 的本质是众多 Job 中的一个。
-
Engine:是 Docker 的运行引擎,它扮演 Docker Container 存储仓库的角色,并且通过执行 Job 的方式来操纵管理这些容器。Docker Engine 有两个不同的版本:Docker Engine Enterprise(企业版)和 Docker Engine Community(社区版)。
-
Job:一个 Job 可以认为是 Engine 内部最基本的工作执行单元。Docker 做的每一项工作,都可以抽象为一个 Job。例如:在容器内部运行一个进程,这是一个 Job;创建一个新的容器,这是一个 Job,从 Internet上 下载一个文档,这是一个 Job,等等。Job 的设计者,把 Job 设计得与 Unix Processor 相仿。比如说:Job 有一个名称,有参数,有环境变量,有标准的输入输出,有错误处理,有返回状态等。
Docker Registry
Docker Registry 是一个存储 Images 的仓库。在 Docker 的运行过程中,Daemon 会与 Registry 通信,并实现搜索镜像、下载镜像、上传镜像三个功能,这三个功能对应的 Job 分别为 search、pul 与 push。
Docker 可以使用公有的 Docker Registry,即:Docker Hub,可以从中找到来自开源项目、软件供应商、乃至个人账户的 Docker Image。同时,Docker 也允许用户构建本地私有的 Docker Registry,这样可以保证容器镜像的获取在内网完成。
Graph
Graph 充当已下载镜像的保管者,以及已下载镜像之间关系的记录者。一方面,Graph 存储着本地具有版本信息的文件系统镜像,另一方面也通过 GraphDB 记录着所有文件系统镜像彼此之间的关系。
其中,GraphDB 是一个构建在 SQLite 之上的小型图数据库,实现了节点的命名以及节点之间关联关系的记录。它仅仅实现了大多数图数据库所拥有的一个小的子集,但是提供了简单的接口表示节点之间的关系。
同时在 Graph 的本地目录中,关于每一个的容器镜像,具体存储的信息有:该容器镜像的元数据,容器镜像的大小信息,以及该容器镜像所代表的具体 Rootfs。
Driver
Driver 作为驱动模块,Docker 通过 Driver 来实现对 Container 执行环境的定制。可以分为以下三类驱动:
- Graphdriver
- Networkdriver
- Execdriver
Graphdriver
Graphdriver 用于完成 Image 的管理,包括存储与获取。当用户需要下载指定的镜像时,Graphdriver 就将镜像存储在本地的指定目录;当用户需要使用指定的镜像来创建容器的 Rootfs 时,Graphdriver 就从本地镜像存储目录中获取指定的容器镜像。
在 Graphdriver 初始化之前,有 4 种文件系统或类文件系统在其内部注册,它们分别是:
- Aufs
- Btrfs
- Vfs
- Devmapper
而 Graphdriver 在初始化之时,通过获取系统环境变量 DOCKER_DRIVER 来提取所使用 Driver 的指定类型。而之后所有的 Graph 操作,都使用该 Driver 来执行。
Graphdriver 的架构如下:
Networkdriver
Networkdriver 用于完成 Container 网络环境的配置,其中包括:
- Docker deamon 启动时为其创建 Bridge 网桥;
- Container 创建时为其创建专属虚拟网卡设备,以及为 Container 分配 IP、Port 并与宿主机做端口映射,设置容器防火墙策略等。
Networkdriver 的架构如下:
Execdriver
Execdriver 作为 Container 的执行驱动,负责创建 Container 运行时 namespace,负责容器资源使用的统计与限制,负责容器内部进程的真正运行等。
在 Execdriver 实现的初期使用了 LXC Driver 调用 LXC 的接口,来操纵容器的配置以及生命周期,而现在 Execdriver 默认使用 Native 驱动,不再依赖于 LXC。可以通过启动 Daemon 时指定 ExecDriverflag 参数来进行选择,默认为 native。
Execdriver 架构如下:
Libcontainer
Libcontainer 是 Docker 架构中一个使用 Golang 实现的库,设计初衷是希望该库可以不依靠任何依赖,直接访问 Kernel 中与容器相关的 API。
正是由于 Libcontainer 的存在,Docker 最终得以操纵 Container 的 Namespace、Cgroups、Apparmor、网络设备以及防火墙规则等。这一系列操作的完成都不需要依赖 LXC 或者其他库。
Libcontainer 架构如下:
Docker 将底层容器运行时剥离出来,实现更好的平台无关性。LibContainer 是对各种容器的抽象,发展为 RunC,并贡献给 OCP 组织作为定义容器环境的标准。
Docker Container
Docker Container 变现为一个运行在 Linux 操作系统之上的容器进程,是 Docker 服务交付的最终形式。
- 用户通过指定 Image,使得 Container 可以自定义 Rootfs 等文件系统。
- 用户通过指定计算资源的配额,使得 Container 使用指定的计算资源。
- 用户通过配置网络及其安全策略,使得 Container 拥有独立且安全的网络环境。
- 用户通过指定运行的命令,使得 Container 执行指定的工作。