Docker和联合文件系统那些事


前言

边学边记,有问题请指教。


一、docker容器到底是什么?

Docker容器是一种轻量级可移植虚拟化技术,用于打包、运输和运行应用程序及其所有依赖项。
它利用Linux内核的特性(如命名空间控制组)来提供隔离性资源管理,使得应用程序可以在相对独立的环境中运行,而无需携带整个操作系统。
每个Docker容器都是一个独立的、可重复的、可部署的单元,具有自己的文件系统、网络和进程空间,能够快速地启动、停止和迁移。Docker容器使得开发人员能够更轻松地构建、交付和运行应用程序,同时提高了资源利用率和部署的一致性。

二、docker使用联合文件系统

Docker 容器使用了一种称为联合文件系统(Union File Systems)的技术来提高存储效率和性能。联合文件系统允许将多个文件系统或目录合并为一个单一的视图,使得这些文件系统中的内容可以被当作一个单独的文件系统来访问。在 Docker 容器中,这种技术主要用于镜像层(image layers)的管理容器运行时的文件系统

三、联合文件系统

目前 docker 支持的联合文件系统有很多种,包括:AUFS、overlay、overlay2、DeviceMapper、VSF等。
Linux 中各发行版实现的 UnionFS 各不相同,所以 docker 在不同 linux 发版中使用的也不同。通过 docker info 命令可以查看当前系统所使用的是哪种 UnionFS:
常见的几种 UnionFS 的 Storage Driver 发行版如下:

CentOS 系统中:overlay2、overlay
debain 系统中:aufs
RedHat 系统中:devicemapper

四、docker容器使用Overlay的分层

在这里插入图片描述

lowerdir(只读层)由多个镜像层(Dockerfile中每一条指令即为一个镜像层)所组成
UpperDir (读写层) upperdir 则是在 lowerdir 之上的一层, 为读写层。当基于镜像创建一个容器时,Docker 会在镜像的顶部添加一个可读写的容器层, 所有对容器的修改, 都是在这层。 比如容器启动写入的日志文件,或者是应用程序写入的临时文件。容器层是临时的,只在容器运行时存在,当容器停止时,对容器层的修改也会被丢弃,保持镜像的不可变性。
MergedDir (合并层) merged 目录是容器的挂载点,在用户视角能够看到的所有文件,都是从这层展示的。
WorkDir (工作目录) workdir 是 OverlayFS 内部用于处理写入操作的临时目录。

尽管容器层通常是临时的,但通过挂载主机目录或使用数据卷,可以实现容器数据的持久化。持久化数据不存储在容器层中,而是存储在主机的文件系统中或者专门管理的 Docker 数据卷中,这样即使容器被删除,数据仍然保留。

容器的copy-on-write特性,修改容器里面的内容会修改镜像吗?

所有对容器的改动 – 无论添加、删除、还是修改文件,都只会发生在容器层中,因为只有容器层是可写的,容器层下面的所有镜像层都是只读的。
添加文件:在容器中创建文件时,新文件被添加到容器层中。
读取文件:在容器中读取某个文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后打开并读入内存。
修改文件:在容器中修改已存在的文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改之。
删除文件:在容器中删除文件时,也只会将存在于容器层中的文件副本删除,这种修改是持久的,直到你重启容器或采取措施(如创建新层或使用持久化存储)
只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。

容器层的文件删除只是一个 “障眼法”,是靠 whiteout 文件将其遮挡, image 层并没有删除,这也就是为什么使用 docker commit 提交保存的镜像会越来越大,无论在容器层怎么删除数据,image 层都不会改变。

五、容器层详解

Docker 的容器层是 Docker 容器的一个核心概念,它是 Docker 镜像的运行实例。当你从 Docker 镜像创建一个容器时,Docker 在镜像的基础上添加了一个可写的容器层,用于存储容器运行时的变更和数据。容器层与镜像层相互结合,使得容器能够在镜像的基础上添加或修改文件、运行进程,并保留这些更改,而不影响基础镜像的原始内容。

下面是容器层的详细介绍:

