Docker 镜像要采用这种分层结构最大的一个好处就是 - 共享资源。
比如:有多个镜像都从相同的 base 镜像构建而来,那么宿主机只需在磁盘上保存一份base镜像,同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。
目录
一、关于base镜像
二、镜像、容器和存储驱动的关系
三、最为典型的镜像的分层技术——aufs
四、总结
base 镜像有两层含义:
search
得到,但pull
不了的特殊镜像)所以,能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像,比如 Ubuntu, Debian, CentOS 等。
base 镜像提供的是最小安装的 Linux 发行版。
我们大部分镜像都将是基于base镜像构建的。所以,通常使用的是官方发布的base镜像。可以在docker hub里找到。比如centos: https://hub.docker.com/_/centos
点击版本可以看到github里的Dockerfile
FROM scratch
ADD centos-7-docker.tar.xz /
LABEL org.label-schema.schema-version="1.0" \
org.label-schema.name="CentOS Base Image" \
org.label-schema.vendor="CentOS" \
org.label-schema.license="GPLv2" \
org.label-schema.build-date="20181205"
CMD ["/bin/bash"]
ADD命令将本地的centos7的tar包添加到镜像,并解压到根目录/下。生成/dev,/proc/,/bin等。
我们可以自己构建docker base镜像,也可以直接使用已有的base镜像。比如centos。我们可以直接从docker hub上拉取。
拉取
docker pull centos
查看
# docker images centos
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 1e1148e4cc2c 2 months ago 202MB
可以看到最新的centos镜像只有200mb,是不是觉得太小了?这是因为docker镜像在运行的时候直接使用docker宿主机器的kernel。
Linux操作系统由内核空间和用户空间组成。
内核空间是kernel,用户空间是rootfs, 不同Linux发行版的区别主要是rootfs.比如 Ubuntu 14.04 使用 upstart 管理服务,apt 管理软件包;而 CentOS 7 使用 systemd 和 yum。这些都是用户空间上的区别,Linux kernel 差别不大。
所以 Docker 可以同时支持多种 Linux 镜像,模拟出多种操作系统环境。
需要注意的是:
前面也已经讲过说,镜像是程序和文件的集合,容器是镜像的运行实例。Docker为了节约存储空间共享数据会对镜像和容器进行分层,不同镜像可以共享相同数据,并且在镜像上为容器分配一个RW层来加快容器的启动顺序
1. 镜像层和容器层
每个镜像都由多个镜像层组成,从下往上以栈的方式组合在一起形成容器的根文件系统,Docker的存储驱动用于管理这些镜像层,对外提供单一的文件系统
当容器启动时,Docker就会创建thin类型的可读写容器层,使用预分配存储空间,也就是开始时并不分配存储空间,当需要新建文件或修改文件时,才从存储池中分配一部分存储空间
每个容器运行时都有自己的容器层,保存容器运行相关数据(所有文件变化数据),因为镜像层是只读的,所以多个容器可以共享同一个镜像
删除容器时,Docker Daemon会删除容器层,保留镜像层
2. 镜像存储方式
为了区别镜像层,Docker为每个镜像层都计算了UUID,在Docker 1.10之前根据镜像层中的数据产生一个随机码作为UUID;Docker 1.10版本中则是根据镜像层中的数据使用加密哈希算法生成UUID,这种方式避免了UUID冲突并且也保证了在pull、push、load等操作中的镜像数据完整性,而容器层仍然是采用随机数方式生成UUID
在Docker 1.10版本前,镜像之间不能共享镜像层,而在Docker 1.10版本中,只要镜像层的数据相同就可以在不同镜像之间共享。当Docker升级到1.10版本后,宿主机上可能存在以前下载的镜像仍使用旧的存储方式,当Docker 1.10版本的Docker Daemon第一次启动时会自动迁移镜像,使用加密哈希算法重新计算每个镜像层的UUID,当重新计算UUID会花费很长时间并且迁移过程中Docker Daemon不能响应其他Docker命令,在实际中并不推荐
3. 写时复制策略(Copy On Write)
Docker中的存储驱动用于管理镜像层和容器层,不同的存储驱动采用不同的算法和管理方式,在管理中使用的两大技术是栈层式管理和写时复制
写时复制采用了共享和复制技术,针对相同的数据系统只保留一份,所有操作都访问这一份数据。当有操作需要修改或添加数据时,操作系统会把这部分数据复制到新的地方再进行修改或添加,而其他操作仍然访问原数据区数据,通过这项技术节约了镜像的存储空间,加快了系统启动时间
所有镜像层和容器层都保存在宿主机的文件系统/var/lib/docker/中,由存储驱动进行管理
在拉取ubutnu的时候可以看到有如下输出,表明该版本Ubuntu由五个镜像层组成,下载镜像时实际上就是下载了这五个镜像层,这些镜像层都在宿主机的文件系统中,每层都有独立的目录,在Docker 1.10之前的版本中,目录的名字和镜像的UUID相同,而Docker 1.10后则采用了新的存储方式
可以看到目录名和下载镜像的UUID并不相同
尽管存储方式不尽相同,但在所有版本的Docker中都可以共享镜像层。在下载镜像时,Docker Daemon会检查景象中的镜像层与宿主机文件系统中的镜像层进行对比,如果存在则不下载,只下载不存在的镜像层
通过Dockfile创建了一个Changed-ubuntu镜像,通过docker history可以查看镜像的镜像层及镜像大小,可以看到新建的镜像层占用空间为0B,也就是说changed-ubuntu镜像只会占用额外的一丢丢空间而不需要为其他的五个镜像层分配存储空间
由此可以看出,Docker通过共享技术,非常节约存储空间
Aufs是Another Union File System的缩写,支持将多个目录挂载到同一个虚拟目录下。
已构建的镜像会设置成只读模式,read-write写操作是在read-only上的一种增量操作,固不影响read-only层。
这个研究有一个好处,比如我们现在可以看到手机里面的APP,在命令里面都会用APP字段下回来,在下回来之前它就是一个静态的,我们没有往里面写东西,但是我们启动起来以后,我们就可以往里面写东西,进行各种各样的操作。但是如果我们把它关掉了以后,或者删除了以后,它的这个镜像是存在远端的,所以在这个镜像里面是不会去修改的。并且这样也会有一个非常好的地方,这个场景非常适合我们去实现测试环境,因为我们的测试环境经常会有一个操作就是灌数据,我们可以提前把这个镜像数据打包到测试里面,那么这个镜像软件里面包含了,最上面是nginx,比如它里面会有一些数据,我们可以在往上面打一层数据,打完之后把它起成一个容器就可以去测试,测试完之后这个容器里面会生成各种各样的数据,也就是脏数据,这样的话,我们就可以把这个容器删掉,删掉以后我们镜像里面的容器是不会受影响的。如果说它想再创建一套,我们可以把这个镜像再启一个容器,就可以是一个一模一样的,并且是一个干净的环境。
(1)base 镜像不依赖其他镜像,从 scratch 构建。(scratch 是一个search
得到,但pull
不了的特殊镜像)其他镜像可以之为基础进行扩展。所以,能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像,比如 Ubuntu, Debian, CentOS
(2)base镜像只是用户空间和发行版一致。kernel使用的是docker宿主机器的kernel
(3)在下载镜像时,Docker Daemon会检查景象中的镜像层与宿主机文件系统中的镜像层进行对比,如果存在则不下载,只下载不存在的镜像层