文章目录
系列文章目录
【云原生-深入理解Kubernetes-1】容器的本质是进程
【云原生-深入理解Kubernetes-2】容器 Linux Cgroups 限制
👹 关于作者
大家好,我是秋意零。
😈 CSDN作者主页
- 😎 博客主页
👿 简介
点赞、收藏+关注下次不迷路!
欢迎加入云社区
一、回顾
上一章我们了解了 Linux Cgroups 限制,容器是如何使用这个 Linux Cgroups 来达到限制这个容器进程的。
二、容器进程的文件系统是什么样子的?
容器中 Namespace 的作用时 “隔离”,让容器进程只能看到自己这片小空间;Linux Cgroups 的作用时 “限制”,它给这片小空间修筑了一圈的围墙。这样进程就被放在了一个与世隔绝的房间里。这时候我们有了房间,房间有了墙,那我们房间的地基是什么呢?
- 说白了就是,容器进程的文件系统是什么样子的?
rootfs
为了使容器有一个自己独立的文件系统,我们可以在容器进程启动之前重新挂载它的根目录 “/”,由于 Mount Namespace 存在,这个挂载对宿主机是不可见的,容器进程可以在这个文件系统中随心所欲。
这个挂载在容器根目录上、用来为容器进程提供文件系统,就是所谓的“容器镜像”。它还有一个更为专业的名字,叫作:rootfs(根文件系统)。
$ ls /
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
一致性
rootfs 只是一个操作系统所包含的文件、配置和目录,并不包括操作系统内核。在 Linux 操作系统中,这两部分(操作系统、文件目录)是分开存放的,操作系统只有在开机启动时才会加载指定版本的内核镜像。这也说明了 rootfs 只有操作系统的 “身体”,没有操作系统的 “灵魂”。
rootfs 文件系统有一个重要特性:一致性
解决应用依赖关系
对于一个应用来说,操作系统本身才是它运行所需要的最完整的“依赖库”。
有了容器镜像 (rootfs)“打包操作系统的身体” 的能力,这个最基础的依赖环境也变成了应用沙盒的一部分,这也赋予了容器所谓的一致性: 无论是在本地、云端或者还是任何一个地方的机器上,用户只要解压打包好的容器镜像,那么这个应用运行所需要的完整的执行环境就被重现出来了。
解决复用性
这时出现了一个棘手的问题: 难道每次开发一个应用,或升级现有的应用,都要重复制作一次 rootfs 吗?
-
一种比较直观的解决办法是,我在制作 rootfs 的时候,每做一步“有意义”的操作,就保存一个 rootfs 出来, 这样其他同事就可以按需求去用他需要的 rootfs 了。
-
但是,这个解决办法并不具备推广性。原因在于,一旦你的同事们修改了这个 rootfs,新旧两个 rootfs 之间就没有任何关系了。这样做的结果就是极度的碎片化。
-
那么,既然这些修改都基于一个旧的 rootfs,我们能不能以增量的方式去做这些修改呢? 答案当然是肯定的。这样做的好处是,所有人都只需要维护相对于 base rootfs (基础 rootfs)修改的增量内容, 而不是每次修改都制造一个“fork”(分支)。
三、OverlayFS 联合文件系统
Docker 镜像,引入了层(layer)的概念。用户制作镜像的每一步操作,都会生成一个层,也就是一个增量 rootfs。这个操作使用的是:联合文件系统(OverlayFS ),也叫 overlay2。
OverlayFS 是一个现代联合文件系统。将 Linux 内核驱动程序称为 OverlayFS,将 Docker 存储驱动程序称为 overlay2。
OverlayFS 最主要的功能是将多个不同位置的目录联合挂载(union mount)到同一个目录下。
先决条件
OverlayFS 是推荐的存储驱动程序,满足以下先决条件,则支持:
- 4.0 或更高版本的 Linux 内核,或使用 3.10.0-514 或更高版本内核的 RHEL 或 CentOS。
- 该 overlay2 驱动程序在后备文件系统上受支持 xfs,但仅在 d_type=true 启用的情况下。用于 xfs_info 验证该 ftype 选项是否设置为 1。要正确格式化 xfs 文件系统,请使用标志 -n ftype=1.
- 更改存储驱动程序会使本地系统上的现有容器和图像无法访问。
docker save
在更改存储驱动程序之前保存镜像。
docker info 查看存储驱动信息:
$ docker info
...
...
Storage Driver: overlay2
Backing Filesystem: xfs
Supports d_type: true
Using metacopy: false
Native Overlay Diff: true
userxattr: false
...
...
Docker 使用 overlary2 存储驱动程序,会自动创建 lowerdir、upperdir、merged 和 workdir
覆盖挂载结构体。
OverlayFS 在单个 Linux 主机上将两个目录分层,并将它们呈现为单个目录,这些目录称为层(layer),两个目录统一为一个目录的过程称为联合挂载(详细过程看上面 两个目录 A 和 B 的例子)。
- OverlayFS 将下层目录称为:lowerdir
- OverlayFS 将上层目录称为:upperdir
- 统一视图通过其 merged 目录展现出来(上层和下层目录的联合挂载内容展现)
overlay2 驱动程序如何工作
结构图
下图显示了 Docker 镜像和 Docker 容器是如何分层的:
- lowerdir 是镜像层
- upperdir 是容器层
探索含义-磁盘上的镜像层和容器层
镜像层
使用 docker pull nginx
命令拉取下载镜像后,可以看到 nginx 镜像有 6 层 Docker 映像,对应到磁盘上是在 /var/lib/docker/overlay2
目录下。
$ docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
9e3ea8720c6d: Pull complete
bf36b6466679: Pull complete
15a97cf85bb8: Pull complete
9c2d6be5a61d: Pull complete
6b7e4a5c7c7a: Pull complete
8db4caa19df8: Pull complete
Digest: sha256:480868e8c8c797794257e2abd88d0f9a8809b2fe956cbfbc05dcc0bca1f7cd43
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
可以使用 docker image inspect
命令查看镜像的分层以及结构体
可以看到 overlay2 目录下自动创建了 6 个目录。镜像层 ID 与目录 ID 不对应。
目录 l (小写的 L),包含作为符号链接的缩短层标识符。这些标识符用于避免达到命令参数的字符长度限制(比如 mount)
镜像层最底层包含一个名为 diff
的目录,包含该镜像层(lowerdir)的内容;以及一个名为 link 的文件,其中包含该 image 层(layer)缩短标识符的名称。
[root@test_2 overlay2]# cd /var/lib/docker/overlay2/
[root@test_2 overlay2]# ls 228b2f89b5e4316b9b613fc6233551a96e64f51e827972caf86acb82338c7fdb/
committed diff link
[root@test_2 overlay2]# ls 228b2f89b5e4316b9b613fc6233551a96e64f51e827972caf86acb82338c7fdb/diff/
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
[root@test_2 overlay2]# cat 228b2f89b5e4316b9b613fc6233551a96e64f51e827972caf86acb82338c7fdb/link
2L2KGYQSG5H43HK6LRJOT7TU4J
镜像层第二低层和每个更高层包含一个名为 lower 的文件(里面记录了镜像层每层的缩短标识符的名称);一个名为 diff 的目录(其中包含其内容);还包含一个 merged 目录(其中包含其父层和自身的统一内容);以及一个 work 目录 (OverlayFS 内部使用的目录);最后一个 link 文件,其中包含该 image 层(layer)缩短标识符的名称。
$ ls /var/lib/docker/overlay2/5605b29d1d7269f11acb3653a8032299b58ca7d111e43dd215fab660851b114c/
committed diff link lower work
$ ll /var/lib/docker/overlay2/5605b29d1d7269f11acb3653a8032299b58ca7d111e43dd215fab660851b114c/diff
total 0
drwxr-xr-x 2 root root 41 May 4 03:51 docker-entrypoint.d
# 记录了镜像层每层的缩短标识符的名称
$ cat /var/lib/docker/overlay2/5605b29d1d7269f11acb3653a8032299b58ca7d111e43dd215fab660851b114c/lower
l/NR7U3COVTPVL3XSBKQ77AYXAMR:l/656UDNBIOMRQG4UU6VGTOPQVMT:l/BJ6JTE7P4WDJROD7M6R56HDZ7G:l/DIKKGXFOP6YWKBZL5SRAQLG3SR:l/2L2KGYQSG5H43HK6LRJOT7TU4J
# 验证 lower 文件内容
$ ll /var/lib/docker/overlay2/l/
total 0
lrwxrwxrwx 1 root root 72 May 23 14:44 2L2KGYQSG5H43HK6LRJOT7TU4J -> ../228b2f89b5e4316b9b613fc6233551a96e64f51e827972caf86acb82338c7fdb/diff
lrwxrwxrwx 1 root root 72 May 23 14:44 656UDNBIOMRQG4UU6VGTOPQVMT -> ../7d85f043da224ace0401e97516cf9b8c63cfac6e9804d5f47b3d0f2fda2e9c11/diff
lrwxrwxrwx 1 root root 72 May 23 14:44 BJ6JTE7P4WDJROD7M6R56HDZ7G -> ../2fd591e59cfa565d2377d074224fb823f09917015b9ce3f46d1285f958dfb7fe/diff
lrwxrwxrwx 1 root root 72 May 23 14:44 DIKKGXFOP6YWKBZL5SRAQLG3SR -> ../b1c6e8fcd75408f47820cab5e2bac117275d23deae5723489a4a6549fced8d45/diff
lrwxrwxrwx 1 root root 72 May 23 14:44 MFPTI6FPG5SCBEN4Q7NFQLO2YO -> ../5605b29d1d7269f11acb3653a8032299b58ca7d111e43dd215fab660851b114c/diff
lrwxrwxrwx 1 root root 72 May 23 14:44 NR7U3COVTPVL3XSBKQ77AYXAMR -> ../eedf9b88406b4323a634400b593c74be86c9e774ea7df8191c9f395a59f00c3d/diff
容器层
容器层也存在于 Docker 主机文件系统的磁盘上,位于 /var/lib/docker/overlay/
[root@test_2 ~]# docker run -idt --name nginx nginx
3345e0df177c94d0f16dfb82918d139937c1e2e3acfc8f1514844f77752cf74d
[root@test_2 ~]#
[root@test_2 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3345e0df177c nginx "/docker-entrypoint.…" 13 seconds ago Up 12 seconds 80/tcp nginx
使用 docker container inspect
命令查看容器层:
查看运行容器的目录:
$ ll /var/lib/docker/overlay2/b8925e85fdaa4abff5cfd7f988e2fa08da349b8b983b864b0686b19c0e825d09/
total 8
drwxr-xr-x 5 root root 39 May 23 16:49 diff
-rw-r--r-- 1 root root 26 May 23 16:49 link
-rw-r--r-- 1 root root 202 May 23 16:49 lower
drwxr-xr-x 1 root root 39 May 23 16:49 merged
drwx------ 3 root root 18 May 23 16:49 work
$ ls /var/lib/docker/overlay2/b8925e85fdaa4abff5cfd7f988e2fa08da349b8b983b864b0686b19c0e825d09/diff/
etc run var
$ cat /var/lib/docker/overlay2/b8925e85fdaa4abff5cfd7f988e2fa08da349b8b983b864b0686b19c0e825d09/link
WOTO7SJMAF42QFMCPIARE7RE46
$ cat /var/lib/docker/overlay2/b8925e85fdaa4abff5cfd7f988e2fa08da349b8b983b864b0686b19c0e825d09/lower
l/ZFU53D2JEDP4EM5HRKMZ6TID3R:l/MFPTI6FPG5SCBEN4Q7NFQLO2YO:l/NR7U3COVTPVL3XSBKQ77AYXAMR:l/656UDNBIOMRQG4UU6VGTOPQVMT:l/BJ6JTE7P4WDJROD7M6R56HDZ7G:l/DIKKGXFOP6YWKBZL5SRAQLG3SR:l/2L2KGYQSG5H43HK6LRJOT7TU4J
$ ls /var/lib/docker/overlay2/b8925e85fdaa4abff5cfd7f988e2fa08da349b8b983b864b0686b19c0e825d09/merged
bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
$ ll /var/lib/docker/overlay2/b8925e85fdaa4abff5cfd7f988e2fa08da349b8b983b864b0686b19c0e825d09/work/
total 0
d--------- 2 root root 6 May 24 10:42 work
使用 mount 命令 ,查看将存储驱动程序与 Docker 一起使用时存在的挂载 overlay(容器运行时才会看到挂载 merged 目录)。括号开头内容: rw ,表示挂载是可读写的。
四、overlay2 容器读写如何工作
还是把上面的结构图拿过来
只读层
通过上面的讲述,大概也已经了解了,只读层是我们镜像层(lowerdir)。
读取文件
考虑三种情况,其中文件在镜像层和容器层同时存在使用覆盖进行读取访问。
- 文件在容器层中不存在: 如果容器打开一个文件进行读取访问,而这个文件在容器中(upperdir)不存在,则从镜像中(lowerdir)读取。这会产生非常小的性能开销。
- 文件存在容器层中: 如果容器打开一个文件进行读取访问,而这个文件在容器中(upperdir)存在,那么就直接读取,不在镜像中(lowerdir)读取。
- 文件同时存在于镜像层和容器层: 如果容器打开一个文件进行读取访问,而这个文件同时在镜像层和容器层存在,容器层 ( upperdir) 中的文件掩盖镜像层 (lowerdir) 中同名的文件,则读取容器层中的版本。
可读写层
可读写层是我们容器层(upperdir)。
修改文件和目录
- 1.第一次写入文件: 容器第一次写入文件时,容器中 (upperdir) 不存在该文件。驱动程序 overlay2 执行 copy_up 操作将文件从镜像层 ( lowerdir) 复制到容器层 ( upperdir)。
OverlayFS 工作在文件级别而不是块级别。这意味着所有 OverlayFS copy_up 操作都会复制整个文件,即使文件非常大并且只修改了一小部分。这会对容器写入性能产生显着影响。- copy_up 操作仅在第一次写入给定文件时发生。对同一文件的后续写入将针对已复制到容器的文件副本进行操作。
- OverlayFS 适用于多层。这意味着在具有多层的图像中搜索文件时,性能可能会受到影响。
- 2.删除文件和目录:
- 容器中删除文件: 删除镜像层中的文件,会在容器中 (upperdir) 创建一个 whiteout 文件,镜像层(lowerdir )中的文件版本没有被删除(因为是 lowerdir 只读的)。但是 whiteout 文件阻止镜像层对容器层可用。
- 容器中删除目录: 会在容器中 ( upperdir ) 创建一个不透明的目录。这与 whiteout 文件的工作方式相同,并有效地防止目录被访问,即使它仍然存在于镜像中 ( lowerdir)。这样一看在容器中目录就被删除了。
- 3.重命名目录: 调用 rename(2) 操作重命名目录,仅在源路径和目标路径都在顶层时才允许调用。否则,返回 EXDEV 错误(“不允许跨设备链接”)。您的应用程序需要设计为处理EXDEV 并回退到“复制和取消链接”策略。
总结
这里,介绍了 Linux 容器进程文件系统的实现方式,而这种机制,正是我们经常提到的容器镜像,也叫作:rootfs。它只是一个操作系统的所有文件和目录,并不包含内核,所以对比虚拟机的镜像要小的多。
通过结合使用 Mount Namespace 和 rootfs,容器就能够为进程构建出一个完善的文件系统隔离环境。这需要依赖 chroot 和 pivot_root 切换进程根目录的能力。
在 rootfs 根文件系统的基础上,Docker 使用了一个 overlay2 联合文件挂载。并提出了容器镜像中“层”(layer)的概念。
✊ 最后
👏 我是秋意零,欢迎大家一键三连、加入云社区
👋 我们下期再见(⊙o⊙)!!!
参考
参考《深入剖析Kubernetes》作者 张磊
https://docs.docker.com/storage/storagedriver/overlayfs-driver/