Docker 测试开发之路--devops (二):docker 的文件系统

孙高飞 · July 28, 2017 · Last by apnsa replied at March 11, 2019 · 4726 hits

前言

这篇文章开始讲一下docker的基础知识,但不是教你docker run , docker build 这种基本命令。 这篇和下一篇文章会分别讲一下docker的文件系统和网络模型。 知道他们的大概原理以后我们就能知道到底什么是容器了,它跟虚拟机有怎样的决定性的区别, 我们在什么情况下选择哪些文件系统和网络方案比较恰当。

什么是容器

先来说说什么是容器,我们都跟刚接触docker的新手讲,你就先把docker当虚拟机。因为我们怕一开始就讲容器巴拉巴拉的会让人懵逼。 反正从使用者的角度来看,大多数情况下也感觉不出来它跟虚拟机有什么区别。 所以不了解容器并不会对我们使用docker run, docker build, docker ps这些命令搭建服务有丝毫的阻碍。就如我们开车的时候不用非得知道汽车里面是怎么构造的。所以如果读者之前没有使用过docker的话,我强烈建议各位先去体验一番,有一个基本的了解之后再来继续读本篇文章,这样你能够更好的理解下面的内容。

那么正题开始,不懂容器原理虽然并不影响我们作为一个docker的使用者。但我们的最终目的是通过docker+k8s构建并维护一个容器集群并提供给公司内部大量使用,所以光学会开车是不行的,我们还得学会修车。所以接下来我们看看容器到底是个什么东东。 首先当我们运行 docker run 的时候,我们知道是根据一个镜像构建出了一个容器, 我们看看这个容器里有什么(假设这里我使用的是一个centos7.2的镜像)。 你可以通过docker exec -it 容器名称 bash 来进入容器。

可以看到我们通过命令进入了容器中,我们发现这是一个完整的操作系统,有root目录,有bin,有sbin,有proc。运行一切centos的命令也都是正常的。好像我们就是在一个虚拟机一样。好像跟宿主机没有半毛钱关系(我这里的实验宿主机是ubantu)。 但这是个假象,我们在容器里运行的任何命令其实就是在宿主机上运行的,是docker耍了一些手段让我们以为是在一个跟宿主机不相关的完整的操作系统上,但其实我们在容器里运行的任何命令在本质上就是在宿主机上运行的。 这就是容器和虚拟机最大的区别。 虚拟机是个完整的操作系统,与宿主机完全隔离,在虚拟机上运行的任何东西都不会影响宿主机。 而容器不是, 容器就是个骗局,它让你以为你是运行在一个完整的操作系统上,其实你们都是在宿主机上玩而已。 所以我们之前一定听过类似这样的话:容器是节省资源的,是共享宿主机内核的,容器上运行的东西是有可能把宿主机搞挂的。 那么docker到底耍了什么手段,我们先从联合文件系统说起

联合文件系统:aufs

aufs,是联合文件系统的简写。是docker最早版本的文件系统。 通过它的特性docker实现了镜像层的重叠,容器层的存储和展示层的显示。

上面这个图就是AUFS的工作原理。 假设我们现在有两个目录X和Y, 他们分别有A和B两个文件。 那么aufs的作用就是可以将这两个目录合并,并挂载到一个新的设备上也就是Z上。 在Z这个目录上我们可以同时看到A和B这两个文件。这就是为什么叫联合文件系统。它的意思就是可以把多个文件联合在一起成为一个统一的视图。 那么如果我们在Z目录上(也就是联合文件系统的视图上)修改A和B会发生什么事情? X和Y这两个目录下的A和B文件也会相应修改么? 我们来试试, 假如我们删除A文件, 并在B文件中添加一个单词Hello。我们会发现出现了下面的变化。

我们在Z目录中对A和B做出了修改。发现原本拥有B文件的X发生了改动。 但是有用Y中的A文件并没有发生改变。 相反的,我们发现在X目录中多了一个删除A文件的记录。 这是为什么? 这就是aufs的一个特性。 在所有联合起来的目录中,只有第一个目录是有写权限的。 也就是说Z不论修改什么都只能对第一个联合进来的X做更改,它是没有权限对其他目录(例如Y)做任何改动的。 如果我们在Z目录中对A做出了更改,它是没有权限修改A的,所以相应的在X中添加一条记录,记录了更改A的内容。 这就是aufs。大家仔细想一下这样的特性会产生什么效果? 看下面的图。

我们之前的Y就是镜像层,X就是容器层,而联合文件目录Z,就是我们进入容器以后展示给我们看的视图层。不知道大家到了这里脑袋能否转过弯来。Y是永远无法更改的,所以它是镜像层。X是可以修改的,并且它可以记录对镜像层的修改来达到定制自己的文件的目的,这样在视图层上,我们就看到的是经过容器层修改后的视图了。 这就是docker使用aufs对文件系统耍的花样。 我们下载各种操作系统的镜像其实是做的很像是操作系统的镜像层。这个镜像层里有root,有bin,有sbin,有proc,有一切你想要的命令脚本。 docker会创建一个空的容器层并和镜像层联合起来,成为一个视图给我们看,并修改了各种环境变量让所有命令都指向镜像层去。 这样就有了在一开始我们进入容器后的那个截图。 我们看到的甚至不是容器层,而是联合起来的视图层。 所以我们其实还是在宿主机上的用户, 只不过我们看不出来。

