Docker技术架构详细分析 Docker模块分析

前面我们介绍了容器的基础知识、Docker AUFS分层文件系统、Docker与VMs的对比分网络和容器关键技术,Docker运行在宿主机的操作系统的 Docker 服务上,Docker服务也称“Docker 引擎”,在此服务上可开启多个 Docker 容器,每个 Docker 容器中相互隔离可运行自己所需的应用程序,在物理上共享宿主机的硬件与网络资源。今天我们重点讨论Docker的技术架构和模块功能。

Docker关键技术回顾

  1. 文件系统隔离:每个进程容器运行在完全独立的Namespace根文件系统里。
  2. 资源隔离:可以使用cgroup为每个进程容器分配不同的系统资源,例如CPU、内存和网络带宽。
  3. 网络隔离:每个进程容器运行在自己的网络命名空间里,拥有自己的虚拟接口和IP地址。
  4. 写时复制:采用写时复制方式创建根文件系统,这让部署变得极其快捷,并且节省内存和硬盘空间。
  5. 日志记录:Docker将会收集和记录每个进程容器的标准流(stdout /stderr /stdin),用于实时检索或批量检索。
  6. 变更管理:容器文件系统的变更可以提交到新的映像中,并可重复使用以创建更多的容器。无需使用模板或手动配置。
  7. 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上,例如运行一个一次性交互shell。
  8. Registry仓库:分为公有仓库和私有仓库,仓库主要管理镜像存储,提供开发者上传和下载docker push/pull镜像。Docker Hub(GitHub)是公共注册服务器中的仓库,GitHub也是一个免费托管开源代码的Git服务器。

Docker实现持续部署

Docker的一个重要优势就是实现DevOps,实现持续的软件版本发布、项目测试和运行(Build、Ship和Run)。下面我们拿持续项目构件为了说明下DevOps的优势。

20160826201737

用户每次向Git服务器的push提交都会通知给Jenkins(基于Java开发的一种持续集成工具),Jenkins触发build。Maven(是一个采用Java编写的开源项目管理工具,我们最常用的就是该工具的构建功能)构建所有的相关代码,包括Docker镜像。Maven会把完成的镜像推送到私有的Registry保存,最后Jenkins会触发Docker Registry pull下载镜像到宿主机本地,并自动启动应用容器。

在Git和Jenkins配合下,开发人员每次Push上传或更新代码,都会自动完成程序部署、发布,全程无需运维、测试人员参与,实现自主维护运行环境。

Docker总架构图

Docker采用Client/Server模式的架构设计,Docker的后端采用非常松耦合的架构,模块之间相互独立、各司其职。用户通过Docker Client与Docker Daemon建立通信,并发送请求给Docker Daemon。Docker Daemon提供Server功能接受Docker Client的请求;随后通过Engine执行Docker内部的一系列工作,每项工作都是以一个Job的形式的存在。Client和Server可以运行在一个机器上,也可以通过socket或者RESTful API来进行通信。

20160826201800

Job是Docker内部执行每项工作的载体,当启动Docker需要容器镜像时,Job则从Docker Registry仓库中下载镜像,并通过镜像管理驱动graphdriver将下载镜像到的镜像以Graph的形式存储和管理;当需要为Docker创建和配置网络环境时,Job通过网络管理驱动networkdriver创建并配置Docker容器网络环境;当需要限制Docker容器运行资源或执行用户指令等操作时,则通过execdriver来完成。

关于libcontainer组建,我们在前面有所介绍,它是一项独立的容器管理包,通过标准接口来管理Namespaces、Cgroups以及文件系统来进行容器控制。Networkdriver和execdriver都需要依靠libcontainer来实现对容器进行网络、命名空间隔离等操作。当执行完运行容器的命令后,一个实际的Docker容器就处于运行状态,该容器拥有独立的文件系统,独立并且安全的运行环境等。

Docker Client模块

