docker我的docker成长之路

第1章 Docker架构

2017-07-07  本文已影响1053人  c84f3109853b

Docker借助操作系统层的虚拟化实现资源的隔离,因此Docker容器在运行时与虚拟机(VM)的运行有很大的区别,Docker容器与宿主机共享同一个操作系统,不会有额外的操作系统开销。这样的优势很明显,因而大大提高了资源利用率,并且提升了I/O等方面的性能。

1.1 Docker各模块功能与实现分析

1.1.1 Docker Client

Docker Client是Docker架构中用户与Docker Daemon建立通信的客户端。在一台安装有Docker的机器上,用户可以使用可执行文件docker作为Docker Client,发起众多Docker容器的管理请求。

Docker Client可以通过以下三种方式和Docker Daemon建立通信,分别为:tcp://host:port、unix:path_to_socket和fd:socketfd。

Docker Client发送容器管理请求后,请求由Docker Daemon接收并处理,当Docker Client接收返回的请求响应并做简单处理后,Docker Client一次完整的生命周期就此结束。若需要继续发送容器管理请求,用户必须再次通过可执行文件docker创建Docker Client,并走完以上相同的流程。

1.1.2 Docker Daemon

Docker Daemon是Docker架构中一个常驻在后台的系统进程。所谓的“运行Docker”,即代表运行Docker Daemon。总之,Docker Daemon的作用主要有以下两方面:

Docker Daemon运行时,会在后台启动一个Server,Server负责接收Docker Client发送的请求;接收请求后,Server通过路由与分发调度,找到相应的Handler来处理请求。

Docker Daemon的架构大致可以分为三部分:Docker Server、Engine和Job。

  1. Docker Server Docker Server在Docker架构中专门服务于Docker Client,它的功能是接收并调度分发Docker Client发送的请求。需要注意的是:Docker Server在Docker的启动过程中运行,通过一个名为“serverapi”的Job来实现。
  2. Engine Engine是Docker架构中的运行引擎,同时也是Docker运行的核心模块。Engine存储着大量的容器信息,同时管理着Docker大部分Job的执行。换言之,Docker中大部分任务的执行都需要Engine协助,并通过Engine匹配相应的Job来执行。除了管理容器之外,Engine还接管Docker Daemon的某些特定任务。当Docker Daemon遭遇到自身进程需要退出的情况时,Engine还负责完成Docker Daemon推出前的所有善后工作。
  3. Job Job可以认为是Docker架构中Engine内部最基本的工作执行单元。Docker Daemon可以完成的每一项工作都会呈现为一个Job。对于Job而言,定义完毕之后,运行才能完成Job自身真正的使命。Job的运行函数Run()则用以执行Job本身。

1.1.3 Docker Registry

Docker Registry是一个存储容器镜像(Docker Image)的仓库。容器镜像(Docker Image)是容器创建时用来初始化容器rootfs的文件系统内容。Docker Registry将大量的容器镜像汇集在一起,并为分散的Docker Daemon提供镜像服务。

Docker的运行过程中,有三种情况可能与Docker Registry通信,分别为搜索镜像、下载镜像、上传镜像。这三种情况所对应的Job名称分别为search、pull和push。

不同场景下,Docker Daemon可以使用不同的Docker Registry。共有Registry与私有Registry就是两种场景模式不同的Docker Registry。其中,大家熟知的Docker Hub,就是全球范围内最大的共有Registry。Docker可以通过互联网访问Docker Hub,并下载容器镜像;同时Docker也允许用户构建本地私有Registry,使用容器镜像的获取在内网完成。

1.1.4 Graph

Graph在Docker架构中扮演的角色是容器镜像的保管者。不论是Docker下载的镜像,还是Docker构建的镜像,均由Graph统一化管理。由于Docker支持多种不同的镜像存储方式,如aufs、devicemapper、Btrfs等,故Graph对镜像的存储也会因以上种类而存在一些差异。对Docker而言,同一种类型的镜像被称为一个repository,如名称为ubuntu的镜像都同属一个repository;而同一个repository下的镜像则会因tag存在差异而不通过,如ubuntu这个repository下tag为12.04的镜像,也有tag为14.04的镜像。