docker的分层镜像

了解了AUFS之后我们就可以来看看docker的分层镜像了。还是上面的例子,假设现在我们想根据现在对X和Y的改动构建一个新的镜像,并用这个新的镜像运行一个容器会是什么样子的? 看了下面的图你应该就能了解了。

X和Y变成了一个两层的镜像层。新创建出的S作为容器层继续接受视图层的更改请求。如果你想继续更改,例如添加一个文件C。 那么就会变成下面这样。

你想把这个最新的修改也做成镜像,并再跑一个容器出来,那么就会变成下面的这个样子。

通过这种上层镜像只记录对下层镜像的更改的方式,我们可以把镜像一层一层的重叠起来。这就是docker的分层镜像系统.

这是我再测试环境中push的一个镜像。 可以看到它有很多的layer, 是一个拥有10层的镜像。这就是docker对文件系统的玩法,也是有效节省资源的方法。 所以一般我们的在一台机器上的镜像都会是树结构。

这也是我们的dockerfile里第一个指定就是FROM。如下:

FROM 指令的意思就是从哪一级镜像层开始继承,也就是从哪一级镜像层开始联合。 RUN指令会在镜像层的基础上运行。但并不对镜像层更改,而是创建一个容器层并记录对镜像层的更改。 最后当RUN指令运行完毕以后才会把容器层制作成新的一层镜像。 这里需要注意的是,一个RUN 指令就是一层。 避免一个命令使用一个RUN。 如果你用了100个RUN指定来跑shell的话, 会造成100层的镜像深度。这会变成一个噩梦的。 合理的利用镜像分层是docker使用者的必修课。

device mapper

刚才我们了解了docker的分层镜像系统和它的实现原理aufs。 但其实docker的分层文件系统有很多的实现方式,aufs只是其中一种。 它是docker最早期实现的storge driver,也是目前最稳定的存储驱动。 但它有一个很不爽的缺线就是aufs一直没能进入linux内核的主干。 也就导致了很多linux的发行版是不支持aufs的。 例如我们最常用的centos。 早期的docker只能运行在ubantu上。据说是因为aufs的代码写的实在太烂,linux的作者一直坚决的反对把aufs添加到内核主干来。 但是大家对于docker的使用越来越迫切, 这时候linux社区的大牛们出马使用device mapper 支持了docker的分层镜像系统。 之后我还会介绍overlayFS和overlayFS2, 但他们都是对aufs的模仿,实现的都是docker的分层镜像系统。所以我就不再具体介绍他们原理了。 这里先简单介绍一下device mapper, linux运维的同学一定对它不陌生,lvm逻辑卷的主要实现技术。 通过快照的方式备份数据,以前很多数据库的热备都是通过类似这样的技术实现的。快照的特点也是只对原始数据修改的部分做记录,而不是真正的修改原始数据。 这一点跟AUFS一样,这样device mapper也就可以跟aufs一样去制作一个分层镜像系统。 device mapper的优点是支持几乎所有linux的发行版本。它弥补了aufs只能在ubantu上运行的缺陷,让很多使用者可以很happy的在centos上安装并使用docker。 但是它的缺点也是很令人诟病的。 device mapper在构建自己的存储设备的时候是通过下面这个流程(图画的实在太low了请大家见谅):

首先会先创建一个空文件A(docker默认创建一个100G的空文件), 这个文件有100G大,但实际上是一个空文件。 然后创建一个本地回环设备loopback0, 这个设备的特点是可以关联这个空文件A,并且可以把这个loopback0挂载出一个设备B。 这样任何对B的改动都会通过loopback0保存在这个文件A中。 这就是device mapper做实际存储的方式(时间有点久了具体细节有点记不起来)。 这种机制有两个主要的缺点

  1. 一开始的那个空文件A是固定大小的,一旦创建没有任何扩容的可能。docker默认创建的是100G大小的空间。如果随着使用的增加,我们的容器和镜像越来越多,100G的硬盘空间极可能是不够的。 如果发生了这种情况,没有任何办法。只能rm -r /var/lib/docker 来解决,也就是整个把docker干掉再通过事先创建文件和回环设备的方式扩容。 我们曾经经历过这种痛,这也是我们放弃device mapper的原因之一。
  2. 这种实现方式依赖本地回环设备进行io读写。 这种方式是不稳定的,在google查看过很多人的评论,都说device mapper的io不稳定,负载高。

还有一些其他的缺点我没有太关注,但是光这两个原因就足以让我放弃device mapper了。

overlayFS

