Docker容器化实战第六课 文件存储驱动

2023-11-22 02:40

本文主要是介绍Docker容器化实战第六课 文件存储驱动,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

14 文件存储驱动:AUFS 文件系统原理及生产环境的最佳配置

我们知道,Docker 主要是基于 Namespace、cgroups 和联合文件系统这三大核心技术实现的。前面的课时我详细讲解了 Namespace 和 cgroups 的相关原理,那么你知道联合文件系统是什么吗?它的原理又是什么呢?

首先我们来了解一下什么是联合文件系统。

什么是联合文件系统

联合文件系统(Union File System,Unionfs)是一种分层的轻量级文件系统,它可以把多个目录内容联合挂载到同一目录下,从而形成一个单一的文件系统,这种特性可以让使用者像是使用一个目录一样使用联合文件系统。

那联合文件系统对于 Docker 是一个怎样的存在呢?它可以说是 Docker 镜像和容器的基础,因为它可以使 Docker 可以把镜像做成分层的结构,从而使得镜像的每一层可以被共享。例如两个业务镜像都是基于 CentOS 7 镜像构建的,那么这两个业务镜像在物理机上只需要存储一次 CentOS 7 这个基础镜像即可,从而节省大量存储空间。

说到这儿,你有没有发现,联合文件系统只是一个概念,真正实现联合文件系统才是关键,那如何实现呢?其实实现方案有很多,Docker 中最常用的联合文件系统有三种:AUFS、Devicemapper 和 OverlayFS。

今天我主要讲解 Docker 中最常用的联合文件系统里的 AUFS,为什么呢?因为 AUFS 是 Docker 最早使用的文件系统驱动,多用于 Ubuntu 和 Debian 系统中。在 Docker 早期,OverlayFS 和 Devicemapper 相对不够成熟,AUFS 是最早也是最稳定的文件系统驱动。 Devicemapper 和 OverlayFS 联合文件系统,我将在第 15 和 16 课时为你详细剖析 。

接下来,我们就看看如何配置 Docker 的 AUFS 模式。

如何配置 Docker 的 AUFS 模式

AUFS 目前并未被合并到 Linux 内核主线,因此只有 Ubuntu 和 Debian 等少数操作系统支持 AUFS。你可以使用以下命令查看你的系统是否支持 AUFS:

$ grep aufs /proc/filesystems
nodev   aufs

执行以上命令后,如果输出结果包含aufs,则代表当前操作系统支持 AUFS。AUFS 推荐在 Ubuntu 或 Debian 操作系统下使用,如果你想要在 CentOS 等操作系统下使用 AUFS,需要单独安装 AUFS 模块(生产环境不推荐在 CentOS 下使用 AUFS,如果你想在 CentOS 下安装 AUFS 用于研究和测试,可以参考这个链接),安装完成后使用上述命令输出结果中有aufs即可。
当确认完操作系统支持 AUFS 后,你就可以配置 Docker 的启动参数了。

先在 /etc/docker 下新建 daemon.json 文件,并写入以下内容:

{"storage-driver": "aufs"
}

然后使用以下命令重启 Docker:

$ sudo systemctl restart docker

Docker 重启以后使用docker info命令即可查看配置是否生效:

$ sudo docker info
Client:Debug Mode: false
Server:Containers: 0Running: 0Paused: 0Stopped: 0Images: 1Server Version: 19.03.12Storage Driver: aufsRoot Dir: /var/lib/docker/aufsBacking Filesystem: extfsDirs: 1Dirperm1 Supported: true

可以看到 Storage Driver 已经变为 aufs,证明配置已经生效,配置生效后就可以使用 AUFS 为 Docker 提供联合文件系统了。

配置好 Docker 的 AUFS 联合文件系统后,你一定很好奇 AUFS 到底是如何工作的呢?下面我带你详细学习一下 AUFS 的工作原理。

AUFS 工作原理

AUFS 是如何存储文件的?

AUFS 是联合文件系统,意味着它在主机上使用多层目录存储,每一个目录在 AUFS 中都叫作分支,而在 Docker 中则称之为层(layer),但最终呈现给用户的则是一个普通单层的文件系统,我们把多层以单一层的方式呈现出来的过程叫作联合挂载。

Lark20201014-171313.png

图 1 AUFS 工作原理示意图

如图 1 所示,每一个镜像层和容器层都是 /var/lib/docker 下的一个子目录,镜像层和容器层都在 aufs/diff 目录下,每一层的目录名称是镜像或容器的 ID 值,联合挂载点在 aufs/mnt 目录下,mnt 目录是真正的容器工作目录。

下面我们针对 aufs 文件夹下的各目录结构,在创建容器前后的变化做详细讲述。

当一个镜像未生成容器时,AUFS 的存储结构如下。

  • diff 文件夹:存储镜像内容,每一层都存储在以镜像层 ID 命名的子文件夹中。

  • layers 文件夹:存储镜像层关系的元数据,在 diif 文件夹下的每个镜像层在这里都会有一个文件,文件的内容为该层镜像的父级镜像的 ID。

  • mnt 文件夹:联合挂载点目录,未生成容器时,该目录为空。

当一个镜像已经生成容器时,AUFS 存储结构会发生如下变化。

  • diff 文件夹:当容器运行时,会在 diff 目录下生成容器层。

  • layers 文件夹:增加容器层相关的元数据。

  • mnt 文件夹:容器的联合挂载点,这和容器中看到的文件内容一致。

以上便是 AUFS 的工作原理,那你知道容器的在工作过程中是如何使用 AUFS 的吗?

AUFS 是如何工作的?

AUFS 的工作过程中对文件的操作分为读取文件和修改文件。下面我们分别来看下 AUFS 对于不同的文件操作是如何工作的。

1. 读取文件

