Docker核心架构原理的深入分析

一、前言

由于平常工作中对Docker使用还是比较频繁的,但是一般都是基础的功能使用,并未对其核心架构原理做梳理,因此抽空简单总结一下这玩意的一些核心概念点知识,以备后面求职工作时可以更为深入地了解这个容器化工具。

二、Docker简介

这个就再简单说一下,因为以前的一些文章也经常提到过基础介绍了。

Docker的英文翻译是”搬运工“的意思,他搬运的东西就是我们常说的集装箱Container,Container 里面装的是任意类型的App,我们的开发人员可以通过Docker 将App变成一种标准化的、可移植的、自管理的组件,我们可以在任何主流的操作系统中开发、调试和运行。

从他的官方介绍图也就不难看出了:一个运货物的鲸鱼 。可以说非常形象,从概念上来看Docker和我们传统的虚拟机比较类似,只是更加轻量级,更加方便使用,非常适合高密度环境以及中小型部署,可以让我们用更少的资源做更多的事情。

和传统虚拟机相比较他主要有以下几点区别:

  1. 传统的虚拟机需要模拟整台机器包括硬件,每台虚拟机都需要有自己的操作系统,虚拟机一旦被开启,预分配给他的资源将全部被占用,每一个虚拟机包括应用,必要的二进制和库,以及一个完整的用户操作系统;而容器技术是和我们的宿主机共享硬件资源及操作系统可以实现资源的动态分配。
  2. 虚拟化技术依赖的是物理CPU和内存,是硬件级别的;而我们的Docker是构建在操作系统层面的,利用操作系统的容器化技术,所以Docker同样的可以运行在虚拟机上面
  3. 虚拟机中的系统就是我们常说的操作系统镜像,比较复杂;而Docker比较轻量级,我们可以使用Docker部署一个独立的MySQL,就像类似于在虚拟机当中安装一个MySQL应用,但是我们用Docker部署的应用是完全隔离的。
  4. 在传统的虚拟化技术是通过快照来保存的;而Docker引用了类似于源码的管理机制,将容器的快照历史版本一一记录下来,切换成本之低
  5. 传统的虚拟化技术在构建系统的时候非常复杂;而Docker可以通过一个简单的Dockerfile文件来构建整个容器,更重要的是Dockerfile可以手动编写,这样应用开发人员可以通过发布Dockerfile来定义应用的环境和依赖,这样对于持续交付非常有利。

这些区别我们可以通过二者的结构图来看看:

不难看出,虚拟机和容器都是在硬件和操作系统以上的,虚拟机有 Hypervisor 层,Hypervisor 是整个虚拟机的核心所在。它为虚拟机提供了虚拟的运行平台,管理虚拟机的操作系统运行。每个虚拟机都有自己的系统和系统库以及应用。

而容器没有 Hypervisor 这一层,并且每个容器是和宿主机共享硬件资源及操作系统,那么由 Hypervisor 带来性能的损耗,在 linux 容器这边是不存在的。

但是虚拟机技术也有其优势,能为应用提供一个更加隔离的环境,不会因为应用程序的漏洞给宿主机造成任何威胁。同时还支持跨操作系统的虚拟化,例如你可以在 linux 操作系统下运行 windows 虚拟机。

从虚拟化层面来看,传统虚拟化技术是对硬件资源的虚拟,容器技术则是对进程的虚拟,从而可提供更轻量级的虚拟化,实现进程和资源的隔离。

从架构来看,Docker 比虚拟化少了两层,取消了 hypervisor 层和 GuestOS 层,使用 Docker Engine 进行调度和隔离,所有应用共用主机操作系统,因此在体量上,Docker 较虚拟机更轻量级,在性能上优于虚拟化,接近裸机性能。

Docker Engine

这个应该是我们安装docker经常都能见到的一个东西,那他具体是个啥呢?

