一 镜像的分层特性

在说docker的文件系统之前,我们需要先想清楚一个问题。我们知道docker的启动是依赖于image,docker在启动之前,需要先拉取image,然后启动。多个容器可以使用同一个image启动。那么问题来了:这些个容器是共用一个image,还是各自将这个image复制了一份,

然后各自独立运行呢?

我们假设每个容器都复制了一份这个image,然后各自独立运行,那么就意味着,启动多少个容器,就需要复制多少个image,毫无疑问这是对空间的一种巨大浪费。事实上,在容器的设计当中,通过同一个Image启动的容器,全部都共享这个image,而并不复制。那么问

题又随之而来:既然所有的容器都共用这一个image,那么岂不是我在任意一个容器中所做的修改,在其他容器中都可见?如果我一个容器要将一个配置文件修改成A,而另一个容器同样要将这个文件修改成B,两个容器岂不是会产生冲突?

我们把上面的问题放一放,先来看下面一个拉取镜像的示例:

[root@docker-server1 ~]# docker pull ubuntu:16.04

Unable to find image 'ubuntu:16.04' locally
16.04: Pulling from library/ubuntu
e80174c8b43b: Pull complete
d1072db285cc: Pull complete
858453671e67: Pull complete
3d07b1124f98: Pull complete
Digest: sha256:bb5b48c7750a6a8775c74bcb601f7e5399135d0a06de004d000e05fd25c1a71c
Status: Downloaded newer image for ubuntu:16.04

上面的示例是从docker官方镜像仓库拉取一个nginx:latest镜像,可以看到在拉取镜像时,是一层一层的拉取的。事实上镜像也是这么一层一层的存储在磁盘上的。通常一个应用镜像包含多层,如下:

必须明确镜像是只读的。每一层都只读。在上图上,我们可以看到,在内核之上,最底层首先是一个基础镜像层,这里是一个ubuntu的基础镜像,因为镜像的只读特性,如果我们想要在这个ubuntu的基础镜像上安装一个emacs编辑器,则只能在基础镜像之上,在构建一层新的镜像层。同样的道理,如果想要在当前的emacs镜像层之上添加一个apache,则只能在其上再构建一个新的镜像层。而这即是镜像的分层特性。

二  容器读写层的工作原理

刚刚在说镜像的分层特性的时候说到镜像是只读的。而事实上当我们使用镜像启动一个容器的时候,我们其实是可以在容器里随意读写的,从结果上看,似乎与镜像的只读特性相悖。

我们继续看上面的图,其实可以看到在镜像的最上层,还有一个读写层。而这个读写层,即在容器启动时为当前容器单独挂载。每一个容器在运行时,都会基于当前镜像在其最上层挂载一个读写层。而用户针对容器的所有操作都在读写层中完成。一旦容器销毁,这个读写

层也随之销毁。

容器=镜像+读写层

而我们针对这个读写层的操作,主要基于两种方式:写时复制和用时分配。

2.1 写时复制

所有驱动都用到的技术——写时复制(CoW)。CoW就是copy-on-write,表示只在需要写时才去复制,这个是针对已有文件的修改场景。比如基于一个image启动多个Container,如果为每个Container都去分配一个image一样的文件系统,那么将会占用大量的磁盘空间。而

CoW技术可以让所有的容器共享image的文件系统,所有数据都从image中读取,只有当要对文件进行写操作时,才从image里把要写的文件复制到自己的文件系统进行修改。所以无论有多少个容器共享同一个image,所做的写操作都是对从image中复制到自己的文件系统

中的复本上进行,并不会修改image的源文件,且多个容器操作同一个文件,会在每个容器的文件系统里生成一个复本,每个容器修改的都是自己的复本,相互隔离,相互不影响。使用CoW可以有效的提高磁盘的利用率。

2.2 用时配置

用时分配是用在原本没有这个文件的场景,只有在要新写入一个文件时才分配空间,这样可以提高存储资源的利用率。比如启动一个容器,并不会为这个容器预分配一些磁盘空间,而是当有新文件写入时,才按需分配新空间。

三 Docker存储驱动

接下来我们说一说,这些分层的镜像是如何在磁盘中存储的。

docker提供了多种存储驱动来实现不同的方式存储镜像,下面是常用的几种存储驱动:

  • AUFS
  • OverlayFS
  • Devicemapper
  • Btrfs
  • ZFS

下面说一说AUFS、OberlayFS及Devicemapper,更多的存储驱动说明可参考:http://dockone.io/article/1513

3.1 AUFS

AUFS(AnotherUnionFS)是一种Union FS,是文件级的存储驱动。AUFS是一个能透明覆盖一个或多个现有文件系统的层状文件系统,把多层合并成文件系统的单层表示。简单来说就是支持将不同目录挂载到同一个虚拟文件系统下的文件系统。这种文件系统可以一层一