1.1.5 Driver

Driver是Docker架构中的驱动模块。通过Driver驱动,Docker可以实现对Docker容器运行环境的定制,定制的维度主要有网络环境、存储方式以及容器执行方式。需要注意的是,Docker运行的生命周期中,并非用户所有的操作都是针对Docker容器的管理,同时包括用户对Docker运行信息的获取,还包括Docker对Graph的存储与记录等。因此,为了将仅与Docker容器有关的管理从Docker Daemon的所有逻辑中区分开来,Docker的创造者设计了Driver层来抽象不同类别各自的功能范畴。

Docker Driver的实现可以分为以下三类驱动:graphdriver、newworkdriver和execdriver。

graphdriver主要用于完成容器镜像的管理,包括从远程Docker Registry上下载镜像并进行存储,也包括本地构建完镜像后的存储。当用户下载指定的容器镜像时,graphdriver将容器镜像分层存储在本地的指定目录下;同时当用户需要使用指定的容器镜像来创建容器时,graphdriver从本地镜像存储目录中获取指定的容器镜像,并按特定规则为容器准备rootfs;另外,当用户需要通过指定Dockerfile构建全新镜像时,graphdriver会负责新镜像的存储管理。

在graphdriver的初始化过程之前,有4中文件系统或类文件系统的驱动Driver在Docker Daemon内部注册,它们分别是aufs、btrfs、vfs和devmapper。其中,aufs、btrfs以及devmapper用于容器镜像的管理,vfs用于容器volume的管理。Docker在初始化之时,优先通过获取系统环境变量"DOCKER_DRIVER"来提取所使用driver的指定类型。因此,之后所有的Graph操作,都是用该driver来执行。Docker镜像是Docker技术中非常关键的。

networkdriver的作用是完成Docker容器网络环境的配置,其中包括Docker Daemon启动时为Docker环境创建网桥;Docker容器创建钱为其分配相应的网络接口资源;以及为Docker容器分配IP、端口并与宿主机做NAT端口映射,设置容器防火墙策略等。

execdriver作为Docker容器的执行驱动,负责创建容器运行时的命名空间,负责容器资源使用的统计与限制,负责容器内部进程的真正运行等。

1.1.6 libcontainer

libcontainer是Docker架构中一个使用Go语言设计实现的库,设计初衷是希望该库可以不依靠任何依赖,直接访问内核中与容器相关的系统调用。

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

另外,libcontainer提供了一整套标准的接口来满足上层对容器管理的需求。或者说,libcontainer屏蔽了Docker上层对容器的直接管理。又由于libcontainer使用Go这种跨平台的语言开发实现,且本身又可以被上层多种不同的编程语言访问,因此,很难说未来的Docker一定会与Linux平台紧紧捆绑在一起。Docker Daemon的逻辑完全有可能位于其他非Linux操作系统的平台上,仅仅通过libcontainer的远程调用来实现对Docker容器的管理。另一方面,libcontainer与Docker Daemon的松耦合设计,似乎让用户感受了除Linux Container之外其他的容器技术接入Docker Daemon的可能性。libcontainer承接Linux内核与Docker Daemon同时,也让Docker的生态在跨平台方面充满生机。

1.1.7 Docker Container

Docker Container(Docker容器)是Docker架构中服务交付的最终体现形式。Docker通过Docker Daemon的管理,libcontainer的执行,最终创建Docker容器。Docker容器作为一个交付单位,功能类似于传统意义上的虚拟机(Virtual Machine),具备资源受限、环境与外界隔离的特点。然而,实现手段却与KVM、Xen等传统虚拟化技术大相径庭。

用户对Docker容器的配置有以下4个基本方面:

  1. 通过指定容器镜像,使得Docker容器可以自定义rootfs等文件系统。
  2. 通过指定物理资源的配额,如CPU、内存等,使得Docker容器使用受限的物理资源。
  3. 通过配置容器网络及其安全策略,使得Docker容器拥有独立且安全的网络环境。
  4. 通过指定容器的运行命令,使得Docker容器执行指定的任务。

1.2 Docker运行案例分析

1.2.1 docker pull

