跟着Docker学分层复用思想

作者:三十

来源:恒生LIGHT云社区

Docker是如何组织存储的

dokcer在组织存储内容时很巧妙的应用了分层复用的思想。所以我们可以以此为案例学习一下该思想。

1.镜像分层

一个Docker镜像在构建的过程中分了很多层,每一层都是只读的。结合下面例子进行说明:

# syntax=docker/dockerfile:1
FROM ubuntu:18.04
LABEL org.opencontainers.image.authors="org@example.com"
COPY . /app
RUN make /app
RUN rm -r $HOME/.cache
CMD python /app/app.py

这个Dockerfile中会有4条指令改变了文件系统并创建了新层。

  • FROM指令从ubuntu:18.04的镜像中创建了基础层。
  • LABEL指令仅仅修改了镜像的元数据,不会创建新层。
  • COPY指令将执行本次构建的当前目录中的内容添加到镜像当中,会创建一个新层记录改变。
  • 第一个RUN指令,构建了程序并将结果输出到镜像中,会创建一个新层记录改变。
  • 第二个RUN指令,删除了缓存目录,会创建一个新层记录改变。
  • CMD指令定义了容器中运行的指令,只是修改了镜像的元数据,并不会创建新层。

这里每层都只记录与其上一层的不同。当我们创建一个容器的时候,这是就会创建一层可写层,也叫容器层。对于正在运行中的容器的内容的变化都会记录在该层中。下图描述了该关系:

跟着Docker学分层复用思想_第1张图片

2.容器分层

容器和镜像的不同主要是最顶层的可写层的不同,所有对于容器的写操作都会记录在这层中,如果容器被删除,那么这个可写层也会被删除,但是镜像会被保留。

注意:如果想要多个容器共享相同的数据,可以通过Docker Volumes实现。

每个容器都有其自己的可写层,所有的变换都会被存放在其中,所以多个容器可共享同一个镜像。下图描述了该关系:

跟着Docker学分层复用思想_第2张图片

注意 :此处还有个细节,多个镜像可能共用相同的层,比如两个镜像中有相同的层,那么在构建或是拉取的时候发现本地以存在,则不会再次构建或拉取。所以计算镜像大小的时候,不能仅通过 docker images命令显示出的大小来汇总求和,该值有可能大于实际值。

3.容器在磁盘占用的空间

可以通过 docker ps -s命令,来看正在运行中的容器占用的空间(部分值)。两个列的不同代表的内容:

  • size: 容器的可写层占用的磁盘大小
  • virtual size: 包含了容器可写层和只读镜像的大小。

容器占用磁盘空间的其它途径:

  • 容器产生的日志文件。
  • 使用Volume和bind mounts挂载的内容。
  • 容器的配置文件
  • 内存中的内容(如果开启了swapping)
  • Checkpoints(如果使用了该功能)

4.Copy-on-Write(CoW)策略

Docker中的存储驱动都是采用该策略。

CoW策略能够最大效率的共享和复制文件。如果一个文件在镜像的更低层存在,那么其上层(包括可写层)需要读取该内容则可以直接使用该文件。当需要对其进行修改时,会复制该文件到该层并进行修改。这最大限度的减少了IO和每个后续层的大小。

4.1共享使镜像更小

当我们使用 docker pull拉取镜像或是使用一个本地没有的镜像创建容器的时候,该镜像会被分层的存储到本地Dockers存储区域中。在linux中通常是 /var/lib/docker

我们可以去 /var/lib/docker/目录下看我们已拉取各层镜像。比如使用 overlay2存储驱动。

跟着Docker学分层复用思想_第3张图片

这么多层,我们可以通过 docker image inspect来查看某个镜像包含哪些层

docker image inspect --format "{{json .RootFS.Layers}}" redis

docker image inspect --format "{{json .RootFS.Layers}}" mysql:5.7

 title=

通过上面查看我们可以看到redis和mysql5.7运用了同一层,这样共享相同层就大大节省了存储镜像的空间,同时也提升了拉取镜像的速度 。

我们可以通过 docker image history命令来查看镜像分层情况,以redis为例

docker history redis

跟着Docker学分层复用思想_第4张图片

注意 :

  • 有些步骤的大小为0,是因为他们只改变了元数据,并不会产生新层,也不会占用额外的空间(除元数据本身)。所以上述redis镜像中包含了5层。
  • 步骤,这些步骤可能是以下情况中的一种

    • 在另一个系统上构建的
    • 从Docker Hub中提取的
    • 使用BuildKit作为构建器构建的。

4.2复制让容器更有效率

当我们启动一个容器的时候,会添加一个可写层在镜像之上,用于存储所有的变化。当对已有文件进行修改的时候采用CoW策略。首先会到各层寻找到该文件,然后复制该文件到可写层,然后进行修改并存储。

这么做能够让我们最大限度地减少I/O操作。

但是,很明显的是当一个容器中的应用需要进行频繁的写操作,那么会造成可写层越来越庞大,此时我们可以通过Volume来帮助我们分担压力。

容器的元数据和日志是单独存放的,一般是存放在 /var/lib/docker/containers中,我们可以使用 du -sh /var/lib/docker/containers/*来查看各个容器占用多少。(容器ID其实就是文件夹名称的前12位)。


想向技术大佬们多多取经?开发中遇到的问题何处探讨?如何获取金融科技海量资源?

恒生LIGHT云社区,由恒生电子搭建的金融科技专业社区平台,分享实用技术干货、资源数据、金融科技行业趋势,拥抱所有金融开发者。

扫描下方小程序二维码,加入我们!

你可能感兴趣的