层地叠加修改文件。无论底下有多少层都是只读的,只有最上层的文件系统是可写的。当需要修改一个文件时,AUFS创建该文件的一个副本,使用CoW将文件从只读层复制到可写层进行修改,结果也保存在可写层。在Docker中,底下的只读层就是image,可写层就是

Container。结构如下图所示:

3.2 OverlayFS

Overlay是Linux内核3.18后支持的,也是一种Union FS,和AUFS的多层不同的是Overlay只有两层:一个upper文件系统和一个lower文件系统,分别代表Docker的镜像层和容器层。当需要修改一个文件时,使用CoW将文件从只读的lower复制到可写的upper进行修改,结果]

也保存在upper层。在Docker中,底下的只读层就是image,可写层就是Container。目前最新的OverlayFS为Overlay2。结构如下图所示:

3.3 Devicemapper

Device mapper是Linux内核2.6.9后支持的,提供的一种从逻辑设备到物理设备的映射框架机制,在该机制下,用户可以很方便的根据自己的需要制定实现存储资源的管理策略。前面讲的AUFS和OverlayFS都是文件级存储,而Device mapper是块级存储,所有的操作都是直

接对块进行操作,而不是文件。Device mapper驱动会先在块设备上创建一个资源池,然后在资源池上创建一个带有文件系统的基本设备,所有镜像都是这个基本设备的快照,而容器则是镜像的快照。所以在容器里看到文件系统是资源池上基本设备的文件系统的快照,并

没有为容器分配空间。当要写入一个新文件时,在容器的镜像内为其分配新的块并写入数据,这个叫用时分配。当要修改已有文件时,再使用CoW为容器快照分配块空间,将要修改的数据复制到在容器快照中新的块里再进行修改。Device mapper 驱动默认会创建一个

100G的文件包含镜像和容器。每一个容器被限制在10G大小的卷内,可以自己配置调整。结构如下图所示:

3.4 常用存储驱动对比

存储驱动 特点 优点 缺点 适用场景
AUFS 联合文件系统、未并入内核主线、文件级存储 作为docker的第一个存储驱动,已经有很长的历史,比较稳定,且在大量的生产中实践过,有较强的社区支持 有多层,在做写时复制操作时,如果文件比较大且存在比较低的层,可能会慢一些 大并发但少IO的场景
overlayFS 联合文件系统、并入内核主线、文件级存储 只有两层 不管修改的内容大小都会复制整个文件,对大文件进行修改显示要比小文件消耗更多的时间 大并发但少IO的场景
Devicemapper 并入内核主线、块级存储 块级无论是大文件还是小文件都只复制需要修改的块,并不是整个文件 不支持共享存储,当有多个容器读同一个文件时,需要生成多个复本,在很多容器启停的情况下可能会导致磁盘溢出 适合io密集的场景
Btrfs 并入linux内核、文件级存储 可以像devicemapper一样直接操作底层设备,支持动态添加设备 不支持共享存储,当有多个容器读同一个文件时,需要生成多个复本 不适合在高密度容器的paas平台上使用
ZFS 把所有设备集中到一个存储池中来进行管理 支持多个容器共享一个缓存块,适合内存大的环境 COW使用碎片化问题更加严重,文件在硬盘上的物理地址会变的不再连续,顺序读会变的性能比较差 适合paas和高密度的场景

3.5 AUFS VS OverlayFS

AUFS和Overlay都是联合文件系统,但AUFS有多层,而Overlay只有两层,所以在做写时复制操作时,如果文件比较大且存在比较低的层,则AUSF可能会慢一些。而且Overlay并入了linux kernel mainline,AUFS没有。目前AUFS已基本被淘汰

OverlayFS VS Device mapper

OverlayFS是文件级存储,Device mapper是块级存储,当文件特别大而修改的内容很小,Overlay不管修改的内容大小都会复制整个文件,对大文件进行修改显示要比小文件要消耗更多的时间,而块级无论是大文件还是小文件都只复制需要修改的块,并不是整个文件,在

这种场景下,显然device mapper要快一些。因为块级的是直接访问逻辑盘,适合IO密集的场景。而对于程序内部复杂,大并发但少IO的场景,Overlay的性能相对要强一些。