docker pull命令的作用是:Docker Daemon从Docker Registry下载指定的容器镜像,并将镜像存储在本地的Graph中,以备后续创建Docker容器时使用。下面我们逐一分析这些步骤:

  1. Docker Client处理用户发起的docker pull命令,解析完请求以及参数之后,发送一个HTTP请求给Docker Server,HTTP请求方法为POST,请求URL为"/images/create?"+"xxx",实际意义为下载相应的镜像。
  2. Docker Server接收以上HTTP请求,并交给mux.Router,mux.Router通过URL以及请求方法类型来确定执行该请求的具体handler。
  3. mux.Router将请求路由分发至相应的handler,具体为PostImagesCreate。
  4. 在PostImagesCreate这个handler中,创建并初始化一个名为"pull"的Job,之后触发执行该Job。
  5. 名为"pull"的Job在执行过程中执行pullRepository操作,即从Docker Registry中下载相应的一个或者多个Docker镜像。
  6. 名为"pull"的Job将下载的Docker镜像交给graphdriver管理。
  7. graphdriver负责存储Docker镜像,一方面将实际镜像存储至本地文件系统中,另一方面为镜像创建对象,由Docker Daemon统一管理。

1.2.2 docker run

docker run命令的作用是创建一个全新的Docker容器,并在容器内部运行指定命令。Docker Daemon处理用户发起的这条命令时,所做工作可以分为两部分:第一,创建Docker容器对象,并为容器准备所需的rootfs;第二,创建容器的运行环境,如网络环境、资源限制等,最终真正运行用户指令。因此,在docker run命令的完整执行流程中,Docker Client给Docker Server发送了两次HTTP请求,第二次请求的发起取决于第一次请求的返回状态。下面我们逐一分析这些步骤:

  1. Docker Client处理用户发起的docker run命令,解析完请求与参数之后,向Docker Server发送一个HTTP请求,HTTP请求方法为POST,请求URL为"/containers/create?"+"xxx",实际意义为创建一个容器对象,即Docker Daemon程序逻辑中的容器对象,并非实际运行的容器。
  2. Docker Server接收以上HTTP请求,并交给mux.Router,mux.Router通过URL以及请求方法来确定执行该请求的具体handler。
  3. mux.Router将请求路由分发至相应的handler,具体为PostContainersCreate。
  4. 在PostContainersCreate这个handler之中,创建并初始化一个名为"create"的Job,之后触发执行该Job。
  5. 名为"create"的Job在运行过程中执行Container.Create操作,该操作需要获取容器镜像来为Docker容器准备rootfs,通过graphdriver完成。
  6. graphdriver从Graph中获取创建Docker容器rootfs所需要的所有镜像。
  7. graphdriver将rootfs所有镜像通过某种联合文件系统的方式加载至Docker容器指定的文件目录下。
  8. 若以上操作全部正常执行,没有返回错误或异常,则Docker Client收到Docker Server的返回状态之后,发起第二次HTTP请求。请求方法为"POST",请求URL为"/containers/"+container_ID+"/start",实际意义为启动时才创建完毕的容器对象,实际物理容器的真正运行。
  9. Docker Server接收以上HTTP请求,并交给mux.Router,mux.Router通过URL以及请求方法来确定执行该请求的具体handler。
  10. mux.Router将请求路由分发至相应的handler,具体为PostContainerStart。
  11. 在PostContainerStart这个handler之中,创建并初始化名为"start"的Job,之后触发执行该Job。
  12. 名为"start"的Job执行需要完成一系列与Docker容器相关的配置工作,其中之一是为Docker容器网络环境分配网络资源,如IP资源等,通过调用networkdriver完成。
  13. networkdriver为指定的Docker容器分配网络资源,其中有IP、port等,另外为容器设置防火墙规则。
  14. 返回名为"start"的Job,执行完一些辅助行操作后,Job开始执行用户指令,调用execdriver。
  15. execdriver被调用,开始初始化Docker容器内部的运行环境,如命名空间、资源控制与隔离,以及用户命令的执行,相应的操作转交至libcontainer来完成。
  16. libcontainer被调用,完成Docker容器内部的运行环境初始化,并最终执行用户要求启动的命令。
上一篇 下一篇

猜你喜欢

热点阅读