当我们在容器中读取文件时,可能会有以下场景。

  • 文件在容器层中存在时:当文件存在于容器层时,直接从容器层读取。

  • 当文件在容器层中不存在时:当容器运行时需要读取某个文件,如果容器层中不存在时,则从镜像层查找该文件,然后读取文件内容。

  • 文件既存在于镜像层,又存在于容器层:当我们读取的文件既存在于镜像层,又存在于容器层时,将会从容器层读取该文件。

2. 修改文件或目录

AUFS 对文件的修改采用的是写时复制的工作机制,这种工作机制可以最大程度节省存储空间。

具体的文件操作机制如下。

  • 第一次修改文件:当我们第一次在容器中修改某个文件时,AUFS 会触发写时复制操作,AUFS 首先从镜像层复制文件到容器层,然后再执行对应的修改操作。

AUFS 写时复制的操作将会复制整个文件,如果文件过大,将会大大降低文件系统的性能,因此当我们有大量文件需要被修改时,AUFS 可能会出现明显的延迟。好在,写时复制操作只在第一次修改文件时触发,对日常使用没有太大影响。

  • 删除文件或目录:当文件或目录被删除时,AUFS 并不会真正从镜像中删除它,因为镜像层是只读的,AUFS 会创建一个特殊的文件或文件夹,这种特殊的文件或文件夹会阻止容器的访问。

下面我们通过一个实例来演示一下 AUFS 。

AUFS 演示

准备演示目录和文件

首先我们在 /tmp 目录下创建 aufs 目录:

$ cd /tmp
/tmp$ mkdir aufs

准备挂载点目录:

/tmp$ cd aufs
/tmp/aufs$ mkdir mnt

接下来准备容器层内容:

## 创建镜像层目录
/tmp/aufs$ mkdir container1
## 在镜像层目录下准备一个文件
/tmp/aufs$ echo Hello, Container layer! > container1/container1.txt

最后准备镜像层内容:

## 创建两个镜像层目录
/tmp/aufs$ mkdir image1 && mkdir image2
## 分别写入数据
/tmp/aufs$ echo Hello, Image layer1! > image1/image1.txt
/tmp/aufs$ echo Hello, Image layer2! > image2/image2.txt

准备好的目录和文件结构如下:

/tmp/aufs$ tree .
.
|-- container1
|   `-- container1.txt
|-- image1
|   `-- image1.txt
|-- image2
|   `-- image2.txt
`-- mnt
4 directories, 3 files
创建 AUFS 联合文件系统

使用 mount 命令可以创建 AUFS 类型的文件系统,命令如下:

/tmp/aufs$ sudo mount -t aufs -o dirs=./container1:./image2:./image1  none ./mnt

mount 命令创建 AUFS 类型文件系统时,这里要注意,dirs 参数第一个冒号默认为读写权限,后面的目录均为只读权限,与 Docker 容器使用 AUFS 的模式一致。

执行完上述命令后,mnt 变成了 AUFS 的联合挂载目录,我们可以使用 mount 命令查看一下已经创建的 AUFS 文件系统:

/tmp/aufs$ mount -t aufs
none on /tmp/aufs/mnt type aufs (rw,relatime,si=4174b83d649ffb7c)

我们每创建一个 AUFS 文件系统,AUFS 都会为我们生成一个 ID,这个 ID 在 /sys/fs/aufs/ 会创建对应的目录,在这个 ID 的目录下可以查看文件挂载的权限。

