Docker技术三大要点:cgroup, namespace和unionFS的理解

www.docker.com的网页有这样一张有意思的动画:java

从这张gif图片,咱们不难看出Docker网站想传达这样一条信息, 使用Docker加速了build,ship和run的过程。spring

Docker最先问世是2013年,以一个开源项目的方式被你们熟知。docker

Docker的奠定者是dotcloud,一家开发PaaS平台的技术公司。apache

不过惋惜的是,这家公司把Docker开源以后,于2016年倒闭了,由于其主业务PaaS没法和微软,亚马逊等PaaS业界巨头竞争,不由让人唏嘘。bash

Docker实际上是容器化技术的具体技术实现之一,采用go语言开发。不少朋友刚接触Docker时,认为它就是一种更轻量级的虚拟机,这种认识实际上是错误的,Docker和虚拟机有本质的区别。容器本质上讲就是运行在操做系统上的一个进程,只不过加入了对资源的隔离和限制。而Docker是基于容器的这个设计思想,基于Linux Container技术实现的核心管理引擎。服务器

为何资源的隔离和限制在云时代更加剧要?在默认状况下,一个操做系统里全部运行的进程共享CPU和内存资源,若是程序设计不当,最极端的状况,某进程出现死循环可能会耗尽CPU资源,或者因为内存泄漏消耗掉大部分系统资源,这在企业级产品场景下是不可接受的,因此进程的资源隔离技术是很是必要的。网络

我当初刚接触Docker时,觉得这是一项新的技术发明,后来才知道,Linux操做系统自己从操做系统层面就支持虚拟化技术,叫作Linux container,也就是你们处处能看到的LXC的全称。学习

LXC的三大特点:cgroup,namespace和unionFS。动画

cgroup:网站

CGroups 全称control group,用来限定一个进程的资源使用,由Linux 内核支持,能够限制和隔离Linux进程组 (process groups) 所使用的物理资源 ,好比cpu,内存,磁盘和网络IO,是Linux container技术的物理基础。

namespace:

另外一个维度的资源隔离技术,你们能够把这个概念和咱们熟悉的C++和Java里的namespace相对照。

若是CGroup设计出来的目的是为了隔离上面描述的物理资源,那么namespace则用来隔离PID(进程ID),IPC,Network等系统资源。

咱们如今能够将它们分配给特定的Namespace,每一个Namespace里面的资源对其余Namespace都是透明的。

不一样container内的进程属于不一样的Namespace,彼此透明,互不干扰。

咱们用一个例子来理解namespace的必要。

假设多个用户购买了一台Linux服务器的Nginx服务,每一个用户在该服务器上被分配了一个Linux系统的帐号。咱们但愿每一个用户只能访问分配给其的文件夹,这固然能够经过Linux文件系统自己的权限控制来实现,即一个用户只能访问属于他自己的那些文件夹。

可是有些操做仍然须要系统级别的权限,好比root,但咱们确定不可能给每一个用户都分配root权限。所以咱们就可使用namespace技术:

咱们可以为UID = n的用户,虚拟化一个namespace出来,在这个namespace里面,该用户具有root权限,可是在宿主机上,该UID =n的用户仍是一个普通用户,也感知不到本身其实不是一个真的root用户这件事。

一样的方式能够经过namespace虚拟化进程树。

在每个namespace内部,每个用户都拥有一个属于本身的init进程,pid = 1,对于该用户来讲,仿佛他独占一台物理的Linux服务器。

对于每个命名空间,从用户看起来,应该像一台单独的Linux计算机同样,有本身的init进程(PID为1),其余进程的PID依次递增,A和B空间都有PID为1的init进程,子容器的进程映射到父容器的进程上,父容器能够知道每个子容器的运行状态,而子容器与子容器之间是隔离的。从图中咱们能够看到,进程3在父命名空间里面PID 为3,可是在子命名空间内,他就是1.也就是说用户从子命名空间 A 内看进程3就像 init 进程同样,觉得这个进程是本身的初始化进程,可是从整个 host 来看,他其实只是3号进程虚拟化出来的一个空间而已。