Docker目前还没有像VMs那样的GUI管理界面,所以Docker的操作主要基于Linux原生的CLI和标准输入输出终端。Docker Client是Docker架构中用户用来和Docker Daemon建立通信的客户端。用户使用的可执行文件为docker,通过docker命令行工具可以发起对众多container的请求管理。

Docker Client与Docker Daemon建立连接时,Docker Client可以通过设置命令行flag参数设置安全传输层协议(TLS)关参数保证传输的安全性。当Docker Client接收到处理结果并加以处理后,Docker Client的一次完整的生命周期就结束了。当需再次发送容器管理请求时,用户必须再次通过docker可执行文件创建Docker Client。

20160826201811

Docker Daemon模块

Docker Daemon是Docker架构中一个常驻在后台的系统进程,该守护进程在后台启动了一个Server,该Server负责接受Docker Client发送的请求;并通过路由与分发调度找到相应的Handler来执行请求。

在Linux系统中,Docker Daemon启动使用的可执行文件也是docker,与Docker Client启动所使用的可执行文件docker相同。在docker命令执行时,通过传入的参数来判别Docker Daemon与Docker Client。

上图展示Docker Daemon的系统架构,Docker Daemon主要由Docker Server、Engine和Job三部分组成,下面我们先分析下这三个子模块。

Docker Server子模块

Docker Server在Docker架构中是专门服务于Docker Client的server。该server的功能是:接受并调度分发Docker Client发送的请求。

20160826201818

Docker采用Go语言开发,所以里面有到很多Go语言相关工具套件,如Web开发套件gorilla。在Docker的启动过程中,通过包gorilla/mux创建了一个mux.Router,提供请求的路由功能。在Golang(Go语言)中,gorilla /mux是一个强大的URL路由器以及调度分发器。在该mux.Router中添加了众多的路由项,每一个路由项由HTTP请求方法(Put、Post、Get和Delete等)、URL、Handler三部分组成。

若Docker Client通过HTTP的形式访问Docker Daemon,创建完mux.Router之后,Docker将Server的监听地址以及mux.Router作为参数,创建一个Server为请求服务。

在Server的服务过程中,Server监听并接受Docker Client的访问请求,并创建一个全新的goroutine来服务该请求。接着找到相应的路由项并调用相应的Handler来处理该请求。

Engine子模块

Engine是Docker架构中的运行引擎,也Docker任务Job的触发器。它负责Docker container存储仓库的管理、Job和Handler映射(把Job映射到daemon对应的具体Handler上),并且通过执行job的方式来操纵管理这些容器。

Job任务子模块

Job是Docker架构中Engine内部最基本的工作执行单元。Job与Unix进程类似,具有名称、参数、环境变量、标准的输入输出,返回状态等。Docker所做的每一项工作都可以抽象为一个job如,在容器内部运行一个进程、创建一个新的容器、从Internet上下载一个文档,创建Server服务于Client请求等都是一个个job。

Docker Registry模块

Docker Registry是一个存储容器镜像的仓库。而容器镜像是在容器创建时,被加载用来初始化容器的文件架构与目录。在Docker的运行过程中,Docker Daemon(Engine)会通过Job与Docker Registry通信,通过名称为Search、Pull与 Push的Job任务实现搜索镜像、下载镜像、上传镜像。

Docker提供了共有和私有的容器镜像仓库,公有Docker Registry是大家熟知的Docker Hub,Docker通过公有仓库获取容器镜像文件时,必须通过互联网访问Docker Hub才行。但用户也可以创建私有的仓库Docker Registry,这样可以保证容器镜像的获取在内网完成。

Graph模块

Graph对已下载Docker容器镜像进行保管,并对已下载容器镜像之间关系进行记录。Graph不但要存储本地具有版本信息的文件系统镜像,而且还要通过GraphDB记录着所有文件系统镜像彼此之间的关系。

