[k8s理论知识]4.docker基础(三)镜像分层

devtools/2024/10/24 5:02:23/

上一节内容讲了Docker的文件系统的挂载和隔离,也讲了Docker镜像打包了一部分的操作系统,既操作系统的文件系统和配置文件。但是容器在宿主机上运行的时候只能使用宿主机内核,这也带来了一定的局限性,就是高版本的docker镜像无法运行在低版本的宿主机上。

一致性

 不过,正是由于容器根文件系统的存在,才保证了容器的一致性。由于rootfs不仅仅打包应用,还打包整个操作系统的文件和目录,也就是说应用和它运行的所有依赖都被打包了。在过去的应用打包过程中,对依赖的理解一直停留在编程语言层面,但是实际上操作系统环境才是它运行的最大依赖库。有了容器镜像“打包操作系统”的能力,这个最基础的依赖环境也终于变成了应用沙盒的一部分。这就赋予了容器所谓的一致性:无论在本地、云端,还是在一台任何地方的机器上,用户只需要解压打包好的容器镜像,那么这个应用运行所需要的完整的执行环境就被重现出来了。这种深入到操作系统级别的运行环境一致性,打通了应用在本地开发和远端执行环境之间难以逾越的鸿沟。

让我们重新梳理一下容器镜像和操作系统镜像的区别和关联。如下是一个dockerfile,用来构建容器镜像,可以看到这里包含了一个ubuntu镜像, Ubuntu 20.04 的用户态文件系统,也就是 Ubuntu 的 rootfs。它包含了 Ubuntu 的常见目录结构 (/bin, /etc, /lib, /usr 等) 以及 Ubuntu 的基础工具和库。容器的 rootfs(根文件系统)不只是基础镜像的文件系统,它还包含了在容器构建过程中添加的所有扩展、修改和依赖。

FROM ubuntu:20.04
镜像的复用

在容器开发和部署过程中,经常会遇到一个非常棘手的问题:每开发一个应用或升级现有应用时,是否需要重复制作一次 rootfs?例如,你使用 Ubuntu 操作系统的 ISO 制作了一个 rootfs,并在其中安装了 Java 环境,用于部署 Java 应用。而当别人使用这个rootfs后,如果做了某个修改,就会导致现有的rootfs和之前的不一样了,不能拿来复用。

为了克服上述问题,可以采用增量更新的方式进行 rootfs 的管理。具体来说,所有的修改都基于一个旧的 rootfs 进行,而不是每次都从头开始制作一个新的 rootfs。Docker在制作镜像的时候,通过联合文件系统来实现增量的rootfs操作。

文件挂载系统

Union File System 也叫 UnionFS,最主要的功能是将多个不同位置的目录联合挂载(union mount)到同一个目录下。我们现在创建两个文件夹A和B,它们分别有两个文件a,x和b,x。另一个文件merge是为了之后联合挂载。为了显示完整的目录结构,这里要安装tree工具。

[root@master ~]# cd study/
[root@master study]# mkdir A B merged
[root@master study]# echo "This is file A/a" > A/a
[root@master study]# echo "This is file A/x" > A/x
[root@master study]# echo "This is file B/b" > B/b
[root@master study]# echo "This is file B/x" > B/x[root@master study]# yum install tree

 随后就可以用tree命令查看本文件夹下的文件结构

[root@master study]# tree
.
├── A
│   ├── a
│   └── x
├── B
│   ├── b
│   └── x
├── merged
├── ns
└── ns.c

 使用OverlayFS 实现联合文件挂载,合并目录A和B。首先创建一个空目录workdir,这是一个OverlayFS所需的工作目录,类似于swap a和b的值的时候需要一个额外的变量c。

mkdir workdir
sudo mount -t overlay overlay -o lowerdir=./B,upperdir=./A,workdir=./workdir ./merged

随后可以看到新的文件系统。由于upperdir是A,所以联合挂载后的x为A中的x。

