互联网技术 / 互联网资讯 · 2024年1月5日

Docker存储写入时复制策略核心技术教程

写入时复制(Copy-on-Write,简称 CoW)是一种用于共享与复制文件的数据管理机制,目标是在保证可修改性的同时,尽可能提升存储与 I/O 效率。在 Docker 中,如果某个文件或目录已经存在于镜像的底层,新的上层在只读访问时会直接复用该文件,而不会额外复制。只有当上层第一次需要修改该文件时,系统才会先将文件复制到当前层,再执行改动。借助这种方式,Docker 能有效减少重复数据、降低后续层的体积,并控制磁盘读写开销。

这种共享机制尤其适合构建体积更小、复用率更高的镜像。

当你执行 docker pull 从仓库拉取镜像,或基于本地尚不存在的镜像创建容器时,镜像的每一层都会被单独下载,并保存在 Docker 的本地存储区域中。在 Linux 主机上,这个位置通常是 /var/lib/docker/。下面是一个拉取镜像时的示例输出:

$ docker pull ubuntu:18.04

18.04: Pulling from library/ubuntu

f476d66f5408: Pull complete

8882c27f669e: Pull complete

d9af21273955: Pull complete

f5029279ec12: Pull complete

Digest: sha256:ab6cb8de3ad7bb33e2534677f865008535427390b117d7939193f8d1a6613e34

Status: Downloaded newer image for ubuntu:18.04

这些层会分别存放在 Docker 主机本地存储中的独立目录里。你可以查看 /var/lib/docker/ 来了解实际的层存储情况。以下示例使用的是 overlay2 存储驱动:

$ ls /var/lib/docker/overlay2

16802227a96c24dcbeab5b37821e2b67a9f921749cd9a2e386d5a6d5bc6fc6d3

377d73dbb466e0bc7c9ee23166771b35ebdbe02ef17753d79fd3571d4ce659d7

3f02d96212b03e3383160d31d7c6aeca750d2d8a1879965b89fe8146594c453d

ec1ec45792908e90484f7e629330666e7eee599f08729c93890a7205a6ba35f5

l

需要注意的是,这些目录名并不直接等同于镜像层 ID。

现在假设你有两个不同的 Dockerfile。先用第一个 Dockerfile 构建一个名为 acme/my-base-image:1.0 的镜像:

FROM ubuntu:18.04

COPY . /app

第二个镜像则基于 acme/my-base-image:1.0 继续构建,并额外增加一层:

FROM acme/my-base-image:1.0

CMD /app/hello.sh

第二个镜像会包含第一个镜像的全部层,同时新增由 CMD 指令产生的一层,以及容器运行时使用的可读写层。由于 Docker 已经拥有基础镜像中的层,因此再次构建或拉取时,不需要重复下载或保存相同内容。也就是说,两个镜像会共享它们共同依赖的底层数据。

如果你分别根据这两个 Dockerfile 构建镜像,可以通过 docker image lsdocker history 来验证它们是否共享了相同的层。通常你会看到,公共层的哈希值是一致的。

可以先创建一个新的目录 cow-test/,然后进入该目录。

cow-test/ 中,新建一个名为 hello.sh 的文件,内容如下:

#!/bin/sh

echo "Hello world"

保存后,为这个脚本添加可执行权限:

chmod +x hello.sh

接着,把前面第一个 Dockerfile 的内容保存到一个名为 Dockerfile.base 的文件中。

再把第二个 Dockerfile 的内容保存到名为 Dockerfile 的文件里。

cow-test/ 目录下,先构建第一个镜像。注意命令末尾要带上 .,它表示当前目录是构建上下文,Docker 会从这里查找需要复制到镜像中的文件。

$ docker build -t acme/my-base-image:1.0 -f Dockerfile.base .

然后构建第二个镜像:

$ docker build -t acme/my-final-image:1.0 -f Dockerfile .

构建完成后,可以查看镜像大小:

$ docker image ls

如果想进一步查看镜像由哪些层组成,可以执行:

$ docker history bd09118bcef6

你会发现,除了第二个镜像最顶层新增的一层外,其余层都与第一个镜像一致。这说明两者共享了相同的基础层,而这些共享层在 /var/lib/docker/ 中只会保存一次。实际上,这个新增顶层往往几乎不占空间,因为它没有修改文件内容,只是定义了容器启动时要执行的命令。