namespace和cgroup都不是新技术,都利用了Linux现有的已经相对成熟的容器化技术,它都不是docker的创举,docker的创举是overylayfs,它开创性的提出了基于分层的文件系统来管理整个容器镜像。
文件系统
容器使用了union fs文件系统,可以将多个目录,mount成为一个目录(虚拟目录里面)。不同的目录在这个虚拟目录里面又可以有独立的权限,也即是将两个不同的目录,挂载到同一个目录下面,然后通过读写权限的设置,使得呈现最终的文件目录给容器(模拟成了一个完整的操作系统)。
容器镜像
dockerfile它利用源码的方式,dockerfile里面定义了你的基础镜像是什么,中间要去运行哪些命令,比如中间需要安装依赖包,或者去拷贝一些要运行的二进制文件,最后还允许通过entrypoint命令来定义容器镜像运行的时候要执行哪些进程的命令。
通过这种方式以源代码的方式去定义一个容器镜像用来运行哪一个应用程序的,这刚好符合了微服务的架构思想
Union FS
- 将不同目录挂载到同一个虚拟文件系统下 (unite several directories into a single virtual filesystem)的文件系统。
- 支持为每一个成员目录(类似Git Branch)设定 readonly、readwrite 和 whiteout-able 权限。
- 文件系统分层, 对 readonly 权限的 branch 可以逻辑上进行修改(增量地, 不影响 readonly 部分的)。
- 通常 Union FS 有两个用途, 一方面可以将多个disk挂到同一个目录下, 另一个更常用的就是将 一个 readonly 的 branch 和一个 writeable 的 branch 联合在一起。
Docker 的文件系统
Linux文件系统会分为两个部分,一个是bootfs,一个是rootfs
典型的 Linux 文件系统组成:Linux会分 Bootfs rootfs
• Bootfs(boot file system)(分为两个子模块)
引导操作系统的启动并且加载 kernel,其实主要就是将kernel加载出来
当 kernel 被加载到内存中后 umount bootfs。
• rootfs (root file system)
• kernel加载完成之后会去加载rootfs,这个rootfs就是我们日常所看到的/dev,/proc,/bin,/etc 等标准目录和文件。
• 对于不同的 linux 发行版, bootfs 基本是一致的,但 rootfs 会有差别。
Docker 启动
docker本身是没有完整的操作系统的,它利用的主机,无论是物理机还是虚拟机,docker启动的容器没有独立的操作系统,不需要自己的Bootloader ,所以没有bootfs的,因为主机已经启动起来了,但是它需要rootfs。
Linux
• 在启动后,首先将 rootfs 设置为 readonly, 进行一系列检查, 然后将其切换为 “readwrite”
供用户使用。
Docker启动(以unionfs方式加载文件系统)
• 初始化时也是将 rootfs 以 readonly 方式加载并检查,然而接下来利用 union mount 的方式
将一个 readwrite 文件系统挂载在 readonly 的 rootfs 之上
• 并且允许再次将下层的 FS(file system) 设定为 readonly 并且向上叠加
• 这样一组 readonly 和一个 writeable 的结构构成一个 container 的运行时态, 每一个 FS 被称
作一个 FS 层
但是在 Docker里,root文件系统永远只能是只读状态
docker启动过程:首先初始化rootfs以readonly方式加载并且检查,但是检查完毕之后,并不是将rootfs直接变为可写,让你直接去写,而是在这个readonly的文件层基础之上,再添加新的readwrite层。这样一层一层堆叠,下面的层永远都是只读的,当所有层级加载完毕之后,它会将最上面的一层变为readwrite。所有你针对这个容器的修改事实上都是在最上面这一层进行修改,并不会修改下面的readonly层。
Union FS是层层叠加的,可以看到在做镜像构建的时候,差不多每条指令都会作为一个文件层保存下来。
可以查看到每一层里面执行了什么样的命令。
在docker run的时候,就会去回放这个镜像,按照层级一级一级的去加载,通过unionfs方式去加载,这会有不同的驱动,会将dockerfile里面的每一层加载,每一层是readonly的层,然后不断的叠加,将下面一层变为readonly,最终将上面变为writeable,这个时候完整的操作系统所需要的文件系统就存在了,rootfs也就存在了,容器就可以去读取这些文件了。
写操作
由于镜像具有共享特性,所以对容器可写层的操作需要依赖存储驱动提供的写时复制和用时分配 机制,以此来支持对容器可写层的修改,进而提高对存储和内存资源的利用率。
写操作对于这种文件系统,修改一个已经存在的文件,这个文件又在底层。不会直接去修改底层的文件,需要将这个文件复制出来,然后做的任何更改都会保存在上面那层。 (一个镜像是可以被多个容器使用的,而且一个镜像里面的不同层也是可以被多个镜像共享的,所以就有了写时复制,这样就确保下面的基础镜像层永远都不会被修改)
• 写时复制
写时复制,即 Copy-on-Write。一个镜像可以被多个容器使用,但是不需要在内存和磁盘上做多
个拷贝。在需要对镜像提供的文件进行修改时,该文件会从镜像的文件系统被复制到容器的可写
层的文件系统进行修改,而镜像里面的文件不会改变。不同容器对文件的修改都相互独立、互不
影响。
• 用时分配
按需分配空间,而非提前分配,即当一个文件被创建出来后,才会分配空间。这样有效节省了主机磁盘空间
容器存储驱动
随着overlayfs出现了第二个版本,引入了内核的发行版,如果要选文件系统更多的是overlayfs。device mapper性能要比overlayfs性能差不少。所以选择存储驱动的时候就直接选overylayfs2,新版本的Linux kernel就默认带了overylayfs的驱动,而且是overylayfs2。
docker本身内嵌了containerd,相当于docker功能当中的一个子集,而且它作为一个独立的开源项目出现。
以 OverlayFS 为例
OverlayFS 也是一种与 AUFS 类似的联合文件系统,同样属于文件级的存储驱动,包含了最初的 Overlay 和更新更稳定的 overlay2。
Overlay 只有两层:upper 层和 Lower 层。Lower 层代表镜像层,upper 层代表容器可写层
相当于可以将多个目录mount成为一个合并的目录,在合并的时候有多个源,需要指定哪个源是下层,哪个源是上层。合并之后就会做merge。
- 如果一个文件在下层,那么合并之后是可以看到的。
- 如果一个文件在上层,合并之后还是可以看的到的。
- 如果文件即来自上层和下层,上层文件会将下层文件覆盖掉。
OverlayFS 文件系统练习
可以看到容器镜像文件系统是怎么来的,dockerfile里面有很多层,每一层都被加载到LowerDir里面了,然后最上面有UpperDir。