[root@master study]# tree
.
├── A
│   ├── a
│   └── x
├── B
│   ├── b
│   └── x
├── merged
│   ├── a
│   ├── b
│   └── x
├── ns
├── ns.c
└── workdir└── work
[root@master study]# cat merged/x
This is file A/x

联合文件挂载在某种程度上可以被看作是一种“障眼法”,因为它创建了一个虚拟的、合并的文件视图,而不改变底层的实际数据。联合挂载并不改变数据本身,而是提供一个不一样的文件视图,所有的更改可以看作是发生在特定的层,而底层实际没有改变。执行umount文件,可以看到merged文件夹马上消失。

[root@master study]# unmount ./merged
-bash: unmount: 未找到命令
[root@master study]# umount ./merged
[root@master study]# tree
.
├── A
│   ├── a
│   └── x
├── B
│   ├── b
│   └── x
├── merged
├── ns
├── ns.c
└── workdir└── work
 docker镜像层

Docker 容器使用联合文件系统来构建层次化的镜像。容器的所有文件系统操作都发生在上层,而基础镜像保持不变。联合文件系统可以方便地创建文件系统的快照,允许用户在不修改基础层的情况下进行更改,并且可以轻松恢复到之前的状态。

启动一个基于Ubuntu的容器:

docker run -d ubuntu:latest sleep 3600

通过docker image inspect ubuntu:latest来查看这个ubuntu基础镜像,在rootFS上可以看到容器镜像现在有14层。这14层就是14个增量 rootfs,每一层都是 Ubuntu 操作系统文件与目录的一部分;而在使用镜像时,Docker 会把这些增量联合挂载在一个统一的挂载点上(等价于前面例子里的“/merged”目录)。

查看container的ID,并通过docker inspect <containerID>来查看挂载点。

[root@master study]# docker ps | grep ubuntu
5a8549d0c32a   ubuntu:latest                                       "sleep 3600"              42 minutes ago   Up 42 minutes             boring_grothendieck

去查看这个挂载点,可以看到一个完整的操作系统目录。这个merged目录是挂载完成后的视图。

[root@master 0c383cfe44e4ede5fd84288b1c3b8651c1edbe1561a7567e3cd92b20266ab4b3]# ls
diff  link  lower  merged  work
[root@master 0c383cfe44e4ede5fd84288b1c3b8651c1edbe1561a7567e3cd92b20266ab4b3]# cd merged/
[root@master merged]# ls
bin  boot  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

可以看到在merged平级有一个work的目录,这是因为宿主机使用的是overlayFS,所以需要一个工作目录。这个目录在挂载完之后是空的。

通过cat /proc/mounts | grep 0c383cfe44e4ede5fd84288b1c3b8651c1edbe1561a7567e3cd92b20266ab4b3/来查看挂载信息,可以看到完整的挂载视图:

