我们在前面的 《Docker 入门教程:快速开始 》文章了解到镜像和容器的概念。本文将了解一下 Docker 的镜像分层(Layer)的概念,在 Docker 的官方文档对 Layer 的定义如下(参见这里):
In an image, a layer is modification to the image, represented by an instruction in the Dockerfile. Layers are applied in sequence to the base image to create the final image. When an image is updated or rebuilt, only layers that change need to be updated, and unchanged layers are cached locally. This is part of why Docker images are so fast and lightweight. The sizes of each layer add up to equal the size of the final image.
中文含义就是:每个镜像都是通过 DockerFile 文本文件定义的,Dockerfile 中的每条指令最终都会成为镜像中的 Layer。Layer 是按顺序构成的,最底层的 Layer 是基础镜像(base image),最上层是最终镜像(final image)。当一个镜像被更新或重新构建时,只有更新的层需要修改,其他没有更新的层可以直接复用本地缓存。这就是 Docker 镜像如此快速和轻量级的部分原因,每一层的大小加起来等于最终镜像的大小。
为了更加深入地理解 Docker 镜像分层机制,我们下面通过几个例子来说明这个。
基础镜像
我们从 Docker 仓库下载 ubuntu 20.04 镜像到本地:
[root@iteblog.com ~]$ docker pull ubuntu:20.04 20.04: Pulling from library/ubuntu 8f6b7df711c8: Pull complete 0703c52b8763: Pull complete 07304348ce1b: Pull complete 4795dceb8869: Pull complete Digest: sha256:7922db6447e9d1470e3bf821e8ff228d70c3593e822e980c58bf9185821ac645 Status: Downloaded newer image for ubuntu:20.04 docker.io/library/ubuntu:20.04
我们来看看刚刚下载下来的 ubuntu 20.04 镜像的大小:
[root@iteblog.com ~]$ docker images ubuntu:20.04 REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu 20.04 2a4d239ad3cc 2 weeks ago 73.4MB
大家可能注意到上面命令输出的最后一列, 它显示 ubuntu 20.04 这个镜像才 73.4MB !但是大家应该都知道,安装一个 ubuntu 系统怎么也不可能就几十兆,那么 Docker 是怎么做到的呢?
这是因为典型的 Linux 运行需要两个 FS: bootfs 和 rootfs,Linux 刚启动时会加载 bootfs 文件系统,之后 bootfs 会被卸载掉。对于不同的 Linux 发行版, bootfs 基本是一致的, 但 rootfs 会有差别,其包含我们熟悉的 /dev, /proc, /bin 等目录。对于 ubuntu 镜像来说,其底层直接使用 Host 的 kernel,自己只需要提供 rootfs 就行了,具体如下:
为了确定 ubuntu 镜像使用的是 Host 的内核,我们可以使用下面命令核实:
[root@iteblog.com ~]$ uname -a Linux VM_32_198_centos 3.10.0-693.el7.x86_64 #1 SMP Tue Aug 22 21:09:27 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux [root@iteblog.com ~]$ [root@iteblog.com ~]$ [root@iteblog.com ~]$ docker run -it ubuntu:20.04 bash root@d4cdc3f4fff0:/# uname -a Linux d4cdc3f4fff0 3.10.0-693.el7.x86_64 #1 SMP Tue Aug 22 21:09:27 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux root@d4cdc3f4fff0:/#
我们使用 docker run -it ubuntu:20.04 bash
命令进入到 ubuntu 镜像的 bash,从上面的结果可以看出, host 的 Linux 内核版本和 ubuntu 镜像的 Linux 内核版本是一致的。
如果我们在同一个 host 上下载不同 Linux 发行版镜像,其都是可以公用 host 的 bootfs,具体如下:
镜像分层
理解上面的设计之后,我们现在来解释最上面关于 Layer 介绍中的 In an image, a layer is modification to the image, represented by an instruction in the Dockerfile 这句话。假设我们的 Dockerfile 定义如下:
FROM debian RUN apt-get update && apt-get -y -f install emacs RUN apt-get update && apt-get -y -f install apache2
上面一共有三条指令,如果编译这个 Dockerfile,其会生成三个镜像:
[root@iteblog.com ~]$ docker build -t iteblog-docker ./ Sending build context to Docker daemon 2.048kB Step 1/3 : FROM debian ---> a8797652cfd9 Step 2/3 : RUN apt-get update && apt-get -y -f install emacs ---> Using cache ---> 4b2cc711d0f1 Step 3/3 : RUN apt-get update && apt-get -y -f install apache2 ---> Using cache ---> 48ec647c89a1 Successfully built 48ec647c89a1 Successfully tagged iteblog-docker:latest
如果用图片表示的话,这个过程如下:
镜像分层的好处就是共享资源,如果是多个镜像都由同一个基础镜像构建而来,那么主机上只需要保证有一份基础镜像即可。同时内存中也只需加载一份基础镜像,就可以为所有容器服务了。
可写的容器层
上面说到如果多个镜像共用一个基础镜像,内存中也只需加载一份基础镜像,就可以为所有容器服务了。那么问题来了,如果我们需要修改基础镜像里面的东西咋办呢?Docker 很好的处理了这个问题,当容器启动时,一个新的可写层被加载到镜像的顶部,这一层通常被称作容器层(container layer),容器层之下的都叫镜像层(image layer),所有的修改(比如删除文件、添加文件)都是在容器层进行的,如下图所示:
容器和镜像的最大不同之处是容器在镜像之上拥有一个可写层(writable layer),也就是上面的 container layer,所有的添加、修改生成的数据都是存放在这一层的。当容器被删除时,可写成也被删除,但是底层的镜像层是不动的。
因为不同容器都有自己独有的容器层,所有的修改只会存在自己的容器层,也就是说不通容器之间的修改都互不影响,这也就使得不同容器可以共享一个镜像层,具体如下:
下两篇文章我将介绍 Docker 的基础技术 Union File System 以及 Docker 如何利用 Union File System 来存储和管理镜像和容器的。
本博客文章除特别声明,全部都是原创!原创文章版权归过往记忆大数据(过往记忆)所有,未经许可不得转载。
本文链接: 【Docker 入门教程:镜像分层】(https://www.iteblog.com/archives/9778.html)