好奇宝宝看 Docker 底层原理(上)

文章目录

    • 致谢 && 明志
    • Docker 架构
      • Docker Daemon
      • Graph
      • Driver
      • libcontainer
      • Docker container
    • Docker 运行流程
      • docker pull
      • docker run

致谢 && 明志

首先,感谢孙宏亮前辈的《Docker 源码分析》系列。

这些天其实我是有点迷茫的,总觉得哪里不扎实,就想着是不是学习过程中有些实践被我偷偷跳过了,但是想来也就那几次实践被跳过了,要补上很快的(这不,Dockerfile已经补上了),但是还是觉得基础很不扎实,直到我看到了孙宏亮前辈的《Docker 源码分析》,我知道我缺什么了:发散性。

此前我学习其他技术的路线是:快速入门 -> 项目练手 -> 深入底层 -> 源码剖析,所以有不少小伙伴说看完我的一整个系列可以去和面试官对线了哈哈。

但是对于云原生,我有太多的问题,得不到解答。一直觉得 docker 东西不多,但是,就问我一个最基础的问题,也问各位在网上入门的朋友们:docker 隔离,到底隔离了什么?再深入一点,是怎么实现的?
还有 docker 的那个层次分明的系统设计,想走架构路线的小伙伴们不心动吗?反正我是一只心痒痒的想拿到手。


Docker 架构

先看一下总架构图,慢慢看,一般有图我就不喜欢多说话:

接下来,我们从总图出发,拆分出一个个子图。

Docker Daemon

Docker Daemon 是 Docker 架构中一个常驻在后台的系统进程,功能是:接受并处理 Docker Client 发送的请求。该守护进程在后台启动了一个 Server,Server 负责接受 Docker Client 发送的请求;接受请求后,Server 通过路由与分发调度,找到相应的 Handler 来执行请求(想一下 RPC,或者“命令模式”)。

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

Docker Daemon 的架构,大致可以分为以下三部分:Docker Server、Engine 和 Job。
好奇宝宝看 Docker 底层原理(上)_第1张图片

(看这张图,我想起了我的毕设哈哈,“这题我会”)

Docker Server 在 Docker 架构中是专门服务于 Docker Client 的 server。该 server 的功能是:接受并调度分发 Docker Client 发送的请求。
好奇宝宝看 Docker 底层原理(上)_第2张图片

Engine 是 Docker 架构中的运行引擎,同时也 Docker 运行的核心模块。它扮演 Docker container 存储仓库的角色,并且通过执行 job 的方式来操纵管理这些容器。

在 Engine 数据结构的设计与实现过程中,有一个 handler 对象。该 handler 对象存储的都是关于众多特定 job 的 handler 处理访问。举例说明,Engine 的 handler 对象中有一项为:{“create”: daemon.ContainerCreate,},则说明当名为"create"的 job 在运行时,执行的是 daemon.ContainerCreate 的 handler。

(是吧,我毕设里用来解耦网络层和业务层也是这个好办法,一个哈希表)

一个 Job 可以认为是 Docker 架构中 Engine 内部最基本的工作执行单元。Docker 可以做的每一项工作,都可以抽象为一个 job。例如:在容器内部运行一个进程,这是一个 job;创建一个新的容器,这是一个 job,从 Internet 上下载一个文档,这也是一个 job,等等。


Graph

Graph 在 Docker 架构中扮演已下载容器镜像的保管者,以及已下载容器镜像之间关系的记录者。一方面,Graph 存储着本地具有版本信息的文件系统镜像,另一方面也通过 GraphDB 记录着所有文件系统镜像彼此之间的关系。

好奇宝宝看 Docker 底层原理(上)_第3张图片

其中,GraphDB 是一个构建在 SQLite 之上的小型图数据库,实现了节点的命名以及节点之间关联关系的记录。它仅仅实现了大多数图数据库所拥有的一个小的子集,但是提供了简单的接口表示节点之间的关系。

同时在 Graph 的本地目录中,关于每一个的容器镜像,具体存储的信息有:该容器镜像的元数据,容器镜像的大小信息,以及该容器镜像所代表的具体 rootfs。


Driver

Driver 是 Docker 架构中的驱动模块。通过 Driver 驱动,Docker 可以实现对 Docker 容器执行环境的定制。由于 Docker 运行的生命周期中,并非用户所有的操作都是针对 Docker 容器的管理,另外还有关于 Docker 运行信息的获取,Graph 的存储与记录等。因此,为了将 Docker 容器的管理从 Docker Daemon 内部业务逻辑中区分开来,设计了 Driver 层驱动来接管所有这部分请求。

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