overlay /var/lib/docker/overlay2/0c383cfe44e4ede5fd84288b1c3b8651c1edbe1561a7567e3cd92b20266ab4b3/merged overlay rw,relatime,
lowerdir=/var/lib/docker/overlay2/l/4KS4LDDDYH5ZFTDRMDYGXXNJJE:
/var/lib/docker/overlay2/l/NFHPILK2Y3EBWC6L2K4E4XZGXF:
/var/lib/docker/overlay2/l/PQ4C3RWQQE46KYSJ54DFCCWPJT:
/var/lib/docker/overlay2/l/X5LNTQDOQ5OEBCCIX27NYVHZER:
/var/lib/docker/overlay2/l/EKTRMQ3ITPCPOCYSEIPTD5RCYK:
/var/lib/docker/overlay2/l/2H4OSPEIBN3TYEYNX5ANGHDTK6:
/var/lib/docker/overlay2/l/YJLD4GRHMSVTDFYCYN43QIYKRS:
/var/lib/docker/overlay2/l/6YEMJ6TZO3QOFCBEJDCCEM6WYZ:
/var/lib/docker/overlay2/l/J7NHZE5GGSZVYMD3SRL4UYTTQ5:
/var/lib/docker/overlay2/l/B3BHKOP3XBP3QIFQEEZEQ6V7E3:
/var/lib/docker/overlay2/l/PEAIPLMWYIGCWQRNFVBRXIBCK3:
/var/lib/docker/overlay2/l/QXDWMP3XFAHQZVX4MWTLNWDF3K:
/var/lib/docker/overlay2/l/AGRN4UADXCVRYG3DVUBZFNUDP3:
/var/lib/docker/overlay2/l/4ZPBVQ7IUTOSAW6QETXOE2K44T:
/var/lib/docker/overlay2/l/BX7HBF725HYULMLF2NC5V2WLU2,
upperdir=/var/lib/docker/overlay2/0c383cfe44e4ede5fd84288b1c3b8651c1edbe1561a7567e3cd92b20266ab4b3/diff,
workdir=/var/lib/docker/overlay2/0c383cfe44e4ede5fd84288b1c3b8651c1edbe1561a7567e3cd92b20266ab4b3/work 0 0

有lowerdir,upperdir和workdir。Lowerdir这些目录是只读的镜像层。我们可以看到这里只读镜像层有14层,而lowerdir也有14层,事实上他们是一一对应的。

upperdir 是容器的可写层。所有对文件系统的修改都会存储在这个目录下。upperdir 作为容器的可写层,始终是一个单独的层,因为 Docker 的设计使得容器的文件系统操作不会在运行时创建新的层。Docker 的分层文件系统主要用于镜像的构建和存储,而不是容器运行时的文件系统管理。workdir是一个工作目录,用于存储挂载操作临时文件的地方,以便协调upperdir和lowerdir的操作。

以下是AuFS的rootfs结构,包含一个init层。但是OverlayFS的文件结构对于init层有别的处理。在 overlay2 中,Docker 在容器启动时创建一个 init 目录,用于容器初始化时的特殊状态管理。它主要用于容器启动时的初始化工作,随后与容器的可写层分离。这个 init 目录并不是在每个容器的用户数据层中,而是在 Docker 的内部管理层中。
 

也就是说OverlayFS的文件系统中没有init层。可以看到这个容器的init文件夹,里面有一个commited文件。committed 文件通常是 Docker 用来标记 初始化层已经成功创建并提交 的标志。这个文件的存在表明,Docker 已经完成了容器的初始化过程,初始化层的所有更改已经被确认和应用。

[root@master study]# cd /var/lib/docker/overlay2/0c383cfe44e4ede5fd84288b1c3b8651c1edbe1561a7567e3cd92b20266ab4b3-init/
[root@master 0c383cfe44e4ede5fd84288b1c3b8651c1edbe1561a7567e3cd92b20266ab4b3-init]# ls
committed  diff  link  lower  work
只读层修改

可读写层是可以随意的删除和修改的,但是如果我们想删除只读层的一个文件,要如何做呢。

如果需要删除只读层中的一个文件,OverlayFS会在可读写层创建一个 whiteout 文件,将只读层中的文件“遮挡”起来。假设要删除只读层中一个名为 foo 的文件。
whiteout 文件:删除操作实际上是在可读写层创建一个名为 .wh.foo 的文件。
联合挂载:当这两个层被联合挂载后,.wh.foo 文件会“遮挡”掉 foo 文件,使其“消失”。
这种挂载方式称为“ro+wh”,即只读 + whiteout。形象地称之为“白障”。