可写性:
容器层是一个可读写的文件系统层,它允许在容器运行时修改和保存数据。容器中的进程可以向容器层添加、修改或删除文件,这些更改将仅影响容器本身,而不会影响基础镜像或其他容器

联合文件系统:
容器层使用联合文件系统(UnionFS)技术,它允许将多个文件系统合并到一个单一的文件系统中。Docker 使用联合文件系统来将容器层和基础镜像层组合在一起,以形成容器的完整文件系统视图。

轻量和高效:
容器层是基于镜像层的增量修改,这使得容器非常轻量且高效。因为容器只保存与基础镜像的差异,所以它们通常只需要很少的磁盘空间,并且在创建和启动时非常快速。

容器生命周期:
当容器运行时,容器层处于活动状态。容器内的进程可以读取和写入容器层的文件系统,并在运行时进行修改。当容器停止后,容器层仍然存在,但它将保持在停止的状态,并且可以随时重新启动和使用。

临时性:
容器层是临时的,任何对容器的更改都只存在于容器的生命周期中。如果容器被删除,容器层中的所有数据和修改也会被丢弃。这使得容器可以非常容易地重新创建和重置。

持久化数据:
尽管容器层通常是临时的,但通过挂载主机目录或使用数据卷,可以实现容器数据的持久化。持久化数据不存储在容器层中,而是存储在主机的文件系统中或者专门管理的 Docker 数据卷中,这样即使容器被删除,数据仍然保留。

六、docker镜像的分层结构

Docker 镜像采用分层结构来构建和管理,这是其轻量、高效和可复用性的关键。镜像的分层结构使得 Docker 镜像在构建、部署和更新过程中非常灵活,同时节省存储空间和下载时间。

只读分层:
Docker 镜像由多个只读的文件系统层组成,每个分层包含了一个或多个文件或目录的更改。这些分层按照从底部到顶部的顺序叠加在一起,形成了一个完整的镜像。底部的分层通常是一个基础操作系统的根文件系统,而顶部的分层包含了应用程序代码和配置等信息。由于每个分层是只读的,所以镜像在创建后不会被更改。

联合文件系统:
Docker 使用联合文件系统(UnionFS)技术将多个只读分层组合成一个单一的虚拟文件系统。联合文件系统使得各个分层看起来像是一个整体,使得镜像中的每个分层的内容在文件系统层次结构中可见,但实际上并不复制这些内容。这样的设计节省了存储空间,并且可以在不同的镜像之间共享公共层,从而加快镜像的构建和下载速度。

分层继承:
Docker 镜像支持分层继承,这意味着可以基于现有的镜像构建新的镜像。当新的镜像构建时,它只需在现有镜像的基础上添加新的分层,而不需要重新复制现有的分层。这种分层继承的特性使得镜像构建变得高效和快速,并允许镜像的复用。

镜像的复用和共享:
Docker 镜像分层的结构使得镜像可以复用和共享。多个镜像可以共享相同的基础层,从而节省存储空间,并减少镜像拉取和构建的时间。这对于持续集成、持续部署和分布式系统的部署非常有益。

镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。

七、docker镜像的组成部分

基础镜像层

Docker 的基础镜像层是构成 Docker 镜像的核心组成部分之一。Docker 镜像是由多个只读的文件系统层叠加在一起构成的,基础镜像层是这些层中的第一层。
Dockerfile 中的 FROM 指令:
基础镜像是通过 Dockerfile 文件中的 FROM指令来指定的。FROM 指令定义了基于哪个现有镜像构建新镜像。Docker 在构建镜像时,会在指定的基础镜像之上创建新的只读层,然后在其上添加其他层。
操作系统和软件环境:
基础镜像通常包含操作系统(如 Ubuntu、Alpine、CentOS 等)以及一些最基本的软件工具和运行时库。这些软件环境为后续的镜像层提供了一个稳定的运行基础。
特定用途的基础镜像:
Docker 官方镜像仓库和其他可信的镜像仓库通常提供了许多特定用途的基础镜像,如数据库镜像、Web 服务器镜像、编程语言运行时环境镜像等。这些基础镜像已经配置好了特定的软件和环境,使得用户可以基于它们构建和部署特定类型的应用程序。
镜像缓存:
Docker 镜像构建过程中,如果使用的基础镜像已经存在于本地主机上,则 Docker 将从本地缓存中直接使用该镜像,而不是重新下载或构建。这样可以节省时间和网络带宽。