tmp/aufs$ cat /sys/fs/aufs/si_4174b83d649ffb7c/*
/tmp/aufs/container1=rw
/tmp/aufs/image2=ro
/tmp/aufs/image1=ro
64
65
66

可以看到 container1 目录的权限为 rw(代表可读写),image1 和 image2 的权限为 ro(代表只读)。

为了验证 mnt 目录下可以看到 container1、image1 和 image2 目录下的所有内容,我们使用 ls 命令查看一下 mnt 目录:

/tmp/aufs$ ls -l mnt/
total 12
-rw-rw-r-- 1 ubuntu ubuntu 24 Sep  9 16:55 container1.txt
-rw-rw-r-- 1 ubuntu ubuntu 21 Sep  9 16:59 image1.txt
-rw-rw-r-- 1 ubuntu ubuntu 21 Sep  9 16:59 image2.txt

可以看到 mnt 目录下已经出现了我们准备的所有镜像层和容器层的文件。下面让我们来验证一下 AUFS 的写时复制。

验证 AUFS 的写时复制

AUFS 的写时复制是指在容器中,只有需要修改某个文件时,才会把文件从镜像层复制到容器层,下面我们通过修改联合挂载目录 mnt 下的内容来验证下这个过程。

我们使用以下命令修改 mnt 目录下的 image1.txt 文件:

/tmp/aufs$ echo Hello, Image layer1 changed! > mnt/image1.txt

然后我们查看下 image1/image1.txt 文件内容:

/tmp/aufs$ cat image1/image1.txt
Hello, Image layer1!

发现“镜像层”的 image1.txt 文件并未被修改。
然后我们查看一下"容器层"对应的 image1.txt 文件内容:

/tmp/aufs$ ls -l container1/
total 8
-rw-rw-r-- 1 ubuntu ubuntu 24 Sep  9 16:55 container1.txt
-rw-rw-r-- 1 ubuntu ubuntu 29 Sep  9 17:21 image1.txt
## 查看文件内容
/tmp/aufs$ cat container1/image1.txt
Hello, Image layer1 changed!

发现 AUFS 在“容器层”自动创建了 image1.txt 文件,并且内容为我们刚才写入的内容。

至此,我们完成了 AUFS 写时复制的验证。我们在第一次修改镜像内某个文件时,AUFS 会复制这个文件到容器层,然后在容器层对该文件进行修改操作,这就是 AUFS 最典型的特性写时复制。

结语

到此,相信你知道了联合文件系统是一种分层的轻量级文件系统,它可以把多个目录内容联合挂载到同一目录下,从而形成一个单一的文件系统。同时也学会了如何配置 Docker 使用 AUFS ,并且明白了 AUFS 的工作原理。

那么你知道 AUFS 为什么一直没能成功进入 Linux 内核主线吗? 思考后,可以把你的想法写在留言区。

下一课时,我将讲解 Docker 的另一个文件存储驱动:Devicemapper 文件系统原理及生产环境的最佳配置。


15 文件存储驱动:Devicemapper 文件系统原理及生产环境的最佳配置

上一课时我带你学习了什么是联合文件系统,以及 AUFS 的工作原理和配置。我们知道 AUFS 并不在 Linux 内核主干中,所以如果你的操作系统是 CentOS,就不推荐使用 AUFS 作为 Docker 的联合文件系统了。

那在 CentOS 系统中,我们怎么实现镜像和容器的分层结构呢?我们通常使用 Devicemapper 作为 Docker 的联合文件系统。

什么是 Devicemapper ?

Devicemapper 是 Linux 内核提供的框架,从 Linux 内核 2.6.9 版本开始引入,Devicemapper 与 AUFS 不同,AUFS 是一种文件系统,而Devicemapper 是一种映射块设备的技术框架。

Devicemapper 提供了一种将物理块设备映射到虚拟块设备的机制,目前 Linux 下比较流行的 LVM (Logical Volume Manager 是 Linux 下对磁盘分区进行管理的一种机制)和软件磁盘阵列(将多个较小的磁盘整合成为一个较大的磁盘设备用于扩大磁盘存储和提供数据可用性)都是基于 Devicemapper 机制实现的。

那么 Devicemapper 究竟是如何实现的呢?下面我们首先来了解一下它的关键技术。

Devicemapper 的关键技术

Devicemapper 将主要的工作部分分为用户空间和内核空间。

  • 用户空间负责配置具体的设备映射策略与相关的内核空间控制逻辑,例如逻辑设备 dm-a 如何与物理设备 sda 相关联,怎么建立逻辑设备和物理设备的映射关系等。

  • 内核空间则负责用户空间配置的关联关系实现,例如当 IO 请求到达虚拟设备 dm-a 时,内核空间负责接管 IO 请求,然后处理和过滤这些 IO 请求并转发到具体的物理设备 sda 上。

这个架构类似于 C/S (客户端/服务区)架构的工作模式,客户端负责具体的规则定义和配置下发,服务端根据客户端配置的规则来执行具体的处理任务。

Devicemapper 的工作机制主要围绕三个核心概念。

  • 映射设备(mapped device):即对外提供的逻辑设备,它是由 Devicemapper 模拟的一个虚拟设备,并不是真正存在于宿主机上的物理设备。

  • 目标设备(target device):目标设备是映射设备对应的物理设备或者物理设备的某一个逻辑分段,是真正存在于物理机上的设备。

  • 映射表(map table):映射表记录了映射设备到目标设备的映射关系,它记录了映射设备在目标设备的起始地址、范围和目标设备的类型等变量。

Drawing 1.png

图 1 Devicemapper 核心概念关系图

Devicemapper 三个核心概念之间的关系如图 1,映射设备通过映射表关联到具体的物理目标设备。事实上,映射设备不仅可以通过映射表关联到物理目标设备,也可以关联到虚拟目标设备,然后虚拟目标设备再通过映射表关联到物理目标设备。

Devicemapper 在内核中通过很多模块化的映射驱动(target driver)插件实现了对真正 IO 请求的拦截、过滤和转发工作,比如 Raid、软件加密、瘦供给(Thin Provisioning)等。其中瘦供给模块是 Docker 使用 Devicemapper 技术框架中非常重要的模块,下面我们来详细了解下瘦供给(Thin Provisioning)。

瘦供给(Thin Provisioning)

瘦供给的意思是动态分配,这跟传统的固定分配不一样。传统的固定分配是无论我们用多少都一次性分配一个较大的空间,这样可能导致空间浪费。而瘦供给是我们需要多少磁盘空间,存储驱动就帮我们分配多少磁盘空间。

这种分配机制就好比我们一群人围着一个大锅吃饭,负责分配食物的人每次都给你一点分量,当你感觉食物不够时再去申请食物,而当你吃饱了就不需要再去申请食物了,从而避免了食物的浪费,节约的食物可以分配给更多需要的人。

那么,你知道 Docker 是如何使用瘦供给来做到像 AUFS 那样分层存储文件的吗?答案就是: Docker 使用了瘦供给的快照(snapshot)技术。

什么是快照(snapshot)技术?这是全球网络存储工业协会 SNIA(StorageNetworking Industry Association)对快照(Snapshot)的定义:

关于指定数据集合的一个完全可用拷贝,该拷贝包括相应数据在某个时间点(拷贝开始的时间点)的映像。快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。

简单来说,快照是数据在某一个时间点的存储状态。快照的主要作用是对数据进行备份,当存储设备发生故障时,可以使用已经备份的快照将数据恢复到某一个时间点,而 Docker 中的数据分层存储也是基于快照实现的。

以上便是实现 Devicemapper 的关键技术,那 Docker 究竟是如何使用 Devicemapper 实现存储数据和镜像分层共享的呢?

Devicemapper 是如何数据存储的?

当 Docker 使用 Devicemapper 作为文件存储驱动时,Docker 将镜像和容器的文件存储在瘦供给池(thinpool)中,并将这些内容挂载在 /var/lib/docker/devicemapper/ 目录下。

这些目录储存 Docker 的容器和镜像相关数据,目录的数据内容和功能说明如下。

  • devicemapper 目录(/var/lib/docker/devicemapper/devicemapper/):存储镜像和容器实际内容,该目录由一个或多个块设备构成。

  • metadata 目录(/var/lib/docker/devicemapper/metadata/): 包含 Devicemapper 本身配置的元数据信息, 以 json 的形式配置,这些元数据记录了镜像层和容器层之间的关联信息。

  • mnt 目录( /var/lib/docker/devicemapper/mnt/):是容器的联合挂载点目录,未生成容器时,该目录为空,而容器存在时,该目录下的内容跟容器中一致。

Devicemapper 如何实现镜像分层与共享?

Devicemapper 使用专用的块设备实现镜像的存储,并且像 AUFS 一样使用了写时复制的技术来保障最大程度节省存储空间,所以 Devicemapper 的镜像分层也是依赖快照来是实现的。

Devicemapper 的每一镜像层都是其下一层的快照,最底层的镜像层是我们的瘦供给池,通过这种方式实现镜像分层有以下优点。

  • 相同的镜像层,仅在磁盘上存储一次。例如,我有 10 个运行中的 busybox 容器,底层都使用了 busybox 镜像,那么 busybox 镜像只需要在磁盘上存储一次即可。

  • 快照是写时复制策略的实现,也就是说,当我们需要对文件进行修改时,文件才会被复制到读写层。

  • 相比对文件系统加锁的机制,Devicemapper 工作在块级别,因此可以实现同时修改和读写层中的多个块设备,比文件系统效率更高。

当我们需要读取数据时,如果数据存在底层快照中,则向底层快照查询数据并读取。当我们需要写数据时,则向瘦供给池动态申请存储空间生成读写层,然后把数据复制到读写层进行修改。Devicemapper 默认每次申请的大小是 64K 或者 64K 的倍数,因此每次新生成的读写层的大小都是 64K 或者 64K 的倍数。

以下是一个运行中的 Ubuntu 容器示意图。

Drawing 3.png

图 2 Devicemapper 存储模型

这个 Ubuntu 镜像一共有四层,每一层镜像都是下一层的快照,镜像的最底层是基础设备的快照。当容器运行时,容器是基于镜像的快照。综上,Devicemapper 实现镜像分层的根本原理就是快照。

接下来,我们看下如何配置 Docker 的 Devicemapper 模式。

如何在 Docker 中配置 Devicemapper

Docker 的 Devicemapper 模式有两种:第一种是 loop-lvm 模式,该模式主要用来开发和测试使用;第二种是 direct-lvm 模式,该模式推荐在生产环境中使用。

下面我们逐一配置,首先来看下如何配置 loop-lvm 模式。

配置 loop-lvm 模式

1.使用以下命令停止已经运行的 Docker:

$ sudo systemctl stop docker

2.编辑 /etc/docker/daemon.json 文件,如果该文件不存在,则创建该文件,并添加以下配置:

{"storage-driver": "devicemapper"
}

3.启动 Docker:

$ sudo systemctl start docker

4.验证 Docker 的文件驱动模式:

$ docker info
Client:Debug Mode: false
Server:Containers: 1Running: 0Paused: 0Stopped: 1Images: 1Server Version: 19.03.12Storage Driver: devicemapperPool Name: docker-253:1-423624832-poolPool Blocksize: 65.54kBBase Device Size: 10.74GBBacking Filesystem: xfsUdev Sync Supported: trueData file: /dev/loop0Metadata file: /dev/loop1Data loop file: /var/lib/docker/devicemapper/devicemapper/dataMetadata loop file: /var/lib/docker/devicemapper/devicemapper/metadataData Space Used: 22.61MBData Space Total: 107.4GBData Space Available: 107.4GBMetadata Space Used: 17.37MBMetadata Space Total: 2.147GBMetadata Space Available: 2.13GBThin Pool Minimum Free Space: 10.74GBDeferred Removal Enabled: trueDeferred Deletion Enabled: trueDeferred Deleted Device Count: 0Library Version: 1.02.164-RHEL7 (2019-08-27)
... 省略部分输出

可以看到 Storage Driver 为 devicemapper,这表示 Docker 已经被配置为 Devicemapper 模式。

但是这里输出的 Data file 为 /dev/loop0,这表示我们目前在使用的模式为 loop-lvm。但是由于 loop-lvm 性能比较差,因此不推荐在生产环境中使用 loop-lvm 模式。下面我们看下生产环境中应该如何配置 Devicemapper 的 direct-lvm 模式。

配置 direct-lvm 模式

1.使用以下命令停止已经运行的 Docker:

$ sudo systemctl stop docker

2.编辑 /etc/docker/daemon.json 文件,如果该文件不存在,则创建该文件,并添加以下配置:

{"storage-driver": "devicemapper","storage-opts": ["dm.directlvm_device=/dev/xdf","dm.thinp_percent=95","dm.thinp_metapercent=1","dm.thinp_autoextend_threshold=80","dm.thinp_autoextend_percent=20","dm.directlvm_device_force=false"]
}

其中 directlvm_device 指定需要用作 Docker 存储的磁盘路径,Docker 会动态为我们创建对应的存储池。例如这里我想把 /dev/xdf 设备作为我的 Docker 存储盘,directlvm_device 则配置为 /dev/xdf。

3.启动 Docker:

$ sudo systemctl start docker

4.验证 Docker 的文件驱动模式:

$ docker info
Client:Debug Mode: false
Server:Containers: 1Running: 0Paused: 0Stopped: 1Images: 1Server Version: 19.03.12Storage Driver: devicemapperPool Name: docker-thinpoolPool Blocksize: 65.54kBBase Device Size: 10.74GBBacking Filesystem: xfsUdev Sync Supported: trueData file:Metadata file:Data loop file: /var/lib/docker/devicemapper/devicemapper/dataMetadata loop file: /var/lib/docker/devicemapper/devicemapper/metadataData Space Used: 22.61MBData Space Total: 107.4GBData Space Available: 107.4GBMetadata Space Used: 17.37MBMetadata Space Total: 2.147GBMetadata Space Available: 2.13GBThin Pool Minimum Free Space: 10.74GBDeferred Removal Enabled: trueDeferred Deletion Enabled: trueDeferred Deleted Device Count: 0Library Version: 1.02.164-RHEL7 (2019-08-27)
... 省略部分输出

当我们看到 Storage Driver 为 devicemapper,并且 Pool Name 为 docker-thinpool 时,这表示 Devicemapper 的 direct-lvm 模式已经配置成功。

结语

Devicemapper 使用块设备来存储文件,运行速度会比直接操作文件系统更快,因此很长一段时间内在 Red Hat 或 CentOS 系统中,Devicemapper 一直作为 Docker 默认的联合文件系统驱动,为 Docker 在 Red Hat 或 CentOS 稳定运行提供强有力的保障。

那么你知道使用 Devicemapper 作为 Docker 联合文件系统的一种解方案是哪家公司在推动吗? 思考后,可以把你的想法写在留言区。

下一课时,我将讲解 Docker 的另一个文件存储驱动:OverlayFS 文件系统原理及生产环境的最佳配置。


16 文件存储驱动:OverlayFS 文件系统原理及生产环境的最佳配置

前面课时我分别介绍了 Docker 常见的联合文件系统解决方案: AUFS 和 Devicemapper。今天我给你介绍一个性能更好的联合文件系统解决方案—— OverlayFS。

OverlayFS 的发展分为两个阶段。2014 年,OverlayFS 第一个版本被合并到 Linux 内核 3.18 版本中,此时的 OverlayFS 在 Docker 中被称为overlay文件驱动。由于第一版的overlay文件系统存在很多弊端(例如运行一段时间后Docker 会报 "too many links problem" 的错误), Linux 内核在 4.0 版本对overlay做了很多必要的改进,此时的 OverlayFS 被称之为overlay2

因此,在 Docker 中 OverlayFS 文件驱动被分为了两种,一种是早期的overlay,不推荐在生产环境中使用,另一种是更新和更稳定的overlay2,推荐在生产环境中使用。下面的内容我们主要围绕overlay2展开。

使用 overlay2 的先决条件

overlay2虽然很好,但是它的使用是有一定条件限制的。

  • 要想使用overlay2,Docker 版本必须高于 17.06.02。

  • 如果你的操作系统是 RHEL 或 CentOS,Linux 内核版本必须使用 3.10.0-514 或者更高版本,其他 Linux 发行版的内核版本必须高于 4.0(例如 Ubuntu 或 Debian),你可以使用uname -a查看当前系统的内核版本。

  • overlay2最好搭配 xfs 文件系统使用,并且使用 xfs 作为底层文件系统时,d_type必须开启,可以使用以下命令验证 d_type 是否开启:

$ xfs_info /var/lib/docker | grep ftype
naming   =version 2              bsize=4096   ascii-ci=0 ftype=1

当输出结果中有 ftype=1 时,表示 d_type 已经开启。如果你的输出结果为 ftype=0,则需要重新格式化磁盘目录,命令如下:

$ sudo mkfs.xfs -f -n ftype=1 /path/to/disk

另外,在生产环境中,推荐挂载 /var/lib/docker 目录到单独的磁盘或者磁盘分区,这样可以避免该目录写满影响主机的文件写入,并且把挂载信息写入到 /etc/fstab,防止机器重启后挂载信息丢失。

挂载配置中推荐开启 pquota,这样可以防止某个容器写文件溢出导致整个容器目录空间被占满。写入到 /etc/fstab 中的内容如下:

$UUID /var/lib/docker xfs defaults,pquota 0 0

其中 UUID 为 /var/lib/docker 所在磁盘或者分区的 UUID 或者磁盘路径。
如果你的操作系统无法满足上面的任何一个条件,那我推荐你使用 AUFS 或者 Devicemapper 作为你的 Docker 文件系统驱动。

通常情况下, overlay2 会比 AUFS 和 Devicemapper 性能更好,而且更加稳定,因为 overlay2 在 inode 优化上更加高效。因此在生产环境中推荐使用 overlay2 作为 Docker 的文件驱动。

下面我通过实例来教你如何初始化 /var/lib/docker 目录,为后面配置 Docker 的overlay2文件驱动做准备。

准备 /var/lib/docker 目录

1.使用 lsblk(Linux 查看磁盘和块设备信息命令)命令查看本机磁盘信息:

$ lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
vda    253:0    0  5000 disk
`-vda1 253:1    0  5000 part /
vdb    253:16   0  5000 disk
`-vdb1 253:17   0    80 part

可以看到,我的机器有两块磁盘,一块是 vda,一块是 vdb。其中 vda 已经被用来挂载系统根目录,这里我想把 /var/lib/docker 挂载到 vdb1 分区上。

2.使用 mkfs 命令格式化磁盘 vdb1:

$ sudo mkfs.xfs -f -n ftype=1 /dev/vdb1

3.将挂载信息写入到 /etc/fstab,保证机器重启挂载目录不丢失:

$ sudo echo "/dev/vdb1 /var/lib/docker xfs defaults,pquota 0 0" >> /etc/fstab

4.使用 mount 命令使得挂载目录生效:

$ sudo mount -a

5.查看挂载信息:

$ lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
vda    253:0    0  5000 disk
`-vda1 253:1    0  5000 part /
vdb    253:16   0  5000 disk
`-vdb1 253:17   0    80 part /var/lib/docker

可以看到此时 /var/lib/docker 目录已经被挂载到了 vdb1 这个磁盘分区上。我们使用 xfs_info 命令验证下 d_type 是否已经成功开启:

$ xfs_info /var/lib/docker | grep ftype
naming   =version 2              bsize=4096   ascii-ci=0 ftype=1

可以看到输出结果为 ftype=1,证明 d_type 已经被成功开启。

准备好 /var/lib/docker 目录后,我们就可以配置 Docker 的文件驱动为 overlay2,并且启动 Docker 了。

如何在 Docker 中配置 overlay2?

当你的系统满足上面的条件后,就可以配置你的 Docker 存储驱动为 overlay2 了,具体配置步骤如下。

1.停止已经运行的 Docker:

$ sudo systemctl stop docker

2.备份 /var/lib/docker 目录:

$ sudo cp -au /var/lib/docker /var/lib/docker.back

3.在 /etc/docker 目录下创建 daemon.json 文件,如果该文件已经存在,则修改配置为以下内容:

{"storage-driver": "overlay2","storage-opts": ["overlay2.size=20G","overlay2.override_kernel_check=true"]
}

其中 storage-driver 参数指定使用 overlay2 文件驱动,overlay2.size 参数表示限制每个容器根目录大小为 20G。限制每个容器的磁盘空间大小是通过 xfs 的 pquota 特性实现,overlay2.size 可以根据不同的生产环境来设置这个值的大小。我推荐你在生产环境中开启此参数,防止某个容器写入文件过大,导致整个 Docker 目录空间溢出。

4.启动 Docker:

$ sudo systemctl start docker

5.检查配置是否生效:

$ docker info
Client:Debug Mode: false
Server:Containers: 1Running: 0Paused: 0Stopped: 1Images: 1Server Version: 19.03.12Storage Driver: overlay2Backing Filesystem: xfsSupports d_type: trueNative Overlay Diff: trueLogging Driver: json-fileCgroup Driver: cgroupfs... 省略部分无用输出

可以看到 Storage Driver 已经变为 overlay2,并且 d_type 也是 true。至此,你的 Docker 已经配置完成。下面我们看下 overlay2 是如何工作的。

overlay2 工作原理

overlay2 是如何存储文件的?

overlay2 和 AUFS 类似,它将所有目录称之为层(layer),overlay2 的目录是镜像和容器分层的基础,而把这些层统一展现到同一的目录下的过程称为联合挂载(union mount)。overlay2 把目录的下一层叫作lowerdir,上一层叫作upperdir,联合挂载后的结果叫作merged

overlay2 文件系统最多支持 128 个层数叠加,也就是说你的 Dockerfile 最多只能写 128 行,不过这在日常使用中足够了。

下面我们通过拉取一个 Ubuntu 操作系统的镜像来看下 overlay2 是如何存放镜像文件的。

首先,我们通过以下命令拉取 Ubuntu 镜像:

$ docker pull ubuntu:16.04
16.04: Pulling from library/ubuntu
8e097b52bfb8: Pull complete
a613a9b4553c: Pull complete
acc000f01536: Pull complete
73eef93b7466: Pull complete
Digest: sha256:3dd44f7ca10f07f86add9d0dc611998a1641f501833692a2651c96defe8db940
Status: Downloaded newer image for ubuntu:16.04
docker.io/library/ubuntu:16.04

可以看到镜像一共被分为四层拉取,拉取完镜像后我们查看一下 overlay2 的目录:

$ sudo ls -l /var/lib/docker/overlay2/
total 0
drwx------. 3 root root      47 Sep 13 08:16 01946de89606800dac8530e3480b32be9d7c66b493a1cdf558df52d7a1476d4a
drwx------. 4 root root      55 Sep 13 08:16 0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb
drwx------. 4 root root      72 Sep 13 08:16 94222a2fa3b2405cb00459285dd0d0ba7e6936d9b693ed18fbb0d08b93dc272f
drwx------. 4 root root      72 Sep 13 08:16 9d392cf38f245d37699bdd7672daaaa76a7d702083694fa8be380087bda5e396
brw-------. 1 root root 253, 17 Sep 13 08:14 backingFsBlockDev
drwx------. 2 root root     142 Sep 13 08:16 l

可以看到 overlay2 目录下出现了四个镜像层目录和一个l目录,我们首先来查看一下l目录的内容:

$ sudo ls -l /var/lib/docker/overlay2/l
total 0
lrwxrwxrwx. 1 root root 72 Sep 13 08:16 FWGSYEA56RNMS53EUCKEQIKVLQ -> ../9d392cf38f245d37699bdd7672daaaa76a7d702083694fa8be380087bda5e396/diff
lrwxrwxrwx. 1 root root 72 Sep 13 08:16 RNN2FM3YISKADNAZFRONVNWTIS -> ../0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb/diff
lrwxrwxrwx. 1 root root 72 Sep 13 08:16 SHAQ5GYA3UZLJJVEGXEZM34KEE -> ../01946de89606800dac8530e3480b32be9d7c66b493a1cdf558df52d7a1476d4a/diff
lrwxrwxrwx. 1 root root 72 Sep 13 08:16 VQSNH735KNX4YK2TCMBAJRFTGT -> ../94222a2fa3b2405cb00459285dd0d0ba7e6936d9b693ed18fbb0d08b93dc272f/diff

可以看到l目录是一堆软连接,把一些较短的随机串软连到镜像层的 diff 文件夹下,这样做是为了避免达到mount命令参数的长度限制。
下面我们查看任意一个镜像层下的文件内容:

$ sudo ls -l /var/lib/docker/overlay2/0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb/
total 8
drwxr-xr-x. 3 root root 17 Sep 13 08:16 diff
-rw-r--r--. 1 root root 26 Sep 13 08:16 link
-rw-r--r--. 1 root root 86 Sep 13 08:16 lower
drwx------. 2 root root  6 Sep 13 08:16 work

镜像层的 link 文件内容为该镜像层的短 ID,diff 文件夹为该镜像层的改动内容,lower 文件为该层的所有父层镜像的短 ID。
我们可以通过docker image inspect命令来查看某个镜像的层级关系,例如我想查看刚刚下载的 Ubuntu 镜像之间的层级关系,可以使用以下命令:

$ docker image inspect ubuntu:16.04
...省略部分输出
"GraphDriver": {"Data": {"LowerDir": "/var/lib/docker/overlay2/9d392cf38f245d37699bdd7672daaaa76a7d702083694fa8be380087bda5e396/diff:/var/lib/docker/overlay2/94222a2fa3b2405cb00459285dd0d0ba7e6936d9b693ed18fbb0d08b93dc272f/diff:/var/lib/docker/overlay2/01946de89606800dac8530e3480b32be9d7c66b493a1cdf558df52d7a1476d4a/diff","MergedDir": "/var/lib/docker/overlay2/0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb/merged","UpperDir": "/var/lib/docker/overlay2/0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb/diff","WorkDir": "/var/lib/docker/overlay2/0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb/work"},"Name": "overlay2"},
...省略部分输出

其中 MergedDir 代表当前镜像层在 overlay2 存储下的目录,LowerDir 代表当前镜像的父层关系,使用冒号分隔,冒号最后代表该镜像的最底层。

下面我们将镜像运行起来成为容器:

$ docker run --name=ubuntu -d ubuntu:16.04 sleep 3600

我们使用docker inspect命令来查看一下容器的工作目录:

$ docker inspect ubuntu
...省略部分输出"GraphDriver": {"Data": {"LowerDir": "/var/lib/docker/overlay2/4753c2aa5bdb20c97cddd6978ee3b1d07ef149e3cc2bbdbd4d11da60685fe9b2-init/diff:/var/lib/docker/overlay2/0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb/diff:/var/lib/docker/overlay2/9d392cf38f245d37699bdd7672daaaa76a7d702083694fa8be380087bda5e396/diff:/var/lib/docker/overlay2/94222a2fa3b2405cb00459285dd0d0ba7e6936d9b693ed18fbb0d08b93dc272f/diff:/var/lib/docker/overlay2/01946de89606800dac8530e3480b32be9d7c66b493a1cdf558df52d7a1476d4a/diff","MergedDir": "/var/lib/docker/overlay2/4753c2aa5bdb20c97cddd6978ee3b1d07ef149e3cc2bbdbd4d11da60685fe9b2/merged","UpperDir": "/var/lib/docker/overlay2/4753c2aa5bdb20c97cddd6978ee3b1d07ef149e3cc2bbdbd4d11da60685fe9b2/diff","WorkDir": "/var/lib/docker/overlay2/4753c2aa5bdb20c97cddd6978ee3b1d07ef149e3cc2bbdbd4d11da60685fe9b2/work"},"Name": "overlay2"},
...省略部分输出

MergedDir 后面的内容即为容器层的工作目录,LowerDir 为容器所依赖的镜像层目录。 然后我们查看下 overlay2 目录下的内容:

$ sudo ls -l /var/lib/docker/overlay2/
total 0
drwx------. 3 root root      47 Sep 13 08:16 01946de89606800dac8530e3480b32be9d7c66b493a1cdf558df52d7a1476d4a
drwx------. 4 root root      72 Sep 13 08:47 0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb
drwx------. 5 root root      69 Sep 13 08:47 4753c2aa5bdb20c97cddd6978ee3b1d07ef149e3cc2bbdbd4d11da60685fe9b2
drwx------. 4 root root      72 Sep 13 08:47 4753c2aa5bdb20c97cddd6978ee3b1d07ef149e3cc2bbdbd4d11da60685fe9b2-init
drwx------. 4 root root      72 Sep 13 08:16 94222a2fa3b2405cb00459285dd0d0ba7e6936d9b693ed18fbb0d08b93dc272f
drwx------. 4 root root      72 Sep 13 08:16 9d392cf38f245d37699bdd7672daaaa76a7d702083694fa8be380087bda5e396
brw-------. 1 root root 253, 17 Sep 13 08:14 backingFsBlockDev
drwx------. 2 root root     210 Sep 13 08:47 l

可以看到 overlay2 目录下增加了容器层相关的目录,我们再来查看一下容器层下的内容:

$ sudo ls -l /var/lib/docker/overlay2/4753c2aa5bdb20c97cddd6978ee3b1d07ef149e3cc2bbdbd4d11da60685fe9b2
total 8
drwxr-xr-x. 2 root root   6 Sep 13 08:47 diff
-rw-r--r--. 1 root root  26 Sep 13 08:47 link
-rw-r--r--. 1 root root 144 Sep 13 08:47 lower
drwxr-xr-x. 1 root root   6 Sep 13 08:47 merged
drwx------. 3 root root  18 Sep 13 08:47 work

link 和 lower 文件与镜像层的功能一致,****link 文件内容为该容器层的短 ID,lower 文件为该层的所有父层镜像的短 ID 。diff 目录为容器的读写层,容器内修改的文件都会在 diff 中出现,merged 目录为分层文件联合挂载后的结果,也是容器内的工作目录。

总体来说,overlay2 是这样储存文件的:overlay2将镜像层和容器层都放在单独的目录,并且有唯一 ID,每一层仅存储发生变化的文件,最终使用联合挂载技术将容器层和镜像层的所有文件统一挂载到容器中,使得容器中看到完整的系统文件。

overlay2 如何读取、修改文件?

overlay2 的工作过程中对文件的操作分为读取文件和修改文件。

读取文件

容器内进程读取文件分为以下三种情况。

  • 文件在容器层中存在:当文件存在于容器层并且不存在于镜像层时,直接从容器层读取文件;

  • 当文件在容器层中不存在:当容器中的进程需要读取某个文件时,如果容器层中不存在该文件,则从镜像层查找该文件,然后读取文件内容;

  • 文件既存在于镜像层,又存在于容器层:当我们读取的文件既存在于镜像层,又存在于容器层时,将会从容器层读取该文件。

修改文件或目录

overlay2 对文件的修改采用的是写时复制的工作机制,这种工作机制可以最大程度节省存储空间。具体的文件操作机制如下。

  • 第一次修改文件:当我们第一次在容器中修改某个文件时,overlay2 会触发写时复制操作,overlay2 首先从镜像层复制文件到容器层,然后在容器层执行对应的文件修改操作。

overlay2 写时复制的操作将会复制整个文件,如果文件过大,将会大大降低文件系统的性能,因此当我们有大量文件需要被修改时,overlay2 可能会出现明显的延迟。好在,写时复制操作只在第一次修改文件时触发,对日常使用没有太大影响。

  • 删除文件或目录:当文件或目录被删除时,overlay2 并不会真正从镜像中删除它,因为镜像层是只读的,overlay2 会创建一个特殊的文件或目录,这种特殊的文件或目录会阻止容器的访问。

结语

overlay2 目前已经是 Docker 官方推荐的文件系统了,也是目前安装 Docker 时默认的文件系统,因为 overlay2 在生产环境中不仅有着较高的性能,它的稳定性也极其突出。但是 overlay2 的使用还是有一些限制条件的,例如要求 Docker 版本必须高于 17.06.02,内核版本必须高于 4.0 等。因此,在生产环境中,如果你的环境满足使用 overlay2 的条件,请尽量使用 overlay2 作为 Docker 的联合文件系统。

那么你知道除了我介绍的这三种联合文件系统外,Docker 还可以使用哪些联合文件系统吗? 思考后,可以把你的想法写在留言区。

下一课时,我将带你进入 Docker 原理实践,自己动手使用 Golang 开发 Docker。


这篇关于Docker容器化实战第六课 文件存储驱动的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/406890

相关文章

LeetCode11. 盛最多水的容器题解

LeetCode11. 盛最多水的容器题解 题目链接: https://leetcode.cn/problems/container-with-most-water 示例 思路 暴力解法 定住一个柱子不动,然后用其他柱子与其围住面积,取最大值。 代码如下: public int maxArea1(int[] height) {int n = height.length;int

【服务器运维】MySQL数据存储至数据盘

查看磁盘及分区 [root@MySQL tmp]# fdisk -lDisk /dev/sda: 21.5 GB, 21474836480 bytes255 heads, 63 sectors/track, 2610 cylindersUnits = cylinders of 16065 * 512 = 8225280 bytesSector size (logical/physical)

Ubuntu20.04离线安装Docker

1.下载3个docker离线安装包,下载网址: https://download.docker.com/linux/ubuntu/dists/xenial/pool/stable/amd64/ 2.把3个离线安装包拷贝到ubuntu本地执行以下命令 sudo dpkg -i containerd.io_1.4.6-1_amd64.deb sudo dpkg -i docker-ce-c

亮相WOT全球技术创新大会,揭秘火山引擎边缘容器技术在泛CDN场景的应用与实践

2024年6月21日-22日,51CTO“WOT全球技术创新大会2024”在北京举办。火山引擎边缘计算架构师李志明受邀参与,以“边缘容器技术在泛CDN场景的应用和实践”为主题,与多位行业资深专家,共同探讨泛CDN行业技术架构以及云原生与边缘计算的发展和展望。 火山引擎边缘计算架构师李志明表示:为更好地解决传统泛CDN类业务运行中的问题,火山引擎边缘容器团队参考行业做法,结合实践经验,打造火山

React+TS前台项目实战(十七)-- 全局常用组件Dropdown封装

文章目录 前言Dropdown组件1. 功能分析2. 代码+详细注释3. 使用方式4. 效果展示 总结 前言 今天这篇主要讲全局Dropdown组件封装,可根据UI设计师要求自定义修改。 Dropdown组件 1. 功能分析 (1)通过position属性,可以控制下拉选项的位置 (2)通过传入width属性, 可以自定义下拉选项的宽度 (3)通过传入classN

利用Frp实现内网穿透(docker实现)

文章目录 1、WSL子系统配置2、腾讯云服务器安装frps2.1、创建配置文件2.2 、创建frps容器 3、WSL2子系统Centos服务器安装frpc服务3.1、安装docker3.2、创建配置文件3.3 、创建frpc容器 4、WSL2子系统Centos服务器安装nginx服务 环境配置:一台公网服务器(腾讯云)、一台笔记本电脑、WSL子系统涉及知识:docker、Frp

Docker启动异常

报错信息: failed to start daemon: Error initializing network controller: error creating default "bridge" network: cannot create network b8fd8c684f0ba865d4a13d36e5282fd694bbd37b243c7ec6c9cd29416db98d4b (d

WDF驱动开发-WDF总线枚举(一)

支持在总线驱动程序中进行 PnP 和电源管理 某些设备永久插入系统,而其他设备可以在系统运行时插入和拔出电源。 总线驱动 必须识别并报告连接到其总线的设备,并且他们必须发现并报告系统中设备的到达和离开情况。 总线驱动程序标识和报告的设备称为总线的 子设备。 标识和报告子设备的过程称为 总线枚举。 在总线枚举期间,总线驱动程序会为其子 设备创建设备对象 。  总线驱动程序本质上是同时处理总线枚

PyTorch模型_trace实战:深入理解与应用

pytorch使用trace模型 1、使用trace生成torchscript模型2、使用trace的模型预测 1、使用trace生成torchscript模型 def save_trace(model, input, save_path):traced_script_model = torch.jit.trace(model, input)<

手把手教你入门vue+springboot开发(五)--docker部署

文章目录 前言一、前端打包二、后端打包三、docker运行总结 前言 前面我们重点介绍了vue+springboot前后端分离开发的过程,本篇我们结合docker容器来研究一下打包部署过程。 一、前端打包 在VSCode的命令行中输入npm run build可以打包前端代码,出现下图提示表示打包完成。 打包成功后会在前端工程目录生成dist目录,如下图所示: 把