只读层修改:基础镜像(例如 Ubuntu 镜像)的内容本身是无法直接修改的。你不能直接修改镜像中的文件,因为它们是以只读方式挂载的。然而,Docker 使用了一种技术,称为 写时复制(Copy-on-Write,简称 CoW),来解决这个问题。当你尝试在容器中修改某个文件时,Docker 并不会直接修改基础镜像中的文件,而是复制该文件到一个可写层,并在这个可写层上进行修改。当你在容器内修改 /etc/hosts 文件时,Docker 并不会直接修改只读的镜像层中的文件。相反,Docker 会将 /etc/hosts 文件从只读层复制到可写层,并在可写层中对其进行修改。之后,容器对 /etc/hosts 文件的访问将指向可写层中的副本,而不是只读层中的原始文件。

当你在容器中通过写时复制 (Copy-on-Write, CoW) 修改了一些文件,并将这些修改保存为一个新的镜像时,Docker 会在原本的只读层上增加一层,而不会直接修改原始的镜像层。这就是 Docker 镜像的分层结构。


http://www.ppmy.cn/devtools/128366.html

相关文章

重构复杂简单变量之用子类替换类型码

子类替换类型码 是一种用于将类型码替换为子类。当代码使用类型码&#xff08;通常是 int、string 或 enum&#xff09;来表示对象的不同类别&#xff0c;并且这些类别的行为有所不同时&#xff0c;使用子类可以更加清晰地表达这些差异并减少复杂的条件判断。 一、什么时候使用…

【书生大模型实战营】闯关任务1-入关岛

这里写自定义目录标题 第一关 L0G1000 Linux 基础知识 第一关 L0G1000 Linux 基础知识 SSH连接与端口映射并运行hello_world.py 关键截图&#xff1a;

Flask-SQLAlchemy 组件

一、ORM 要了解 ORM 首先了解以下概念。 什么是持久化 持久化 (Persistence)&#xff0c;即把数据&#xff08;如内存中的对象&#xff09;保存到可永久保存的存储设备中&#xff08;如磁盘&#xff09;。持久化的主要应用是将内存中的数据存储在关系型的数据库中&#xff0c;…

Android 开发 TabLayout 自定义指示器长度

前言 原生 TabLayout 的指示器长度是充满整个屏幕的&#xff0c;但在实际开发中 UI 会设计成 指示器的长度等于或者小于标题字体长度&#xff0c;如图 如果设置成跟字体长度一样即使用 API: mTabLayout.setTabIndicatorFullWidth(false);或者在 xml 布局文件中的TabLayout标签…

无人机封闭空间建图检测系统技术详解

无人机封闭空间建图检测系统技术是一种集成了多种传感器、智能算法与控制系统的高级技术&#xff0c;旨在实现无人机在复杂封闭环境下的自主导航、精确建图与高效检测。以下是对该技术的详细解析&#xff1a; 一、技术概述 无人机封闭空间建图检测系统通过集成激光雷达(LiDAR…

【设计模式-迪米特法则】

迪米特法则&#xff08;Law of Demeter&#xff0c;LoD&#xff09;&#xff0c;也称为最少知识原则&#xff08;Principle of Least Knowledge&#xff09;&#xff0c;是一种面向对象编程中的设计原则。它的核心思想是&#xff1a;一个对象应当尽可能少地了解其他对象&#x…

基于STM32的水温控制系统设计

引言 水温控制系统在家庭、工业和农业等多个领域具有重要应用&#xff0c;尤其在加热设备、热水器和地暖系统中&#xff0c;通过实时监测和调节水温可以提高能源利用效率&#xff0c;保证使用的舒适性和安全性。本文基于STM32设计了一个智能水温控制系统&#xff0c;集成温度传…

调查显示软件供应链攻击增加

OpenText 发布了《2024 年全球勒索软件调查》&#xff0c;强调了网络攻击的重要趋势&#xff0c;特别是在软件供应链中&#xff0c;以及生成式人工智能在网络钓鱼诈骗中的使用日益增多。 尽管各国政府努力加强网络安全措施&#xff0c;但调查显示&#xff0c;仍有相当一部分企…