扩展镜像层

“扩展镜像层” 是指在一个现有的基础镜像上构建新的镜像层,以添加或修改容器所需的内容和配置。Docker 使用分层文件系统的方式来管理镜像,每个镜像层都包含一个或多个文件或目录的更改。当你在现有的镜像上进行更改时,Docker 会在原有的镜像层之上创建一个新的镜像层,这个过程就是所谓的 “扩展镜像层”。

基础镜像:
扩展镜像层的起点是一个现有的基础镜像。基础镜像通常是一个已经构建好的、可用于部署特定应用程序的镜像。这个基础镜像可以是 Docker 官方镜像、第三方镜像或者你自己构建的镜像。

创建 Dockerfile:
扩展镜像层的过程通常是通过创建一个 Dockerfile 文件来完成的。Dockerfile 是一个文本文件,其中包含一系列的指令,用于定义新镜像的构建过程。Dockerfile 可以基于已有的基础镜像,然后在其上添加更改,以满足特定的需求。

添加新的镜像层:
Docker 会根据 Dockerfile 中的指令逐步构建新的镜像。当 Docker 构建过程遇到一个指令时,它会在当前镜像层的基础上创建一个新的镜像层,并应用该指令所描述的更改。这样,新的镜像层就被添加到了构建过程中。

缓存机制:
Docker 在构建过程中使用了缓存机制,这意味着当一个指令没有发生变化时,Docker 将重用之前构建的层,而不会重新创建。这样可以提高构建的速度和效率。

多层继承:
在 Dockerfile 中,你可以利用多层继承的特性,基于多个基础镜像构建一个新的镜像。这使得扩展镜像层的过程更加灵活,可以根据实际需求选择不同的基础镜像,并在其上添加所需的更改。

不可变性:
镜像层是不可变的,一旦创建后就不会更改。这意味着每次在扩展镜像层上做出的更改都会创建一个新的镜像层,而原有的镜像层保持不变。这有助于确保镜像的稳定性和可重现性。

在基础镜像层之上的镜像层称为扩展镜像层。顾名思义,其是对基础镜像层功能的扩展。在 Dockerfile 中,每条指令都是用于完成某项特定功能的,而每条指令都会生成一个扩展镜像层。

Docker 镜像为什么被称为树状结构

是因为其核心设计采用了 分层叠加(Layer Stacking)和写时复制(Copy-On-Write) 技术,使得镜像的构建、存储和分发呈现出树形特征。

1. 镜像分层的树状本质
基础镜像层(Root Layer)是树的 “根节点”=,通常由FROM指令指定(如ubuntu:latest)。
中间层(Intermediate Layers)是树的 “枝干”,由RUN、COPY、ADD等指令生成。
最终层(Final Layer)是树的 “叶子节点”,包含容器启动时的配置(如CMD、ENTRYPOINT)。

示例镜像构建过程(dockerfile):

FROM ubuntu:latest  # 根节点(基础镜像层)
RUN apt-get update  # 枝干层1
RUN apt-get install -y nginx  # 枝干层2
COPY . /app  # 枝干层3
CMD ["nginx", "-g", "daemon off;"]  # 叶子节点

2. 树状结构的技术实现

联合文件系统
分层合并:
所有镜像层通过 UnionFS 技术合并为一个虚拟文件系统。每层文件系统的差异通过增量方式存储。
叠加顺序:
基础镜像层在最底层,后续层依次叠加。最终容器看到的文件系统是所有层的合并视图。

写时复制(CoW)
只读特性:
所有镜像层默认是只读的,容器启动时在顶层添加一个可写层。对文件的修改仅发生在可写层,不影响底层镜像层。
共享机制:
多个容器可共享同一个镜像层,仅存储各自的修改。