由于大家对device mapper的失望,所以新一代的联合文件系统overlayFS问世。它可以说是aufs的升级版, 并且成功进入linux内核主干。 这里面有个小插曲,网上曾经流传着一封给linux作者的邮件,希望将overlayFS加入到linux中, 回复的邮件说中说虽然我也不喜欢overlayFS,但它总比aufs好太多了,考虑到大多数人的需要,我决定把它加入到linux内核中来(这是对aufs有多大的怨念啊😂 )。 overlayFS作为aufs的升级版,他们实现的功能是一样的,架构也很像。

还记得上面这个是aufs实现分层镜像系统的图么。 它是通过把所有的镜像层通过aufs联合到一个视图上来实现的。 但是overlayFS对待镜像层的方式不是这样的,它只有一层镜像,通过在镜像层使用硬链接的方式指向深层的镜像。 当然镜像层仍然是不可更改的,容器层会记录针对镜像层的更改。 这种方式造就了overlayFS的一个重大缺陷, 那就是linux inode的消耗。 我们知道每个linux系统的inode是有限的。 而软链接是会创建新的inode,所以overlayFS选择了不会消耗inode的硬链接来实现。但是硬链接有一个缺点就是只能对文件做硬链接,而不能对目录做硬链接。 所以overlayFS在实现分层镜像系统的时候,是copy了镜像层的所有目录结构,然后再使用硬链接。 这就使得这些copy的目录都消耗了inode。 所以对使用overlayFS的docker而言, 一旦镜像和容器的结构变的复杂,就很可能触发inode耗尽的情况(我们曾经遇见过)。 这个缺点直接导致了很多人弃用了overlayFS(包括我)。所以overlayFS2问世了。

overlayFS2

overlayFS2作为第三代的联合文件系统,在架构上优化了很多。 当然其中最主要的变化就是在镜像层次在128层以下的时候仍然使用像aufs的联合文件视图的方式,到了128层以上的时候才会使用硬链接。这样避免了inode耗尽的情况。 当然了overlayFS2刚刚问世不久, 你需要把内核版本升级到4.4以上才能够加载overlayFS2的内核模块。 目前针对overlayFS2的测试使用还比较少,网上也没有多少针对它的吐槽。有很多公司对系统的内核版本有运维要求,所以肯升级到4.4以上去尝试overlayFS2的人还是比较少的。

aufs on centos

我们刚才说了linux社区的大牛们极其的厌恶aufs,它这辈子是没可能进入linux 内核主干了。 甚至linux社区曾经宣布早晚有一天ubantu也要放弃aufs。 那么这个aufs on centos又是什么鬼? 因为很多公司要么有运维要求,要么对centos情有独钟。总之他们就是想用centos跑docker,而且他们还不想用device mapper和overlayFS这俩坑爹货。他们还不敢随便尝试刚出来的overlayFS2。对,说的就是像我这样的😂 。 所以网上有个好心人自己维护了一个内核版本,在centos上安装了这个内核版本之后,就可以使用aufs来运行docker了。

设置storage driver

OK,再介绍了常用的这几种虚拟文件系统后,我们来看看怎么设置docker来使用这些文件系统。以systemd管理举例,编辑 /etc/systemd/system/docker.service.d/docker.conf
添加--storage-driver=aufs 来选择你需要的文件系统吧。

新手建议

如果你是新手,强烈建议就在ubantu下老老实实的玩aufs吧。aufs是使用最多,最稳定,最不容易出错的。虽然它的性能没有overlayFS好,但是足够你用在测试环境里了。如果你还是偏爱centos,请联系运维同学帮你安装内核使用aufs。

总结

这些就是我接触过的docker的strange driver并对他们做了一些比较。 并且也讲了一下容器的本质并非完整的操作系统。 它只是跟你玩了点小花招,让你以为是在一个容器就是一个隔离的完整的操作系统。 实际上我们还是在宿主机上,只是他通过联合文件系统给我们的错觉。联合文件系统通过展示给我们的视图来隔离了容器间的文件系统。但光隔离文件系统是不行的,我们的网络,进程都需要隔离。 所以下一篇我将介绍docker的网络原理。通过下一篇的讲解,大家应该就知道容器到底是什么了。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 8 条回复 时间 点赞

好文,长知识了

厉害厉害。学习了

收藏,养肥了一起研究!!!!

谢谢分享,期待后面的更新

是Ubuntu而不是ubantu😂

Droid roll 回复

不要在意这些细节哈哈哈

以前只管用,现在终于想着掀开引擎盖了

孙高飞 线下课第二期_docker_20180520 中提及了此贴 20 May 10:42
孙高飞 Docker 实战 中提及了此贴 03 Mar 16:24
孙高飞 专栏文章:POD 衍生知识 (cgroups) 中提及了此贴 11 Mar 16:22
孙高飞 docker 实战课程贴 中提及了此贴 31 Aug 11:08
孙高飞 docker 实战-2020215 中提及了此贴 15 Feb 17:22
待办 中提及了此贴 03 Apr 23:05
需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up