20160826201829

GraphDB是一个构建在SQLite(嵌入式数据库)之上的小型图数据库,实现了节点的命名以及节点之间关联关系的记录。在Graph的本地目录中,具体存储的容器镜像信息包含,该容器镜像的元数据,容器镜像的大小信息,以及该容器镜像所代表的具体Rootfs信息。

Driver模块

Driver是Docker架构中的驱动模块。Driver驱动主要作用是实现对Docker容器进行环境的定制。在Docker运行的生命周期中,用户的操作除了针对Docker容器的管理外,还有关于Docker运行信息的获取、Graph的存储与记录等操作。因此,为了将Docker容器的管理从Docker Daemon内部业务逻辑中区分开来,设计了Driver层驱动来接管容器的管理。

Docker Driver模块实现了以下三类驱动graphdriver、networkdriver和execdriver,分别完成不同功能。

Graphdriver主要用于完成容器镜像的管理,包括存储与获取。即当用户需要下载指定的容器镜像时,graphdriver将容器镜像存储在本地的指定目录;同时当用户需要使用指定的容器镜像来创建容器的rootfs时,graphdriver从本地镜像存储目录中获取指定的容器镜像。

20160826201836

在Docker中有4种文件系统或类文件系统在其内部注册,它们分别是aufs、btrfs、vfs和devmapper。而Docker在初始化之时,通过获取系统环境变量提取所使用driver的指定类型。而之后所有的graph操作,都由该driver来执行。

Networkdriver的用途是完成Docker容器网络环境的配置,其中包括Docker启动时为Docker环境创建网桥;Docker容器创建时为其创建专属虚拟网卡设备;以及为Docker容器分配IP、端口并与宿主机做端口映射,设置容器防火墙策略等。

20160826201843

Execdriver作为Docker容器的执行驱动,负责创建容器运行命名空间,负责容器资源使用的统计与限制,负责容器内部进程的真正运行等。在execdriver的实现过程中,可以使用LXC驱动调用LXC的接口来操纵容器的配置以及生命周期,但现在execdriver默认使用native驱动不依赖于LXC。具体体现在Daemon启动过程中加载的ExecDriverflag参数,该参数在配置文件已经被设为Native。目前Docker支持的容器管理方式有两种,一种就是最初支持的LXC方式,另一种称为Native的Libcontainer进行容器管理。

20160826201852

Libcontainer模块

关于libcontainer原理之前有所讨论,它是Docker架构中一个使用Go语言设计实现容器相关的API的库,无论使用Namespace、Cgroups技术或是使用Systemd等其他方案,只要实现了Libcontainer定义的一组接口,Docker都可以运行。

20160826201859

正是由于libcontainer的存在,Docker可以直接调用libcontainer,而最终操纵容器的namespace、cgroups、apparmor、网络设备以及防火墙规则等。这一系列操作的完成都不需要依赖LXC或者其他包。

libcontainer提供了一整套标准的接口来满足上层对容器管理的需求,屏蔽了Docker上层对容器的直接管理。也正是由于此特性,Microsoft在其著名云计算平台Azure中也添加了对Docker的支持。

Docker container模块

Docker容器是Docker架构中服务交付的最终体现形式,Docker按照业务的需求、依赖关系和配置文件打包相应的Docker容器。容器是通过使用主机上的孤立进程,建立虚拟环境的一种方法。

20160826201911

用户根据需通过加载容器镜像,使得Docker容器可以自定义Rootfs等文件系统,用户通过指定计算资源的配额,使得Docker容器使用指定的计算资源,用户通过配置网络及其安全策略,使得Docker容器拥有独立且安全的网络环境,用户通过指定运行的命令,使得Docker容器得以孤立运行并执行指定的工作。这种孤立的容器通过进程或Job实现,它有自己的一套文件系统资源和从属进程。

K8S中文社区微信公众号
分享到:更多 ()