🌈个人主页: 秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
🔥 系列专栏: Docker_秦jh_的博客-CSDN博客
目录
[Docker 镜像原理](#Docker 镜像原理)
[Union FS(联合文件系统)](#Union FS(联合文件系统))
[再看 Docker 镜像是什么](#再看 Docker 镜像是什么)
[Docker 分层存储实现原理](#Docker 分层存储实现原理)
[Union FS 的原理](#Union FS 的原理)
[overlay2 实现](#overlay2 实现)
[docker 镜像加载原理](#docker 镜像加载原理)
[Docker 卷原理](#Docker 卷原理)
[Docker 卷机制](#Docker 卷机制)
前言
💬 hello! 各位铁子们大家好哇。
今日更新了Docker相关内容
🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝
Docker 镜像原理
docker 是操作系统层的虚拟化,所以 docker 镜像的本质是在模拟操作系统。
操作系统基础
操作系统由:进程调度子系统、进程通信子系统、内存管理子系统、设备管理子系统、文件管理子系统、网络通信子系统、作业控制子系统组成。
Linux 的文件管理子系统由 bootfs 和 rootfs 组成。
(1). bootfs:要包含 bootloader 和 kernel, bootloader 主要是引导加载 kernel, Linux 刚 启动时会加载 bootfs 文件系统,在 Docker 镜像的最底层是引导文件系统 bootfs。这 一层与我们典型的 Linux/Unix 系统是一样的,包含 boot 加载器和内核。当 boot 加载 完成之后整个内核就都在内存中了,此时内存的使用权已由 bootfs 转交给内核,此时 系统也会卸载 bootfs。
(2). rootfs: 在 bootfs 之上。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等 标准目录和文件。rootfs 就是各种不同的操作系统发行版,比如 Ubuntu,Centos 等等。

Union FS(联合文件系统)
联合文件系统(Union File System),2004 年由纽约州立大学开发,它可以把多个 目录内容联合挂载到同一个目录下,而目录的物理位置是分开的。UnionFS 可以把只 读和可读写文件系统合并在一起,具有写时复制功能,允许只读文件系统的修改可以 保存到可写文件系统当中。
UnionFS(联合文件系统)是一种分层、轻量级并且高性能的文件系统,它支持对 文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚 拟文件系统下。UnionFS 是一种为 Linux,FreeBSD 和 NetBSD 操作系统设计的把其 他文件系统联合到一个联合挂载点的文件系统服务。它使用 branch 把不同文件系统的 文件和目录"透明地"覆盖,形成一个单一一致的文件系统。这些 branches 或者是 read-only 或者是 read-write 的,所以当对这个虚拟后的联合文件系统进行写操作的时 候,系统是真正写到了一个新的文件中。看起来这个虚拟后的联合文件系统是可以对 任何文件进行操作的,但是其实它并没有改变原来的文件,这是因为 unionfs 用到了一 个重要的资管管理技术叫写时复制。
写时复制(copy-on-write,下文简称 CoW),也叫隐式共享,是一种对可修改资源实现高效复制的资源管理技术。它的思想是,如果一个资源是重复的,但没有任何 修改,这时候并不需要立即创建一个新的资源;这个资源可以被新旧实例共享。创建 新资源发生在第一次写操作,也就是对资源进行修改的时候。通过这种资源共享的方 式,可以显著地减少未修改资源复制带来的消耗,但是也会在进行资源修改的时候增 加小部分的开销。
再看 Docker 镜像是什么
image 里面是一层层文件系统 Union FS。联合文件系统,可以将几层目录挂载到 一起,形成一个虚拟文件系统。虚拟文件系统的目录结构就像普通 linux 的目录结构一 样,docker 通过这些文件再加上宿主机的内核提供了一个 linux 的虚拟环境。
每一层文件系统我们叫做一层 layer,联合文件系统可以对每一层文件系统设置三 种权限,只读(readonly)、读写(readwrite)和写出(whiteout-able),但是 docker 镜像中每一层文件系统都是只读的。
构建镜像的时候,从一个最基本的操作系统开始,每个构建的操作都相当于做一层 的修改,增加了一层文件系统。一层层往上叠加,上层的修改会覆盖底层该位置的可 见性,这也很容易理解,就像上层把底层遮住了一样。当你使用的时候,你只会看到 一个完全的整体,你不知道里面有几层,也不清楚每一层所做的修改是什么。

可以看到镜像分层结构有以下特性
(1)镜像共享宿主机的 kernel
(2)base 镜像是 linux 的最小发行版
(3)同一个 docker 主机支持不同的 Linux 发行版
(4)采用分层结构,可以上层引用下层,最大化的共享资源
(5)容器层位于可写层,采用 cow 技术进行修改,该层仅仅保持变化的部分,并不 修改镜像下面的部分
(6)容器层以下都是只读层
(7)docker 从上到下找文件
镜像实现原理
Docker 分层存储实现原理
分层存储实现方式
docker 镜像技术的基础是联合文件系统(UnionFS),其文件系统是分层的
目前 docker 支持的联合文件系统有很多种,包括:AUFS、overlay、overlay2、 DeviceMapper、VSF 等
Linux 中各发行版实现的 UnionFS 各不相同,所以 docker 在不同 linux 发行版中使用 的也不同。通过 docker info 命令可以查看当前系统所使用哪种 UnionFS,常见的几种 发行版使用如下:
CentOS, Storage Driver: overlay2、overlay
debain, Storage Driver: aufs
RedHat, Storage Driver: devicemapper
overlay2 是 overlay 的升级版,官方推荐,更加稳定,而新版的 docker 默认也是这个 驱动,linux 的内核 4.0 以上或者或使用 3.10.0-514 或更高版本内核的 RHEL 或 CentOS。
Union FS 的原理
docker 镜像由多个只读层叠加面成,启动容器时,docker 会加载只读镜像层并在 镜像栈顶部加一个读写层;
如果运行中的容器修改了现有的一个已经存在的文件,那该文件将会从读写层下 面的只读层复制到读写层,该文件版本仍然存在,只是已经被读写层中该文件的副本 所隐藏,此即"写时复制(COW)"机制。

如果一个文件在最底层是可见的,如果在 layer1 上标记为删除,最高的层是用户看到 的 Layer2 的层,在 layer0 上的文件,在 layer2 上可以删除,但是只是标记删除,用 户是不可见的,总之在到达最顶层之前,把它标记来删除,对于最上层的用户是不可 见的,当标记一删除,只有用户在最上层建一个同名一样的文件,才是可见的。
overlay2 实现
架构
OverlayFS 将单个 Linux 主机上的两个目录合并成一个目录。这些目录被称为层,统 一过程被称为联合挂载 OverlayFS 底层目录称为 lowerdir, 高层目录称为 upperdir,合并统一视图称为 merged

图中可以看到三个层结构,即 lowerdir、upperdir、merged 层
分层
- lowerdir 层
其中 lowerdir 是只读的镜像层(image layer),其中就包含 bootfs/rootfs 层, bootfs(boot file system)主要包含 bootloader 和 kernel,bootloader 主要是引导加载 kernel,当 boot 成功 kernel 被加载到内存中,bootfs 就被 umount 了,rootfs(root file system)包含的就是典型 Linux 系统中的/dev、/proc、/bin、/etc 等标准目录。
lowerdir 是可以分很多层的,除了 bootfs/rootfs 层以外,还可以通过 Dockerfile 建立很 多 image 层
- upperdir 层
upper 是容器的读写层,采用了 CoW(写时复制)机制,只有对文件进行修改才会将文件拷 贝到 upper 层,之后所有的修改操作都会对 upper 层的副本进行修改。upperdir 层是 lowerdir 的上一层,只有这一层可读可写的,其实就是 Container 层,在启动一个容器 的时候会在最后的 image 层的上一层自动创建,所有对容器数据的更改都会发生在这 一层
- workdir 层
它的作用是充当一个中间层的作用,每当对 upper 层里面的副本进行修改时,会先当到 workdir,然后再从 workdir 移动 upper 层
- merged 层
是一个统一图层,从 mergedir 可以看到 lower,upper,workdir 中所有数据的整合,整 个容器展现出来的就是 mergedir 层.merged 层就是联合挂载层,也就是给用户暴露 的统一视觉,将 image 层和 container 层结合,就如最上边的图中描述一致,同一文 件,在此层会展示离它最近的层级里的文件内容,或者可以理解为,只要 container 层 中有此文件,便展示 container 层中的文件内容,若 container 层中没有,则展示 image 层中的。
如何完成读写
1、读:
如果文件在 upperdir(容器)层,直接读取文件;
如果文件不在 upperdir(容器)层,则从镜像层(lowerdir)读取;
2、写:
首次写入:如果 upperdir 中不存在,overlay 和 overlay2 执行 copy_up 操作,把文件 从 lowdir 拷贝到 upperdir 中,由于 overlayfs 是文件级别的(即使只有很少的一点修改, 也会产生 copy_up 的动作),后续对同一文件的再次写入操作将对已经复制到容器层的 文件副本进行修改,这也就是常常说的写时复制(copy-on-write)。
删除文件或目录:当文件被删除时,在容器层(upperdir)创建 whiteout 文件,镜像层 (lowerdir)的文件是不会被删除的,因为它们是只读的,但 whiteout 文件会阻止它们显 示,当目录被删除时,在容器层(upperdir)一个不透明的目录,这个和上边的 whiteout 的原理一样,组织用户继续访问,image 层不会发生改变
3、注意事项
copy_up 操作只发生在文件首次写入,以后都是只修改副本。
容器层的文件删除只是一个"障眼法",是靠 whiteout 文件将其遮挡,image 层并没有删 除,这也就是为什么使用 docker commit 提交保存的镜像会越来越大,无论在容器层 怎么删除数据,image 层都不会改变。
docker 镜像加载原理
boots(boot file system)主要包含 bootloader 和 Kernel, bootloader 主要是引导加 kernel,Linux 刚启动时会加 bootfs 文件系统,在 Docker 镜像的最底层是 bootfs。这一 层与我们典型的 Linux/Unix 系统是一样的,包含 boot 加載器和内核。当 boot 加载完 成之后整个内核就都在内存中了,此时内存的使用权已由 bootfs 转交给内核,此时系 统也会卸载 bootfs。

rootfs(root file system),在 bootfs 之上。包含的就是典型 Linux 系统中的 /dev,/proc,/bin,/etc 等标准目录和文件。 rootfs 就是各种不同的操作系统发行版,比如 Ubuntu,Centos 等等。
典型的 Linux 在启动后,首先将 rootfs 置为 readonly, 进行一系列检查, 然后将其切换 为 "readwrite" 供用户使用。在 docker 中,起初也是将 rootfs 以 readonly 方式加载并 检查,然而接下来利用 union mount 的将一个 readwrite 文件系统挂载在 readonly 的 rootfs 之上,并且允许再次将下层的 file system 设定为 readonly 并且向上叠加, 这样 一组 readonly 和一个 writeable 的结构构成一个 container 的运行目录, 每一个被称作 一个 Layer

下面的这张图片非常好的展示了组装的过程,每一个镜像层都是建立在另一个镜像层 之上的,同时所有的镜像层都是只读的,只有每个容器最顶层的容器层才可以被用户 直接读写,所有的容器都建立在一些底层服务(Kernel)上,包括命名空间、控制组、 rootfs 等等,这种容器的组装方式提供了非常大的灵活性,只读的镜像层通过共享也 能够减少磁盘的占用。

Docker 卷原理
Docker 卷机制
Docker 又是如何做到把一个宿主机上的目录或者文件,挂载到容器里面去呢?
当容器进程被创建之后,尽管开启了 Mount Namespace,但是在它执行 chroot (chroot 就是可以改变某进程的根目录,使这个程序不能访问目录之外的其他目录, 这个跟我们在一个容器中是很相似的)之前,容器进程一直可以看到宿主机上的整个 文件系统。
而宿主机上的文件系统,也自然包括了我们要使用的容器镜像。这个镜像的各个层, 保存在 /var/lib/docker/overlay2/{layer id}/diff 目录下,在容器进程启动后,它们会被联 合挂载在 /var/lib/docker/{layerid}/merged/ 目录中,这样容器所需的 rootfs 就准备好了。
所以,我们只需要在 rootfs 准备好之后,在执行 chroot 之前,把 Volume 指定的宿主 机目录(比如 /home 目录),挂载到指定的容器目录(比如 /test 目录)在宿主机上对 应的目录(即 /var/lib/docker/aufs/mnt/可读写层 ID/test)上,这个 Volume 的挂载工 作就完成了。
由于执行这个挂载操作时,"容器进程"已经创建了,也就意味着此时 Mount Namespace 已经开启了。所以,这个挂载事件只在这个容器里可见。你在宿主机上, 是看不见容器内部的这个挂载点的。这就保证了容器的隔离性不会被Volume 打破。
