Docker是Linux容器的封装,将应用和依赖包到一个可移植的容器中,然后发布到Linux机器上。容器完全使用沙箱机制,相互之间不会有任何接口。
Docker本质上是运行在宿主机上的进程,它通过namespace实现了资源隔离,并通过cgroups实现了资源限制,同时通过写时复制(copy-on-write)实现了高效的文件操作。
Namespace
Linux内核中提供了6种namespace隔离的系统调用,分别完成对文件系统、网络、进程间通信、主机名、进程号以及用户权限的隔离。
在同一个namespace下的进程可以感知彼此的变化,而对外界的进程一无所知。这样就可以让容器中的进程产生错觉,仿佛置身于一个独立的系统环境中,从而达到独立和隔离的目的。
镜像特点
分层
docker 镜像是采用分层的方式构建的,每个镜像都由一系列的 "镜像层" 组成。分层结构是 docker 镜像如此轻量的重要原因。当需要修改容器镜像内的某个文件时,只对处于最上方的读写层进行变动,不覆写下层已有文件系统的内容,已有文件在只读层中的原始版本仍然存在,但会被读写层中的新版本所隐藏。当使用 docker commit 提交这个修改过的容器文件系统为一个新的镜像时,保存的内容仅为最上层读写文件系统中被更新过的文件。分层达到了在不的容器同镜像之间共享镜像层的效果。写时复制
docker 镜像使用了写时复制(copy-on-write)的策略,在多个容器之间共享镜像,每个容器在启动的时候并不需要单独复制一份镜像文件,而是将所有镜像层以只读的方式挂载到一个挂载点,再在上面覆盖一个可读写的容器层。在未更改文件内容时,所有容器共享同一份数据,只有在 docker 容器运行过程中文件系统发生变化时,才会把变化的文件内容写到可读写层,并隐藏只读层中的老版本文件。写时复制配合分层机制减少了镜像对磁盘空间的占用和容器启动时间。内容寻址
在 docker 1.10 版本后,docker 镜像改动较大,其中最重要的特性便是引入了内容寻址存储(content-addressable storage) 的机制,根据文件的内容来索引镜像和镜像层。与之前版本对每个镜像层随机生成一个 UUID 不同,新模型对镜像层的内容计算校验和,生成一个内容哈希值,并以此哈希值代替之前的 UUID 作为镜像层的唯一标识。该机制主要提高了镜像的安全性,并在 pull、push、load 和 save 操作后检测数据的完整性。另外,基于内容哈希来索引镜像层,在一定程度上减少了 ID 的冲突并且增强了镜像层的共享。对于来自不同构建的镜像层,主要拥有相同的内容哈希,也能被不同的镜像共享。联合挂载
通俗地讲,联合挂载技术可以在一个挂载点同时挂载多个文件系统,将挂载点的原目录与被挂载内容进行整合,使得最终可见的文件系统将会包含整合之后的各层的文件和目录。实现这种联合挂载技术的文件系统通常被称为联合文件系统(union filesystem)。
联合挂载是用于将多个镜像层的文件系统挂载到一个挂载点来实现一个统一文件系统视图的途径,是下层存储驱动(aufs、overlay等) 实现分层合并的方式。所以严格来说,联合挂载并不是 docker 镜像的必需技术,比如在使用 device mapper 存储驱动时,其实是使用了快照技术来达到分层的效果。
docker镜像元数据
Docker 在管理镜像层元数据时采用的是从上至下 repository、image 和 layer 三个层次。
repository 元数据
repository.json存储了所有本地镜像的repository的名字,还有每个 repository 下的镜像的名字、标签及其对应的镜像 ID。
当前 docker 默认采用 SHA256 算法根据镜像元数据配置文件计算出镜像 ID。image 元数据
image 元数据包括了镜像架构(如 amd64)、操作系统(如 linux)、镜像默认配置、构建该镜像的容器 ID 和配置、创建时间、创建该镜像的 docker 版本、构建镜像的历史信息以及 rootfs 组成。
docker 利用 rootfs 中的 diff_id 计算出内容寻址的索引(chainID) 来获取 layer 相关信息,进而获取每一个镜像层的文件内容layer元数据
Docker 中定义了 Layer 和 RWLayer 两种接口,分别用来定义只读层和可读写层的一些操作,又定义了 roLayer 和 mountedLayer,分别实现了上述两种接口。其中,roLayer 用于描述不可改变的镜像层,mountedLayer 用于描述可读写的容器层。
具体来说,roLayer 存储的内容主要有索引该镜像层的 chainID、该镜像层的校验码 diffID、父镜像层 parent、graphdriver 存储当前镜像层文件的 cacheID、该镜像层的 size 等内容。
在 layer 的所有属性中,diffID 采用 SHA256 算法,基于镜像层文件包的内容计算得到。而 chainID 是基于内容存储的索引,它是根据当前层与所有祖先镜像层 diffID 计算出来。
- 如果该镜像层是最底层(没有父镜像层),该层的 diffID 便是 chainID。
- 该镜像层的 chainID 计算公式为 chainID(n)=SHA256(chain(n-1) diffID(n)),也就是根据父镜像层的 chainID 加上一个空格和当前层的 diffID,再计算 SHA256 校验码。
namespace
cgroup (Controller Group)
控制组可以提供对容器的内存、CPU、磁盘IO等资源进行限制和计费管理。
具体来看,控制组提供:
- 资源限制(Resource limiting):可以将组设置为不超过设定的内存限制。比如:内存子系统可以为进程组设定一个内存使用上限,一旦进程组使用的内存达到限额再申请内存,就会出发Out of Memory警告。
- 优先级(Prioritization):通过优先级让一些组优先得到更多的CPU等资源。
- 资源审计(Accounting):cgroups可以统计系统的资源使用量,如CPU使用时长、内存用量等,这个功能非常适用于计费。
- 隔离(isolation):为组隔离命名空间,这样一个组不会看到另一个组的进程、网络连接和文件系统。
- 控制(Control):挂起、恢复和重启动等操作。
UnionFS
联合文件系统(UnionFS)是一种轻量级的高性能分层文件系统,它支持将文件系统中的修改信息作为一次提交,并层层叠加,同时可以将不同目录挂载到同一个虚拟文件系统下,应用看到的是挂载的最终结果。
Docker镜像自身就是由多个文件层组成,每一层有唯一的编号(层ID)。
可以通过docker history查看一个镜像由哪些层组成。
rootfs是什么?
rootfs 是 docker 容器在启动时内部进程可见的文件系统,即 docker 容器的根目录。rootfs 通常包含一个操作系统运行所需的文件系统,例如可能包含典型的类 Unix 操作系统中的目录系统,如 /dev、/proc、/bin、/etc、/lib、/usr、/tmp 及运行 docker 容器所需的配置文件、工具等。
overlay2是什么?
Overlay2是文件存储驱动。Overlay2的挂载方式比Overlay的要简单许多,它基于内核overlayfs的Multiple lower layers特性实现,不在需要硬链接,直接将镜像层的各个目录设置为overlayfs的各个lower layer即可(Overlayfs最多支持500层lower dir)。
Overlay2将lowerdir、upperdir、workdir联合挂载,形成最终的merged挂载点,其中lowerdir是镜像只读层,upperdir是容器可读可写层,workdir是文件系统工作的基础目录,挂载后目录会被清空。
copy-up特性
用户在写文件时,如果文件来自upper层,那直接写入即可。但是如果文件来自lower层,由于lower层文件无法修改,因此需要先复制到upper层,然后再往其中写入内容,这就是overlayfs的写时复制(copy-up)特性。
Overlayfs的lower layer文件写时复制机制让某一个用户在修改来自lower层的文件不会影响到其他用户(容器),但是这个文件的复制动作会显得比较慢,后面我们会看到为了保证文件系统的一致性,这个copy-up实现包含了很多步骤,其中最为耗时的就是文件数据块的复制和fsync同步。用户在修改文件时,如果文件较小那可能不一定能够感受出来,但是当文件比较大或一次对大量的小文件进行修改,那耗时将非常可观。虽然自Linux-4.11起内核引入了“concurrent copy up”特性来提高copy-up的并行性,但是对于大文件也还是没有明显的效果。不过幸运的是,如果底层的文件系统支持reflink这样的延时拷贝技术(例如xfs)那就不存在这个问题了。
docker文件的结构是什么样的?
有新版本imagee更新,推荐的操作流程是什么?
docker stop container
docker rm container
docker rmi image
docker pull image
docker run ...