看下面的图加深理解。

父容器有两个子容器,父容器的命名空间里有两个进程,id分别为3和4, 映射到两个子命名空间后,分别成为其init进程,这样命名空间A和B的用户都认为本身独占整台服务器。

Linux操做系统到目前为止支持的六种namespace:

unionFS:

顾名思义,unionFS能够把文件系统上多个目录(也叫分支)内容联合挂载到同一个目录下,而目录的物理位置是分开的。

要理解unionFS,咱们首先要认识bootfs和rootfs。

1. boot file system (bootfs):包含操做系统boot loader 和 kernel。用户不会修改这个文件系统。

一旦启动完成后,整个Linux内核加载进内存,以后bootfs会被卸载掉,从而释放出内存。

一样内核版本的不一样的 Linux 发行版,其bootfs都是一致的。

2. root file system (rootfs):包含典型的目录结构,包括 /dev, /proc, /bin, /etc, /lib, /usr, and /tmp

就是我下面这张图里的这些文件夹:

等再加上要运行用户应用所须要的全部配置文件,二进制文件和库文件。这个文件系统在不一样的Linux 发行版中是不一样的。并且用户能够对这个文件进行修改。

Linux 系统在启动时,roofs 首先会被挂载为只读模式,而后在启动完成后被修改成读写模式,随后它们就能够被修改了。

不一样的Linux版本,实现unionFS的技术可能不同,使用命令docker info查看,好比个人机器上实现技术是overlay2:

看个实际的例子。

新建两个文件夹abap和java,在里面用touch命名分别建立两个空文件:

新建一个mnt文件夹,用mount命令把abap和java文件夹merge到mnt文件夹下,-t执行文件系统类型为aufs:

sudo mount -t aufs -o dirs=./abap:./java none ./mnt

mount完成后,到mnt文件夹下查看,发现了来自abap和java文件夹里总共4个文件:

如今我到java文件夹里修改spring,好比加上一行spring is awesome, 而后到mnt文件夹下查看,发现mnt下面的文件内容也自动被更新了。

那么反过来会如何呢?好比我修改mnt文件夹下的aop文件:

而java文件夹下的原始文件没有受到影响:

实际上这就是Docker容器镜像分层实现的技术基础。若是咱们浏览Docker hub,能发现大多数镜像都不是从头开始制做,而是从一些base镜像基础上建立,好比debian基础镜像。

而新镜像就是从基础镜像上一层层叠加新的逻辑构成的。这种分层设计,一个优势就是资源共享。

想象这样一个场景,一台宿主机上运行了100个基于debian base镜像的容器,难道每一个容器里都有一份重复的debian拷贝呢?这显然不合理;借助Linux的unionFS,宿主机只须要在磁盘上保存一份base镜像,内存中也只须要加载一份,就能被全部基于这个镜像的容器共享。

当某个容器修改了基础镜像的内容,好比 /bin文件夹下的文件,这时其余容器的/bin文件夹是否会发生变化呢?

根据容器镜像的写时拷贝技术,某个容器对基础镜像的修改会被限制在单个容器内。

这就是咱们接下来要学习的容器 Copy-on-Write 特性。

容器镜像由多个镜像层组成,全部镜像层会联合在一块儿组成一个统一的文件系统。若是不一样层中有一个相同路径的文件,好比 /text,上层的 /text 会覆盖下层的 /text,也就是说用户只能访问到上层中的文件 /text。

假设我有以下这个dockerfile:

FROM debian

RUN apt-get install emacs

RUN apt-get install apache2

CMD ["/bin/bash"]

执行docker build .看看发生了什么。

生成的容器镜像以下:

当用docker run启动这个容器时,实际上在镜像的顶部添加了一个新的可写层。这个可写层也叫容器层。

容器启动后,其内的应用全部对容器的改动,文件的增删改操做都只会发生在容器层中,对容器层下面的全部只读镜像层没有影响。

要获取更多Jerry的原创文章,请关注公众号"汪子熙":

相关文章
相关标签/搜索