转载

吓尿了,让一个小白定制 Docker?

这是一个天朗气清,惠风和畅的下午,办公室交织着此起彼伏的机械键盘敲击声。加入 DaoCloud 不久的我,正看着《 GO 并发编程实战》熟悉 Go 语言中接口的概念。一旁的 Allen 在两个显示屏之间来回切换着脑回路,一手流利的英文输入法暗示他又在和全球的 Docker Maintainers 谈笑风生了。

他突然停下来,看了看我手中的教材,问 “最近 go 语言看的怎么样了?” 我略为得意的回答 “还不错,比 C 语言简单”。全然不知 Allen 接下来的一句话犹如晴天霹雳。“那要不给你布置个家庭作业, 试着定制一下 Docker , 限制每个 Daemon 创建 volume 的个数 ?” 他淡淡的说道。

接到题目的瞬间,我脑海中浮现的是一个大写的问号。

吓尿了,让一个小白定制 Docker?

对于刚入职的 Docker 小白,还沉浸在学会 docker pull、docker run 的喜悦中,如何定制 Docker,限制每个 daemon 创建 volume 的个数,对我来说几乎是个不可能完成的作业。就好比让一个刚学会加法的小学生去计算微积分。我不禁怀疑来这家公司实习是否是个正确的决定。

夹杂着恐慌与愤懑,我脑海中对容器、 Docker、 Daemon、 Volume 的印象似乎也模糊了起来,这可不是什么好事情。时间滴嗒滴嗒的流走,我的电脑屏幕上开始跳动着 “ make yourself clear ” 几个英文单词。这也是 Allen 常常嘱咐我的,没把基本概念搞清楚之前,不要急着谈什么高级用法,谈什么实现细节。

还是把基本概念先捋一捋吧 !!

Concept |  容器 

首先是对容器的理解,我印象中容器是指 linux 容器(LXC), 一种操作系统层虚拟化(Operating system–level virtualization)技术,可以在单一 Linux 主机上提供多个隔离的 Linux 环境,如下图所示:

吓尿了,让一个小白定制 Docker?

Concept |  Docker 

然而,容器既不会凭空产生,也不会凭空消失。容器是到底如何产生的?当今行业内,与容器技术最为相关的莫属 Docker。如此火热的技术,业界肯定对 Docker 有着准确的定义,而对一位 Docker 小白而言,他们的定义太过晦涩,我粗略的认识仅仅为:  Docker 是创建、运行容器的玩意儿。

吓尿了,让一个小白定制 Docker?

大致清楚 Docker 的皮毛后,才有机会领略这条鲸鱼的庐山真面目。这时候,显然没有任何捷径,我再次陷入新的恐慌。面对这种不可思议的题目,一开始我是拒绝的,但我不能怂,更不能因为无法完成作业,就为这次实习画上句点。

我回忆起操作 docker pull、docker run  的美好时光,在 CLI 发送一个特定的请求,经过后台处理后,再返回一个请求结果。回忆起 Allen 反复强调的 Docker Clien 和 Docker Daemon,Docker 这种工作模式似乎像极了客户端-服务器(Client/Server)结构。我甚至大胆的猜测,从使用者的角度来看  Docker 就是一个简单的 C/S 架构

说到 docker 与 C/S 架构,应该如下图所示:

吓尿了,让一个小白定制 Docker?

厉害了 word 小白,对 Docker 的结构也有了初步的认识,想想还有点小激动呢。可是现实却是残酷的,我依旧不清楚 Docker 背后的大 Boss  Docker Daemon 是如何处理请求的。

带着疑问我翻开了《 Docker 源码分析》,书的开头便是极为复杂的架构图,看的小白是云里雾里。但是随后作者就对架构中的各个模块进行了拆分与讲解,其中,就有我想了解的 Docker Daemon。

可惜书中的解释对小白来说同样晦涩,在我看来 Docker Daemon 更像个大管家,它即要管存储,又要管网络,管理所有 Docker 容器的大小事务。如此一说,Docker 的架构会不会是下面这样的呢?

吓尿了,让一个小白定制 Docker?

从流程上看,daemon 创建存储卷的请求可以简单拆分为如下几个步骤

  1. Docker 客户端发起创建存储卷的请求
  2. Docker 服务端通过 API 接收到请求,并交给 Handler 处理
  3. Handler 将创建存储卷的请求下发给 Docker 后端相应的存储卷模块处理
  4. 存储卷模块负责完成存储卷的创建,最终将请求处理后结果返回至 Docker 客户端

daemon 创建存储卷的流程是这样,那么创建网络,创建服务是否同样如此?我不禁开了开脑洞。

回到题目 “限制每个 daemon 创建存储卷的个数”,无非是在以上某个步骤做限制。步骤 1 应该不行,这属于在 Docker 客户端处做限制,不符合题目要求。步骤 2 从 API 入手,我心里打起了小算盘,如果能限制 API 处接收请求的个数,不就可以限制 daemon 创建存储卷的个数了吗 ?? 

踏破铁鞋无觅处,得来费了点工夫,小白定制 Docker 看来指日可待了。

我正要下手时,竟有些犹豫,如此简单的解决方法,谨防有诈。在 API 处做限制会不会出什么幺蛾子?我想起了软件设计的原则,通常强调高內聚、低耦合。既然真正处理存储卷请求的地方是在存储卷模块,那么在 API 处做限制就违背了高內聚的原则。看来从步骤 4 中的存储卷模块下手才是上乘之举。