实际上Docker Engine是一种开源容器化技术,用于构建和容器化我们的应用程序。Docker Engine充当具有以下功能的客户端服务器应用程序,他主要包含下面几个组件:

  • 常驻运行的守护进程的服务器Dockerd
  • API,用于指定程序可以用来与Docker守护程序进行对话和指示的接口。
  • 命令行界面CLI,通过和REST API进行交互

    CLI使用Docker API通过脚本或直接CLI命令来控制Docker守护程序或与Docker守护程序进行交互。许多其他Docker应用程序都使用基础API和CLI。守护程序创建和管理Docker对象,例如映像,容器,网络和卷。

     

    Docker架构 

    Docker使用了C/S体系架构,Docker客户端与Docker守护进程通信,Docker守护进程负责构建,运行和分发Docker容器。Docker客户端和守护进程可以在同一个系统上运行,也可以将Docker客户端连接到远程Docker守护进程。Docker客户端和守护进程使用REST API通过UNIX套接字或网络接口进行通信。

    1. Docker Damon DockerD用来监听Docker API的请求和管理Docker对象,比如镜像、容器、网络和Volume。
    2. Docker Client docker client是我们和Docker进行交互的最主要的方式方法,比如可以通过docker run来运行一个容器,然后我们的这个client会把命令发送给上面的Docker。
    3. Docker Registry 用来存储Docker镜像的仓库,Docker Hub是Docker官方提供的一个公共仓库,而且Docker默认也是从Docker Hub上查找镜像的,当然你也可以很方便的运行一个私有仓库,当我们使用docker pull或者docker run命令时,就会从我们配置的Docker镜像仓库中去拉取镜像,使用docker push命令时,会将我们构建的镜像推送到对应的镜像仓库中。
    4. Images 镜像,镜像是一个制度模板,带有Docker容器的说明,一般来说的,镜像会基于另外的一些基础镜像上面安装一个Nginx服务器,这样就可以构建一个属于我们自己的镜像了。
    5. Containers 容器,容器是一个镜像的可运行的实例,可以使用Docker REST API或者CLI来操作容器,容器的实质是进程,但与直接在宿主执行的实例进程不同,容器进程属于自己的独立的命名空间。因此容器可以拥有自己的root文件系统、自己的网络配置、自己的进程空间、甚至自己的用户ID。容器内的经常是运行在一个隔离的环境里,使用起来,就好像在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。

    三、Docker的核心概念

    Docker namespace

    Linux 内核2.4.19中开始陆续引用了namespace概念。目的是将某个特定的全局系统资源(global system resource)通过抽象方法使得namespace中的进程看起来拥有它们自己的隔离的全局系统资源实例。为运行中的进程提供了隔离,从而限制了它们对系统资源的访问,而运行中的进程却没有意识到这些限制。

    命名空间是Linux内核强大的特性。每个容器都有自己的命名空间,运行在其中的应用都是在独立操作系统中运行一样。命名空间保证了容器之间彼此互不影响。

    Docker 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,/tmp)

    Linux系统在启动时,rootfs首先会被挂载为只读模式,然后在启动完成后被修改为读写模式,随后它们就可以被修改了假设Dockerfile内容如下:

    FROM ubuntu:14.04
    ADD run.sh /
    VOLUME /data
    CMD ["./run.sh"]

    那么联合文件系统对应的层次结构如下图所示:

    图中的顶上两层,是Docker为Docker容器新建的内容,而这两层属于容器范畴。这两层分别为Docker容器的初始层(init Layer)与可读写层(Read-write Layer)

    初始层: 大多是初始化容器环境时,与容器相关的环境信息,如容器主机名,主机host信息以及域名服务文件等。

    读写层: Docker容器内的进程只对可读可写层拥有写权限,其它层进程而言都是只读的(Read-Only)。关于VOLUME以及容器的host、hostname、resolv.conf文件都会挂载到这里

    • FROM ubuntu:14.04 设置基础镜像,此时会使用Ubuntu:14.04作为基础镜像
    • ADD run.sh / 将Dockerfile所在目录下的run.sh加至镜像的根目录,此时新一层的镜像只有一项内容,即根目录下的run.sh
    • VOLUME /data 设置镜像的存储,此VOLUME在容器内部的路径为/data。此时并未在新一层的镜像中添加任何文件,但是更新了镜像的json文件,以便通过此镜像启动容器时获取这方面的信息(下面会有详细的介绍)
    • CMD ["./run.sh"] 设置镜像的默认执行入口,此命令同样不会在新建镜像中添加任何文件,仅仅在上一层镜像json文件的基础上更新新的镜像的json文件
      Docker CGroups

      Docker CGroups(也称为控制组)是Linux内核的一个特性,用于限制和隔离资源的使用。Docker使用CGroups来管理和分配系统资源,以确保容器之间的资源互不干扰。

      之前也提到过,Docker容器使用Linux namespace来隔离其运行环境,使得容器中的进程看起来就像在一个独立的环境中运行。但是光有运行环境隔离还不够,因为这些进程还是可以不受限制地使用系统资源,比如网络、磁盘、CPU以及内存等。关于其目的,是为了防止它占用了太多的资源而影响到其它进程;另一方面,在系统资源耗尽的时候,Linux内核会触发OOM (out of memory killer,OOM会在系统内存耗尽的情况下跳出来,选择性的干掉一些进程以求释放一些内存)这会让一些被杀掉的进程成了无辜的替死鬼,因此为了让容器中的进程更加可控,Docker使用Linux CGroups来限制容器中的进程允许使用的系统资源。

      CGroups允许管理员将一组进程组织在一起,并为它们分配特定资源的限制。这些资源可以包括CPU、内存、磁盘I/O、网络带宽等。通过使用CGroups,Docker可以为每个容器分配一定数量的资源,并确保容器不会超过其限制。

      CGroups通过将进程分配到不同的组中,同时使用Linux内核的调度器来控制资源分配。每个CGroup都有一个层次结构,可以将进程分配到不同的组中,从而实现资源的隔离和限制。

      Docker使用CGroups来限制容器的资源使用。例如,我们可以使用CGroups来限制容器使用的CPU的数量和频率,以防止容器占用所有系统资源。也可以使用CGroups来限制容器使用的内存量,以防止容器使用过多的内存导致系统宕机。

      四、Docker存储驱动

      Docker最开始采用AUFS作为文件系统,也得益于AUFS分层的概念,实现了多个Container可以共享一个image。但是由于AUFS未并入Linux内核,且只支持Ubuntu,考虑到兼容性问题,在Docker 0.7 版本中引入了存储驱动,目前,Docker支持AUFS、Btrfs、Devicemapper、OverlayFS、ZFS五种存储驱动。

      (1)原理说明

      写时复制 (CoW)

      所有驱动都用到的技术————写时复制,Cow全称copy-on-write,表示只是在需要写时才去复制,这个是针对已有文件的修改场景。比如基于一个image启动多个Container,如果每个Container都去分配一个image一样的文件系统,那么将会占用大量的磁盘空间。而CoW技术可以让所有的容器共享image的文件系统,所有数据都从image中读取,只有当要对文件进行写操作时,才从image里把要写的文件复制到自己的文件系统进行修改。所以无论有多少个容器共享一个image,所做的写操作都是对从image中复制到自己的文件系统的副本上进行,并不会修改image的源文件,且多个容器操作同一个文件,会在每个容器的文件系统里生成一个副本,每个容器修改的都是自己的副本,互相隔离,互不影响。使用CoW可以有效的提高磁盘的利用率。

      用时分配 (allocate-on-demand)

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

      (2)存储驱动介绍

      AUFS

      AUFS (AnotherUnionFS)是一种UnionFS,是文件级的存储驱动。AUFS能透明覆盖一或多个现有文件系统的层状文件系统,把多层合并成文件系统的单层表示。简单来说就是支持将不同目录挂载到同一个虚拟文件下的文件系统。这种文件系统可以一层一层地叠加修改文件。无论底下有多少层都是只读的,只有最上层的文件系统是可写的。当需要修改一个文件时,AUFS创建该文件的一个副本,使用CoW将文件从只读层复制到可写层进行修改,结果也保存在可写层。在Docker中,只读层就是image,可写层就是Container。

       OverlayFS

      OverlayFS是一种和AUFS很类似的文件系统,与AUFS相比,OverlayFS有以下特性;

      1) 更简单地设计;

      2) 从Linux 3.18开始,就加入了Linux内核主线;

      3) 速度更快

      因此,OverlayFS在Docker社区关注提高很快,被很多人认为是AUFS的继承者。Docker的overlay存储驱动利用了很多OverlayFS特性来构建和管理镜像与容器的磁盘结构

      从Docker1.12起,Docker也支持overlay2存储驱动,相比于overlay来说,overlay2在inode优化上更加高效,但overlay2驱动只兼容Linux kernel4.0以上的版本

      注意: 自从OverlayFS加入kernel主线后,它的kernel模块中的名称就从overlayfs改为overlay了

      OverlayFS使用两个目录,把一个目录置放于另一个智商,并且对外提供单个统一的视角。这两个目录通常被称作层,这个分层的技术被称作union mount。术语上,下层的目录叫做lowerdir,上层的叫做upperdir。对外展示的统一视图称作merged。

      注意镜像层和容器层是如何处理相同文件的: 容器层(upperdir)的文件是显性的,会隐藏镜像层(lowerdir)相同文件的存在。并在容器映射(merged)显示出统一视图。

      overlay驱动只能工作在两层之上,也就是说多层镜像不能用多层OverlayFS实现。替代的,每个镜像层在/var/lib/docker/overlay中用自己的目录来实现,使用硬链接这种有效利用空间的方法,来引用底层分享的数据。

      注意: Docker1.10之后,镜像层ID和/var/lib/docker中的目录名不再一一对应。

      创建一个容器,overlay驱动联合镜像层和一个新目录给容器。镜像顶层中的overlay是只读lowerdir,容器的新目录是可写的upperdir

      OverlayFS (overlay2)镜像分层与共享

      overlay驱动只工作在一个lower OverlayFS层之上,因此需要硬链接来实现多层镜像,但overlay2驱动原生地支持多层lower OverlayFS镜像(最多128层)。因此overlay2驱动在合层相关的命令(如build何commit)中提供了更好的性能,与overlay驱动对比,减少了inode消耗

      容器overlay读写

      有三种场景,容器会通过overlay只读访问文件:

      1. 容器层不存在的文件 如果容器只读打开一个文件,但该容器不在容器层(upperdir),就要从镜像层(lowerdir)中读取。这会引起很小的性能消耗
      2. 只存在于容器层的文件 如果容器只读权限打开一个文件,并且容器只存在于容器层(upperdir)而不是镜像层(lowerdir),那么直接从镜像层读取文件,无额外的性能损耗
      3. 文件同时存在于容器层和镜像层 那么会读取容器层的文件,因为容器层(upperdir)隐层了镜像层(lowerdir)的同名文件,因此,也没有额外的性能损耗

      有以下场景容器修改文件

      第一次写一个文件,容器第一次写一个已经存在的文件,容器层不存在这个文件。overlay/overlay2驱动执行copy-up操作,将文件从镜像层拷贝到容器层。然后容器修改容器层新拷贝的文件

      • copy-up 操作只发生在第一次写文件时,后续的对同一个文件的鞋操作都是直接针对拷贝到容器层的文件
      • OverlayFS只工作在两层中。这比AUFS要在多层镜像中查找时性能要好

        删除文件和目录 删除文件时,容器会在镜像层创建一个whiteout文件,而镜像层的文件并没有删除,但是whiteout文件会隐藏它。容器中删除一个目录,容器层会创建一个不透明目录,这和whiteout文件隐藏镜像层的文件类似

        重命名目录 只有在源文件和目的路径都在顶层容器层时,才允许执行rename操作,否则返回EXDEV。因此,应用需要能够处理EXDEV,并且回滚操作,执行替代的"拷贝和删除"策略

        在Docker中配置overlay2 存储驱动

        为了给Docker配置overlay存储驱动,你的Docker host必须在Linux kernel3.18版本之上,并且加载了overlay内核驱动。对于overlay2驱动,kernel版本必须在4.0或以上。OverlayFS可以运行在大多数Linux文件系统之上。这里我用Ubuntu22.04进行配置:

        #1.停止Docker
        root@young-virtual-machine:~/桌面#systemctl stop docker
         
        #2.检查kernel版本,确定overlay的内核模块是否加载
        root@young-virtual-machine:~/桌面# uname -r
        6.5.0-26-generic
        root@young-virtual-machine:~/桌面# lsmod |grep overlay
        overlay               196608  8
         
        #如果没有过滤出overlay模块,说明驱动没有加载,使用下面方法进行加载
        root@young-virtual-machine:~/桌面# modprobe overlay

        然后使用verlay2存储来启动docker,配置方法有2种:

        1. 在Docker的启动配置文件中添加--storage-driver=overlay2的标志到DOCKER_OPTS中,这样可以持久化配置。
        2. 或者将配置持久化到配置文件/etc/docker/daemon.json中 :"storage-driver": "overlay2"。
        root@young-virtual-machine:~/桌面# cat /etc/docker/daemon.json 
        {
         "exec-opts": ["native.cgroupdriver=systemd"],
         "registry-mirrors": ["https://hjvrgh7a.mirror.aliyuncs.com"],
         "log-driver": "json-file",
         "log-opts": {
         "max-size": "100m"
         },
         "storage-driver": "overlay2"
        }
        root@young-virtual-machine:~/桌面# docker info
        Client: Docker Engine - Community
         Version:    25.0.4
         Context:    default
         Debug Mode: false
         Plugins:
          buildx: Docker Buildx (Docker Inc.)
            Version:  v0.13.0
            Path:     /usr/libexec/docker/cli-plugins/docker-buildx
          compose: Docker Compose (Docker Inc.)
            Version:  v2.24.7
            Path:     /usr/libexec/docker/cli-plugins/docker-compose
        Server:
         Containers: 8
          Running: 8
          Paused: 0
          Stopped: 0
         Images: 8
         Server Version: 25.0.4
         Storage Driver: overlay2
          Backing Filesystem: extfs
          Supports d_type: true
          Using metacopy: false
          Native Overlay Diff: true
          userxattr: false
         Logging Driver: json-file
         Cgroup Driver: systemd
         Cgroup Version: 2
         Plugins:
          Volume: local
          Network: bridge host ipvlan macvlan null overlay
          Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
         Swarm: inactive
         Runtimes: io.containerd.runc.v2 runc
         Default Runtime: runc
         Init Binary: docker-init
         containerd version: ae07eda36dd25f8a1b98dfbf587313b99c0190bb
         runc version: v1.1.12-0-g51d5e94
         init version: de40ad0
         Security Options:
          apparmor
          seccomp
           Profile: builtin
          cgroupns
         Kernel Version: 6.5.0-26-generic
         Operating System: Ubuntu 22.04.4 LTS
         OSType: linux
         Architecture: x86_64
         CPUs: 4
         Total Memory: 3.78GiB
         Name: young-virtual-machine
         ID: 83b0f441-c3fa-446a-b3b1-85ae528fd8ef
         Docker Root Dir: /var/lib/docker
         Debug Mode: false
         Experimental: false
         Insecure Registries:
          127.0.0.0/8
         Registry Mirrors:
          https://hjvrgh7a.mirror.aliyuncs.com/
         Live Restore Enabled: false

        此时可以看到Docker已经使用overlay2作为存储引擎了。

        Devicemapper

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

        Devicemapper驱动会先在块设备上创建一个资源池,然后在资源池上创建一个带有文件系统的基本设备,所有镜像都是这个基本设备的快照,而容器则是镜像的快照。所以在容器看到文件系统是资源池上基本设备的文件系统的快照,并不是为容器分配空间。当要修改已有文件时,再使用CoW为容器快照分配块空间,将要修改的数据复制到容器快照中的新快里再进行修改。

        Devicemapper驱动默认会创建一个100G的文件包含镜像和容器,每个容器被限制在10G大小的卷内,可以自己配置调整。

        Btrfs

        Btrfs被称为下一代写时复制的文件系统,并入Linux内核,也是文件级存储,但可以像Devicemapper一直被操作底层设备。Btrfs把文件系统的一部分配置为一个完整的子文件系统,称之为subvolume。采用subvolume,一个大的文件系统可以被划分多个子文件系统,这些子文件系统共享底层的设备空间,在需要磁盘空间时便从底层设备中分配,比如Btrfs支持动态添加设备。用户在系统中新增加硬盘后,可以使用Btrfs的命令将该设备添加到文件系统中。

        Btrfs把一个大的文件系统当成一个资源池,配置成多个完整的子文件系统,还可以往资源池里加新的子文件系统,而基础镜像则是子文件系统的快照,每个子镜像和容器都有自己的快照,这些快照都是subvolume的快照。

        当写入一个新文件时,在容器的快照里为其分配了一个新的数据块,文件写在这个空间里,叫做用时分配。而当修改已有文件时,使用CoW复制分配一个新的原始数据和快照,在这个新分配的空间变更数据,等结束后再进行相关的数据结构指引到新子文件系统和快照,原来的原始数据和快照没有指针指示,被覆盖。

        ZFS

        ZFS文件系统是一个革命性的全新的文件系统,它从根本上改变了文件系统的管理方式,ZFS完全抛弃了"卷管理",不再创建虚拟的卷,而是把所有设备集中到一个存储池中来进行管理,用"存储池"的概念来管理物理存储空间。

        过去,文件系统都是构建在物理设备之上的,并为数据提供冗余,"卷管理"的概念提供了一个单设备的映像。而ZFS创建在虚拟的,被称为"zpools"的存储池上。每个存储池由若干虚拟设备(virtual devices,vdevs)组成。这些虚拟设备可以是原始磁盘,也可以是一个RAID的镜像设备,或者是非标准RAID等级的多磁盘组。这样zpool上的文件系统可以使用这些虚拟设备的总存储容量。

        在Docker宿主机上使用ZFS存储驱动,镜像的最底层(base layer)是一个ZFS文件系统。每一个镜像子层都是一个基于下层ZFS快照的ZFS克隆实体。一个容器的文件系统是一个ZFS克隆,下层是从镜像层创建的快照。所有ZFS数据实体都是从一个共用的zpool分配空间。他们间的关联关系如下:

        首先从zpool里分配一个ZFS文件系统给镜像的基础层,而其它镜像层则是这个ZFS文件系统快照的克隆。克隆是可写的,并根据需要在zpool中分配物理空间。而快照是只读的,让base层成为一个不变的实体。当容器启动时再将一个读写层加入到镜像的最上层。

        当要写一个新文件时,使用按需分配,一个新的数据块从zpool里生成,新的数据写入这个块,而这个新的空间存储于容器(ZFS的克隆)里。当要修改一个已存在的文件时,使用写时复制,分配一个新空间并把原始数据复制到新空间完成修改。

        存储驱动的对比及适应场景
        存储驱动名称特点优点缺点适用场景

        AUFS

        联合文件系统未并入内核主线文件存储

        作为doker的第一个存储驱动,新版默认使用Devicemapper有多层,在进行写复制操作时,如果文件比较大且勋在比较低的层,可能较慢大并发但少IO的场景
        OverlayFS联合文件系统未并入内核主线文件存储只有两层不管修改的内容大小,都会复制整个文件,对大文件进行修改时要比小文件要消耗更多时间大并发但少IO的场景
        Devicemapper并入内核主线块级存储块级无论是大文件还是小文件都只复制需要修改的块并不是整个文件不支持共享存储,表示当有多个容器读同一个文件时,需要生成多个副本,在很多容器后停情况下可能会导致磁盘溢出适合IO密集的场景
        Btrfs并入内核主线块级存储可以像Devicemapper直接操作系统底层设备支持动态添加设备不支持共享存储,当有多个容器读同一个文件时,需要生成多个副本不适合在高密度容器的PAAS平台
        ZFS把所有设备集中到一个存储池来进行管理支持多个容器共享一个缓存块适合内存大的环境COW使碎片化问题更加严重文件在硬盘上的物理地址会变得不再连续,顺序会变得性能比较差。适合PAAS和高度密集的场景

        一般来说,overlay2驱动更快一些,几乎肯定比AUFS和devicemapper更快,在某些情况下,可能比Btrfs也快。在使用overlay2存储驱动时,需要注意以下几点:

        • Page Caching 页缓存
        • OverlayFS支持页缓存,也就是说如果多个容器访问同一个文件,可以共享一个或多个页缓存选项。这使得overlay2驱动高效地利用了内存,是Pass平台或者高密度场景很好的选择
        • copy_up 和AUFS一样,在容器第一次修改文件时,OverlayFS都需要执行copy_up操作,这会给操作带来一些延迟————尤其这个拷贝很大的文件时,不过一旦文件已经执行了这个向上拷贝的操作后,所有后续对这个文件的操作都只针对这份容器层的拷贝而已
        • Inode limits 使用overlay存储驱动可能导致过多的inode消耗,尤其是Docker host上镜像和容器的数目增长时。大量的镜像或者很多容器启停,会迅速消耗该Docker host的inode。但是overlay2 存储驱动不存在这个问题

          overlay2存储驱动已经成为了Docker首选存储驱动,并且性能优于AUFS和devicemapper。不过,也带来了一些与其他文件系统不兼容性,如对open和rename操作的支持,另外,overlay和overlay2相比,overlay2支持了多层镜像,优化了inode的使用。