博主声明:本文的内容来源主要来自誉天教育晏威老师,由本人实验完成操作验证,需要的博友请联系誉天教育(http://www.yutianedu.com/),获得官方同意或者晏老师(https://www.cnblogs.com/breezey/)本人同意即可转载,谢谢!

DOCKER学习_006:Docker存储驱动的更多相关文章

  1. Docker学习笔记 — Docker私有仓库搭建

    Docker学习笔记 — Docker私有仓库搭建   目录(?)[-] 环境准备 搭建私有仓库 测试 管理仓库中的镜像 查询 删除 Registry V2   和Mavan的管理一样,Dockers ...

  2. Docker学习之Docker容器基本使用

    Docker学习之Docker容器基本使用 新建容器并启动 命令格式:docker run --options repository:tag 后台运行 命令格式:-d 已存在的容器相关操作 启动:do ...

  3. Docker学习之Docker镜像基本使用

    Docker学习之Docker镜像基本使用 获取镜像 命令格式:docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签] 例如: docker pull ...

  4. DOCKER学习_014:Docker存储补充

    在前面已经学习了Docker的存储,https://www.cnblogs.com/zyxnhr/p/11830238.html,现在对前面的内容的一个补充,具体请参考https://www.cnbl ...

  5. Docker学习笔记 - Docker的守护进程

    学习目标:  查看Docker守护进程的运行状态 启动.停止.重启Docker守护进程 Docker守护进程的启动选项 修改和查看Docker守护进程的启动选项 1.# 查看docker运行状态  方 ...

  6. DOCKER学习_010:Docker的文件系统以及制作镜像

    一 文件系统简介 1.1 Linux文件系统 LInux空间组成分为内核空间和用户空间(使用rootfs) linux文件系统由 bootes和 rootfs组成, bootes主要包含boot1 o ...

  7. Docker学习笔记 — Docker私有仓库搭建【转载】

    标签: Docker 2015-03-10 21:08 24190人阅读 评论(0) 收藏 举报  分类: Docker(26)    目录(?)[+]   和Mavan的管理一样,Dockers不仅 ...

  8. Docker学习笔记 - Docker容器内部署redis

    Docker学习笔记(2-4)Docker应用实验-redist server 和client的安装使用 一.获取redis容器(含客户端和服务端) 二.创建服务端容器 1.在终端A中运行redis- ...

  9. Docker学习笔记 - Docker客户端和服务端

    学习内容: Docker客户端和服务端的通讯方式:client和自定义程序 Docker客户端和服务端的连接方式:socket 演示Docker客户端和服务端之间用remote-api通讯:nc   ...

随机推荐

  1. 设置WPF窗口相对于非WPF窗口的位置

    原文:设置WPF窗口相对于非WPF窗口的位置 在前一个Post当中,指出了在WPF的WindowInteropHelper类中的一个BUG:通过WindowInteropHelper的Owner属性不 ...

  2. 『创造 Cloud Toolkit』贡献排行榜——如何参与定义一款 IDE 插件?

    自从我们团队在去年12月发布 Cloud Toolkit(一款让开发部署效率提速 8 倍的 IDE 插件)以来,已帮助数以万计的开发者们提高了云上的部署效率,期间,开发者们不仅积极地向 Cloud T ...

  3. 洞见数据库前沿 阿里云数据库最强阵容 DTCC 2019 八大亮点抢先看

    摘要: 作为DTCC的老朋友和全球领先的云计算厂商,阿里云数据库团队受邀参加本次技术盛会,不仅将派出重量级嘉宾阵容,还会为广大数据库业内人士和行业用户奉上8场精彩议题.下面小编就为大家提前梳理了8大亮 ...

  4. spring配置文件各个属性详解

    一.引用外部属性文件 <bean id="propertyConfigurer" class="org.springframework.beans.factory. ...

  5. url地址栏参数<==>对象(将对象转换成地址栏的参数以及将地址栏的参数转换为对象)的实用函数

    /** * @author web得胜 * @param {Object} obj 需要拼接的参数对象 * @return {String} * */ function obj2qs(obj) { i ...

  6. Android ListView批量选择(全选、反选、全不选)

    APP的开发中,会常遇到这样的需求:批量取消(删除)List中的数据.这就要求ListVIew支持批量选择.全选.单选等等功能,做一个比较强大的ListView批量选择功能是很有必要的,那如何做呢? ...

  7. Chrome接口请求一直是pending状态,但接口实际上是正常的

    1.现象 个别机器突然出现Chrome访问我司产品异常,本该通过接口获取的数据没有呈现,之前都是好好的,而且其他机器同样用同版本Chrome访问正常. 出现问题的机器重装Chrome问题依然存在,直到 ...

  8. pip、conda 换国内源,大大提高下载速度

    https://www.jianshu.com/p/b2d53904dd37 源就是下载地址了,换到国内的源下载速度真的快了近10倍,这里都用了清华的源 pip 只要新建一个配置文件,写上路径就行了 ...

  9. Python--day72--Django内置的serializers序列化介绍

    序列化 Django内置的serializers def books_json(request): book_list = models.Book.objects.all()[0:10] from d ...

  10. HDU 1251 裸的字典树、入门题

    裸的字典树还是挺简单的. 四个基本操作建立.查找.插入.删除 建立新结点我是用的c++中 new操作.当然也可以用malloc,都方便 不过指针阿.地址阿.这其中关系什么的我貌似还不是很清楚阿. 因为 ...