整理好思路,等待小白的将是浩浩荡荡几十万行的 Docker 源码。

开启 Docker 源码之旅

开启 Docker 源码之旅前,需要下载 Docker 源码到本地

吓尿了,让一个小白定制 Docker?
Docker 源码中包含了所有我见过的,以及我没见过的内容。结合作业题目,我的初步目标就是找到和 volume 相关的东西。我记忆中  volume 就是可以让容器共享宿主机文件、目录的存储卷  。[敲重点]

存储卷创建流程

存储卷是如何创建的?小白习惯先在 CLI 上玩儿起来。连续三个 help 找到创建存储卷的命令行语句。

吓尿了,让一个小白定制 Docker?

我试着用 docker volume create 创建一个名为 foo 的 volume,并用 docker volume ls 命令查看 volume 列表,果真看到了刚才创建的 volume “foo”。

吓尿了,让一个小白定制 Docker?

docker volume create 这条命令,在小白这种初级玩家看来是这样,但是中间究竟发生了什么?也许只有代码才能准确的回答这个问题。在浏览了代码后我惊喜的发现,代码中描述的创建过程和我之前的猜测竟然一一吻合,创建存储卷的过程大致分为三个步骤,流程图如下所示 :

吓尿了,让一个小白定制 Docker?

一、 Docker API 接收来自 Docker 客户端的请求

Docker API 和其他应用程序接口一样,负责衔接软件系统中不同的组成部分。Docker 的 API 通过initRoutes() 函数初始化存储卷模块的路由,注册一个路由记录,然后接收到一个 POST 请求 “/volumes/create”。postVolumesCreate() 函数收到请求后再交给 VolumesCreate() 函数处理。

如果纯粹为了解题,在此处设置限制不失为一种方法,简单粗暴,也不用深究 VolumesCreate() 函数后面的故事。但是,如果大家还存在一些疑惑,还想探索后面的未知世界,那么,请允许小白带大家继续这次 Docker 源码之旅。

吓尿了,让一个小白定制 Docker?

二、Docker Daemon 接收来自 Docker API 的请求,交由存储卷模块处理

API 的源码之旅告一段落,转眼来到了 Docker Daemon 的世界。小白一直很好奇,Docker Daemon 作为大管家,是如何接管多种类型的请求?在浏览代码的过程中我发现, struct  Daemon  is all the magic behind it。Docker Daemon 核心的结构体就是 Daemon, 所有存储、网络、服务都是调用 Daemon 这个容器数据结构。如下图所示 :

吓尿了,让一个小白定制 Docker?

其中,所有和存储卷相关的请求都以 Daemon 结构体中的 volumes 字段为入口往下延伸。VolumeCreate() 函数将调用后端存储卷模块对请求进行进最后的处理。

吓尿了,让一个小白定制 Docker?

三、存储卷模块接收请求并进行最后的处理

Docker Daemon 调用 volume 模块中的 Create() 函数,Create() 函数的接收者为 VolumeStore 结构体,返回值为 s.CreateWithRef(name, driverName, “”, opts, labels)

吓尿了,让一个小白定制 Docker?

小白有些疑惑,这个 Create() 函数与 CreateWithRef() 函数是有什么关系?

Create() 函数的返回值s.CreateWithRef(name, driverName, “”, opts, labels) 中 ref 项为空,表明创建存储卷时没有创建 reference 记录。我暂且把 reference 理解为一条连结存储卷与容器的关系纽带。

CreateWithRef() 函数处理来自 Create() 函数的请求,调用  create()  函数完成创建。

兜兜转转,真正创建存储卷的地方竟是在 create() 函数中, 它检查名称是否合乎规范,为存储卷创建 driver…

在存储卷模块做终极限制

找到了 create() 函数意味着终于看到了希望的曙光,于是我打算在 create() 函数中做限制。浏览存储卷模块代码,发现结构体 VolumeStore 是存储卷模块的关键,所有关于 volume 的信息都被保存在这个结构体中。而 New() 函数则负责初始化 VolumeStore 结构体。

吓尿了,让一个小白定制 Docker?

小白限制每个 Daemon 创建的存储卷个数,定制 Docker 的具体过程如下 :

  1. 在 VolumeStore 结构体中声明一个 max 变量,代表 volume 的极限值
  2. New() 函数中为 max 变量赋初值 5
  3. 在 create() 函数中添加如下代码

吓尿了,让一个小白定制 Docker? 编译,运行,结果和我设想的一致。小白做到定制 Docker 了 :v::v:

总结

这仅仅是其中一种限制方式,答案本身显得并不那么重要了。小白在解题的过程中,大致清楚了容器与 Dokcer 的定义,对 Docker 的结构也有些许的了解,同时还培养了从代码的角度看待问题的能力。一题多解,由浅入深,可浅尝辙止,也可深入探究,Allen 老师真是高深莫测。如此看来,让一个小白定制 Docker 也不是不可能。 Disappointing, but not fatal

作者介绍 | Evelyn

DaoCloud 容器技术开源团队成员,曾在 Intel DCG 部门担任 Cloud Engineering, 对分布式调度框架 Mesos 有深入学习,对集群资源管理有一定经验,目前致力于 Docker 容器技术的研究和实践。Evelyn 所在的容器技术开源团队,在 DaoCloud 主要负责容器生态的开源工作。

原文  http://blog.daocloud.io/dockerby-evelyn/
正文到此结束
Loading...