(我将之理解为 “抽出系统状态”)

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

好奇宝宝看 Docker 底层原理(上)_第4张图片

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

好奇宝宝看 Docker 底层原理(上)_第5张图片

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

好奇宝宝看 Docker 底层原理(上)_第6张图片


libcontainer

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

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

好奇宝宝看 Docker 底层原理(上)_第7张图片

另外,libcontainer 提供了一整套标准的接口来满足上层对容器管理的需求。或者说,libcontainer 屏蔽了 Docker 上层对容器的直接管理。又由于 libcontainer 使用 Go 这种跨平台的语言开发实现,且本身又可以被上层多种不同的编程语言访问,因此很难说,未来的 Docker 就一定会紧紧地和 Linux 捆绑在一起。

暂不谈 Docker,由于 libcontainer 的功能以及其本身与系统的松耦合特性,很有可能会在其他以容器为原型的平台出现,同时也很有可能催生出云计算领域全新的项目。


Docker container

Docker container(Docker 容器)是 Docker 架构中服务交付的最终体现形式。

Docker 按照用户的需求与指令,订制相应的 Docker 容器:

用户通过指定容器镜像,使得 Docker 容器可以自定义 rootfs 等文件系统;
用户通过指定计算资源的配额,使得 Docker 容器使用指定的计算资源;
用户通过配置网络及其安全策略,使得 Docker 容器拥有独立且安全的网络环境;
用户通过指定运行的命令,使得 Docker 容器执行指定的工作。

好奇宝宝看 Docker 底层原理(上)_第8张图片


Docker 运行流程

看完架构图,就该看流程图了嘛。

docker pull

从 Docker Registry 中下载指定的容器镜像,并存储在本地的 Graph 中,以备后续创建 Docker 容器时的使用。
好奇宝宝看 Docker 底层原理(上)_第9张图片

(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) 在 PostImageCreate 这个 handler 之中,一个名为"pull"的 job 被创建,并开始执行;

(5) 名为"pull"的 job 在执行过程中,执行 pullRepository 操作,即从 Docker Registry 中下载相应的一个或者多个 image;

(6) 名为"pull"的 job 将下载的 image 交给 graphdriver;

(7) graphdriver 负责将 image 进行存储,一方创建 graph 对象,另一方面在 GraphDB 中记录 image 之间的关系。


docker run

Docker 在执行这条命令的时候,所做工作可以分为两部分:第一,创建 Docker 容器所需的 rootfs;第二,创建容器的网络等运行环境,并真正运行用户指令。因此,在整个执行流程中,Docker Client 给 Docker Server 发送了两次 HTTP 请求,第二次请求的发起取决于第一次请求的返回状态。

好奇宝宝看 Docker 底层原理(上)_第10张图片

(1) Docker Client 接受 docker run 命令,解析完请求以及收集完请求参数之后,发送一个 HTTP 请求给 Docker Server,HTTP 请求方法为 POST,请求 URL 为"/containers/create? "+“xxx”;

(2) Docker Server 接受以上 HTTP 请求,并交给 mux.Router,mux.Router 通过 URL 以及请求方法来确定执行该请求的具体 handler;

(3) mux.Router 将请求路由分发至相应的 handler,具体为 PostContainersCreate;

(4) 在 PostImageCreate 这个 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,具体为 PostContainersStart;

(11) 在 PostContainersStart 这个 handler 之中,名为"start"的 job 被创建,并开始执行;

(12) 名为"start"的 job 执行完初步的配置工作后,开始配置与创建网络环境,调用 networkdriver;

(13)networkdriver 需要为指定的 Docker 容器创建网络接口设备,并为其分配 IP,port,以及设置防火墙规则,相应的操作转交至 libcontainer 中的 netlink 包来完成;

(14)netlink 完成 Docker 容器的网络环境配置与创建;

(15) 返回至名为"start"的 job,执行完一些辅助性操作后,job 开始执行用户指令,调用 execdriver;

(16)execdriver 被调用,初始化 Docker 容器内部的运行环境,如命名空间,资源控制与隔离,以及用户命令的执行,相应的操作转交至 libcontainer 来完成;

(17)libcontainer 被调用,完成 Docker 容器内部的运行环境初始化,并最终执行用户要求启动的命令。


你可能感兴趣的