Docker用户指南(中译版)——关于 镜像、容器和storage drivers

来源:互联网 时间:2017-06-01


关于 images、containers、storage drivers

英语原文出自官方网址:


(https://docs.docker.com/engine/userguide/storagedriver/imagesandcontainers/#data-volumes-and-the-storage-driver)


想有效使用存储drivers,你必须了解Docker如何建立和存储镜像。然后,您需要了解这些docker镜像是如何被容器使用的。最后,对于镜像和容器操作的技术。您需要一个简短的介绍。 理解Docker如何管理镜像和容器中的数据,将帮助你了解设计自有容器、docker化应用程序的最佳方式,并据此避免性能上的问题。


镜像和层(Images and layers):

Docker镜像建立在一系列的层之上。每一层表现为镜像dockerfile中的一个指令。除了最后一个,每一层都是只读的。思考一下这个dockerfile:


FROM ubuntu:15.10
COPY . /app
RUN make /app
CMD python /app/app.py

这个dockerfile包含四条命令,每一条命令创建一个层。 “FROM”-声明开始从创建一个层从Ubuntu:15.10镜像。 “COPY”-命令从您的DOCKER客户端的当前目录导入一些文件。 "RUN"-命令使用make命令构建应用程序。 最后,最后一层指定要在容器中运行的命令。


每层只有一组不同的层之前。这些层叠在彼此的顶部。创建新容器时,在底层上添加新的可写层。这个层通常被称为“容器层”。对正在运行的容器所做的所有更改,如写入新文件、修改现有文件和删除文件都将写入该薄可写容器层。下图显示了基于Ubuntu 15.04图像的容器。


下图显示了一个包含Ubuntu 15.04图像的容器,该图像包含了4个层叠的图像层,以及顶部的可写容器层。



存储驱动程序处理有关这些层交互方式的详细信息。不同的存储驱动程序,在不同的情况下有优缺点。


Content addressable storage(可寻址存储)

如果你已经运行Docker 1.10或更高,你可以跳过这一节直接阅读[容器和层]。Docker 1.10引入了一个新的内容寻址存储模型。这是一个全新的方式来解决磁盘上的图像和层数据。此前,图像和层的数据参考和使用随机生成的UUID储存。在新的模型中,这将被一个[安全内容散列]替换。


新的模式提高了安全性,提供了一个内置的方式,以避免ID冲突,并保证pull, push, load, save后的数据完整性。通过允许多个镜像任意共享它们的层来也使得共享性增强,即使他们没有来自相同的构建。


下图是前一个示意图的更新版本,突出了Docker1.10之后的变化。



所有的镜像层的ID是加密的哈希散列,而容器的ID仍然是一个随机生成的UUID。


新的模式需要对从现有Docker1.9版本和1.9以下的镜像进行升级,包括镜像和层文件系统结构也发生了一些变化。如果你已经运行Docker 1.10及以上版本,你可以跳过这一节。


在Docker1.9或更早版本创建的镜像需要升级之前,他们可以用新的模型。这种升级涉及到计算新的安全校验和,当新版Docker守护进程第一次启动的时候会自动执行。升级完成后,所有镜像和TAG标签将拥有全新的安全ID。


虽然迁移是自动和透明的,但它是计算密集型的。这可能需要一些时间来完成,而在这段时间,Docker无法响应其他请求。如果这会是一个问题,你可以在升级Docker前,使用迁移工具迁移现有的镜像到新的格式。这避免了停机时间,并允许您将已迁移的镜像分发到已经升级的系统。更多信息,见https://github.com/docker/v1.10-migrator/releases。


在迁移过程中,你需要让你的Docker主机的数据目录迁移容器。如果你使用的是默认的Docker数据路径,使用命令如下:


$ sudo docker run --rm -v /var/lib/docker:/var/lib/docker docker/v1.10-migrator

如果你使用devicemapper驱动程序,你需要包括--privileged选项使容器可以访问您的存储设备。


容器和层(Containerlayers)

容器和镜像的主要区别是顶层可写层。所有写入新添加或修改现有数据的容器都存储在此可写层中。当容器被删除时,可写层也被删除。基础镜像保持不变。


由于每个容器都有自己的可写容器层,所有的更改都存储在这个容器层中,多个容器可以共享对同一基础镜像的访问,但也有自己的数据状态。下图显示了多个容器共用相同的Ubuntu 15.04镜像。




注意:如果你需要多个镜像可以共享访问完全相同的数据,应该把这些数据保存到一个Docker Volume中,并装载(Mount)到各个容器。



Docker使用存储驱动程序来管理镜像层内容、可写容器层。每个存储驱动程序处理的实现方式不同,但所有的驱动都使用可堆叠的镜像层和即写即拷copy-on-write (CoW)策略。


CoW即写即拷(copy-on-write)策略

写副本是一种共享和复制文件的策略,以达到最大的效率。如果文件或目录存在于Docker镜像的较低层中,而另一层(包括可写层)需要对其进行读访问,则只使用现有文件。当另一个层在第一次需要修改文件(在生成Docker镜像或运行Docker容器时),文件被复制到该层并修改。这样可最大限度地减少I / O和每个后续层的大小。这些优点在下面解释得更为深入。


共享机制使Docker镜像较小

当你使用docker pull从Docker仓库下载一个镜像,或当你创建一个本地还不存在的镜像容器时,每个镜像层是分别下载的,并存储在Docker的本地存储区,在Linux主机中通常是/var/lib/docker/。看一下这个镜像层下载的例子:


$ docker pull ubuntu:15.04
15.04: Pulling from library/ubuntu
1ba8ac955b97: Pull complete
f157c4e5ede7: Pull complete
0b7e98f84c4c: Pull complete
a3ed95caeb02: Pull complete
Digest: sha256:5e279a9df07990286cce22e1b0f5b0490629ca6d187698746ae5e28e604a640e
Status: Downloaded newer image for ubuntu:15.04

每一个镜像层被保存在Docker宿主机本地存储区的自有文件夹内。可以通过查看/var/lib/docker//layers/来观察一下文件系统中的Docker镜像层,这个示例采用了aufs这是默认的存储驱动。


$ ls /var/lib/docker/aufs/layers
1d6674ff835b10f76e354806e16b950f91a191d3b471236609ab13a930275e24
5dbb0cbe0148cf447b9464a358c1587be586058d9a4c9ce079320265e2bb94e7
bef7199f2ed8e86fa4ada1309cfad3089e0542fec8894690529e4c04a7ca2d73
ebf814eccfe98f2704660ca1d844e4348db3b5ccc637eb905d4818fbfb00a06a

文件夹名称与镜像层的ID并不是一一对应的(Docker 1.10之后修复了?).


现在假设你有不同的Dockerfiles. 你用其中第1个构建docker镜像:acme/my-base-image:1.0.


FROM ubuntu:16.10
COPY . /app

第2个基于acme/my-base-image:1.0, 但还有一些另外的镜像层:


FROM acme/my-base-image:1.0
CMD /app/hello.sh

第2个镜像包含了第1个镜像里所有的层,外加一个带有RUN指令的新层,一个可读写的镜像层。Docker已经有了来自第一个镜像的所有层, 所以不再需要下载(pull)了。这两个Docker镜像会共享两者共有的任何镜像层。


如果用两个Dockerfile构建镜像,你可以使用docker images和docker history命令来验证共享镜像层的ID是否相同。

新建cow-test/文件夹,并切换到该文件夹内。


在cow-test/文件夹内新建一个文件,内容为:

#!/bin/sh
echo "Hello world"

保存,并使其可执行:

chmod +x hello.sh

将上文第1个Dockerfile的内容复制到一个名为Dockerfile.base的新文件中。


将上文第2个Dockerfile的内容复制到一个名为Dockerfile的新文件中。


转到cow-test/目录下,构建第一个Docker镜像:

$ docker build -t acme/my-base-image:1.0 -f Dockerfile.base .
Sending build context to Docker daemon4.096kB
Step 1/2 : FROM ubuntu:16.10
---> 31005225a745
Step 2/2 : COPY . /app
---> Using cache
---> bd09118bcef6
Successfully built bd09118bcef6
Successfully tagged acme/my-base-image:1.0

构建第2个:

$ docker build -t acme/my-final-image:1.0 -f Dockerfile .
Sending build context to Docker daemon4.096kB
Step 1/2 : FROM acme/my-base-image:1.0
---> bd09118bcef6
Step 2/2 : CMD /app/hello.sh
---> Running in a07b694759ba
---> dbf995fc07ff
Removing intermediate container a07b694759ba
Successfully built dbf995fc07ff
Successfully tagged acme/my-final-image:1.0

检查Docker镜像的文件大小:

$ docker images
REPOSITORYTAG IMAGE ID CREATEDSIZE
acme/my-final-image1.0 dbf995fc07ff 58 seconds ago103MB
acme/my-base-image 1.0 bd09118bcef6 3 minutes ago103MB

查看每个Docker镜像的层:

$ docker history bd09118bcef6
IMAGE CREATEDCREATED BY SIZECOMMENT
bd09118bcef6 4 minutes ago/bin/sh -c #(nop) COPY dir:35a7eb158c1504e... 100B
31005225a745 3 months ago /bin/sh -c #(nop)CMD ["/bin/bash"] 0B
3 months ago /bin/sh -c mkdir -p /run/systemd && echo '... 7B
3 months ago /bin/sh -c sed -i 's/^#/s*/(deb.*universe/... 2.78kB
3 months ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0B
3 months ago /bin/sh -c set -xe && echo '#!/bin/sh' >... 745B
3 months ago /bin/sh -c #(nop) ADD file:eef57983bd66e3a... 103MB
$ docker history dbf995fc07ff
IMAGE CREATEDCREATED BY SIZECOMMENT
dbf995fc07ff 3 minutes ago/bin/sh -c #(nop)CMD ["/bin/sh" "-c" "/a... 0B
bd09118bcef6 5 minutes ago/bin/sh -c #(nop) COPY dir:35a7eb158c1504e... 100B
31005225a745 3 months ago /bin/sh -c #(nop)CMD ["/bin/bash"] 0B
3 months ago /bin/sh -c mkdir -p /run/systemd && echo '... 7B
3 months ago /bin/sh -c sed -i 's/^#/s*/(deb.*universe/... 2.78kB
3 months ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0B
3 months ago /bin/sh -c set -xe && echo '#!/bin/sh' >... 745B
3 months ago /bin/sh -c #(nop) ADD file:eef57983bd66e3a... 103MB

注意所有的镜像都是一致的,除了第2个镜像的顶层。 所有的层在两个镜像间共享, 并只在/var/lib/docker/中保存一次。 这个新的层其实并没有占用多少空间,因为它并没有改变任何文件,只是执行了一条命令。



Note: Thelines in thedocker historyoutput indicate that those layers were built on another system and are not available locally. This can be ignored.


Copying 使Docker容器更高效

启动容器时,在其他层的顶部添加一个薄可写容器层。存储在文件系统中的任何更改都存储在这里。容器不更改的任何文件都不会复制到该可写层。这意味着可写层越小越好。


当容器中的现有文件被修改时,存储驱动程序在写操作上执行一个副本。具体步骤依赖于特定的存储驱动程序。


对于默认的aufs驱动和overlay、overlay2格式,“即拷即写”copy-on-write 操作依次为以下四步:

通过图像层搜索需要更新的文件。这个过程会从最新的层开始,一直到最底层,每次一个层。当找到结果时,它们会被添加到高速缓存中以加速其后的操作。


执行copy_up命令,在找到的第一个副本, 将文件复制到容器的可写层。


任何修改只针对该文件的副本,容器无法看到存在于低层文件的只读副本。

Btrfs, ZFS, 和其他驱动对于处理 copy-on-write 是不同的。 可以在详细描述中读到更多关于这些驱动方法。


写大量数据的容器将比其它容器耗费更多的空间,这是因为大多数写操作会在容器顶部的薄可写层中,占用更多的空间。



Note: 对于大量写操作的应用程序,不应该将数据保存在容器中。而是使用Docker volumes,它独立于运行中的容器,且被设计为高效的I/O。另外,docker卷可以在容器间共享,不会增加你的容器读写层的空间大小。



Acopy_upoperation can incur a noticeable performance overhead. This overhead is different depending on which storage driver is in use. Large files, lots of layers, and deep directory trees can make the impact more noticeable. This is mitigated by the fact that eachcopy_upoperation only occurs the first time a given file is modified.


To verify the way that copy-on-write works, the following procedures spins up 5 containers based on theacme/my-final-image:1.0image we built earlier and examines how much room they take up.



Note: This procedure won’t work on Docker for Mac or Docker for Windows.


在Docker主机中,执行docker run命令。执行结果(底部的字符串)返回每个容器的ID:

$ docker run -dit --name my_container_1 acme/my-final-image:1.0 bash /
&& docker run -dit --name my_container_2 acme/my-final-image:1.0 bash /
&& docker run -dit --name my_container_3 acme/my-final-image:1.0 bash /
&& docker run -dit --name my_container_4 acme/my-final-image:1.0 bash /
&& docker run -dit --name my_container_5 acme/my-final-image:1.0 bash
c36785c423ec7e0422b2af7364a7ba4da6146cbba7981a0951fcc3fa0430c409
dcad7101795e4206e637d9358a818e5c32e13b349e62b00bf05cd5a4343ea513
1e7264576d78a3134fbaf7829bc24b1d96017cf2bc046b7cd8b08b5775c33d0c
38fa94212a419a082e6a6b87a8e2ec4a44dd327d7069b85892a707e3fc818544
1a174fc216cccf18ec7d4fe14e008e30130b11ede0f0f94a87982e310cf2e765

执行docker ps命令,验证5个容器是否正在运行:

CONTAINER ID IMAGE COMMANDCREATEDSTATUSPORTS NAMES
1a174fc216cc acme/my-final-image:1.0 "bash" About a minute ago Up About a minutemy_container_5
38fa94212a41 acme/my-final-image:1.0 "bash" About a minute ago Up About a minutemy_container_4
1e7264576d78 acme/my-final-image:1.0 "bash" About a minute ago Up About a minutemy_container_3
dcad7101795e acme/my-final-image:1.0 "bash" About a minute ago Up About a minutemy_container_2
c36785c423ec acme/my-final-image:1.0 "bash" About a minute ago Up About a minutemy_container_1

列出本地存储区的内容:

$ sudo ls /var/lib/docker/containers
1a174fc216cccf18ec7d4fe14e008e30130b11ede0f0f94a87982e310cf2e765
1e7264576d78a3134fbaf7829bc24b1d96017cf2bc046b7cd8b08b5775c33d0c
38fa94212a419a082e6a6b87a8e2ec4a44dd327d7069b85892a707e3fc818544
c36785c423ec7e0422b2af7364a7ba4da6146cbba7981a0951fcc3fa0430c409
dcad7101795e4206e637d9358a818e5c32e13b349e62b00bf05cd5a4343ea513

检查docker镜像的大小:

$ sudo du -sh /var/lib/docker/containers/*
32K/var/lib/docker/containers/1a174fc216cccf18ec7d4fe14e008e30130b11ede0f0f94a87982e310cf2e765
32K/var/lib/docker/containers/1e7264576d78a3134fbaf7829bc24b1d96017cf2bc046b7cd8b08b5775c33d0c
32K/var/lib/docker/containers/38fa94212a419a082e6a6b87a8e2ec4a44dd327d7069b85892a707e3fc818544
32K/var/lib/docker/containers/c36785c423ec7e0422b2af7364a7ba4da6146cbba7981a0951fcc3fa0430c409
32K/var/lib/docker/containers/dcad7101795e4206e637d9358a818e5c32e13b349e62b00bf05cd5a4343ea513

每个容器只占用文件系统32k空间大小。

即写即拷copy-on-write 不仅节省磁盘空间,它还减少启动时间。当你启动一个容器(或基于同一个镜像的多个容器),Docker 仅需生成很“薄”的可写容器层。


如果Docker在每次启动一个新的容器时,不得不制作一个底层镜像的完整副本,容器的启动时间和磁盘空间将大大增加。这将类似于虚拟机的工作方式,每个虚拟机都有一个或多个虚拟磁盘。


数据卷(Data volumes)和存储驱动

当Docker容器被删除后,任何写入Docker容器、而不是保存到data volume的数据,将随着容器一起删除。 数据卷(Data Volume)是Docker主机文件系统中被直接加载进Docker容器的目录或文件。Data Volume不是由存储驱动程序控制的。读取和写入数据卷绕过存储驱动程序并以本地主机速度运行。你可以将任意数量的数据卷装入容器。多个容器也可以共享一个或多个数据卷。


下图展示了单个Docker host主机如何运行两个docker容器。每一个容器在Docker宿主机本地存储区的自有地址空间中存在(/var/lib/docker/...)。有一个单独共享的Data Volume数据卷位于Docker主机的/data目录。它被直接mount到两个镜像中:



Data volumes 驻扎在 Dock Host的本地存储区之外。这使得更多的主机目录增加了独立性。from the storage driver’s control. When a container is deleted, any data stored in data volumes persists on the Docker host.


Data volumes 驻于Docker host本地存储区外,使其相对存储驱动程序控制的独立性进一步增强。当容器被删除,存储数据量的任何数据在Docker主机上被保存。

有关数据卷的详细信息,请参见容器中的数据管理。


For detailed information about data volumes, seeManaging data in containers.

待续:


Related informationSelect a storage driver
AUFS storage driver in practice
Btrfs storage driver in practice
Device Mapper storage driver in practice

相关阅读:
Top