3. 树状结构的核心优势

特性传统 VM 镜像Docker 镜像(树状)
存储效率完整副本,体积大共享层,体积小
构建速度每次构建需从头开始复用已有层,仅构建差异部分
版本控制全量镜像,难以回滚每层可单独版本化,支持快速回滚
分发带宽全量传输,耗时分层传输,仅传新增层

八、再底层一点,linux和docker启动时

linux启动时

Bootfs(启动文件系统)
作用:负责启动系统内核,包含引导加载程序(如 GRUB)和内核镜像。
生命周期:启动时加载到内存,完成内核初始化后,bootfs 会被卸载(内存中仅保留内核)。
物理存储(如硬盘)中的 bootfs 通常位于 /boot 目录。
特点:体积小,仅包含必要的启动文件。

Rootfs(根文件系统)
作用:提供完整的 Linux 用户空间环境,包含 /bin、/etc、/dev 等目录。
启动流程:内核挂载 只读 的 rootfs(确保系统完整性)。完成硬件检测和初始化后,内核将 rootfs 切换为 读写模式(或挂载其他文件系统如 ext4)。
发行版差异:不同 Linux 发行版(如 Ubuntu、CentOS)的 rootfs 内容不同,但内核是通用的。

Linux 刚启动时会加载 bootfs 文件系统,bootfs包含 boot 加载器和内核。当 boot 加载完成之后整个内核就都在内存中了,此时内存的使用权已由 bootfs 转交给内核,此时系统也会卸载 bootfs。
rootfs,在 bootfs 之上。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。rootfs 就是各种不同的操作系统发行版,比如 Ubuntu,Centos 等等。
传统来说,Linux 操作系统内核启动时,内核首先会挂载一个只读(read-only)的 rootfs,当系统检测其完整性之后,决定是否将其切换为读写(read-write)模式,或者最后在 rootfs 之上另行挂载一种文件系统并忽略 rootfs。

在这里插入图片描述

Docker启动时

bootfs 的特殊性:
容器并不包含完整的 bootfs。Docker 容器基于宿主机的内核运行,因此镜像中通常不包含 boot 加载器和内核(如/boot目录)。
宿主机内核共享:所有容器直接复用宿主机的内核,这也是 Docker 轻量的核心原因之一。

rootfs 的核心作用:
Docker 镜像的最底层是rootfs(而非 bootfs),它对应一个完整的 Linux 发行版环境(如 Ubuntu、CentOS 的根文件系统)。
例如,ubuntu:latest镜像的 rootfs 包含/bin、/etc、/lib等目录,与传统 Linux 系统完全一致。
所有镜像的最下层,有一个可以看得到的基础镜像层 Base Image,基础镜像层的文件系统称为根文件系统 rootfs。而 rootfs 则是建立在 Linux 系统中“看不到的”引导文件系统bootfs 之上

Docker 容器不包含 bootfs,bootfs(引导文件系统)属于宿主机物理磁盘,位于/boot目录,所有 Docker 容器直接复用宿主机的 bootfs 和内核。
容器启动流程:宿主机内核加载 bootfs,完成初始化。Docker Daemon 基于镜像的 rootfs 启动容器,rootfs只读,叠加可写层,挂载时该读写(read-write)文件系统内空无一物
在这里插入图片描述

为什么 Docker 不需要镜像包含 Bootfs?/内核共享的核心优势

轻量启动: 容器无需加载独立内核,直接复用宿主机已运行的内核。
隔离性限制: Linux Namespace 技术无法隔离内核,容器必须与宿主机共享内核。
这是 Docker 区别于虚拟机的核心特性(虚拟机需独立内核)。

传统 Linux vs Docker 容器

传统 Linux: 物理机有独立的 bootfs 和 rootfs。bootfs 启动内核后卸载,rootfs 切换为读写模式。
Docker 容器: 无独立 bootfs,共享宿主机内核。镜像仅包含 rootfs(基础镜像层),叠加可写层。


总结

Docker果然是越了解,越佩服,但我觉得我对docker的认知还是远远不够,相信继续学习,总有一天能完全理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值