学习资料:公众号cloudman
文章目录
Docker的两类存贮资源
Docker为容器提供了两种存放数据的资源:
- 由storage driver管理的镜像层和容器层
- Data Volume
我们会详细讨论它们的原理和特性。
storage driver
在前面镜像章节我们学习到Docker镜像的分层结构,简单回顾一下。
容器由最上面一个可写的容器层,以及若干只读的镜像层组成,容器的数据就存放在这些层中。这样的分层结构最大的特性是Copy-on-Write:
- 新数据会直接存放在最上面的容器层
- 修改现有数据会先从镜像层将数据复制到容器层,修改后的数据直接保存在容器层中,镜像层保持不变。
- 如果多个层中有命名相同的文件,用户只能看到最上面那层中的文件
分层结构使镜像和容器的创建、共享以及分发变得非常高效,而这些都要归功于Docker storage driver。正式storage driver实现了多层数据的堆叠并为用户提供一个单一的合并之后的统一视图。
Docker支持多种storage driver,有AUFS、Device Mapper、Btrfs、OverlayFS、VFS和ZFS。它们都能实现分层的架构,同时又有各自的特性。对于Docker用户来说,具体选择使用哪个storage driver是一个难题,因为:
- 没有哪个driver能够适应所有的场景。
- driver本身在快速发展和迭代
不过Docker官方给出了一个简单的答案:
优先使用Linux发行版默认的storage driver
Docker 安装时会根据系统的配置选择默认的driver。默认driver具有最好的稳定性,因为默认driver在发行版上经过了严格的测试。
运行docker info
查看默认driver:
对于某些容器,直接将数据放在由storage driver维护的层中是很好的选择,比如那些无状态的应用。无状态意味着容器没有需要持久化的数据,随时可以从镜像直接创建。比如busybox,它是一个工具箱,我们启动busybox是为了执行诸如wget、ping之类的命令,不需要保存数据供以后使用,使用完直接退出,容器删除时存放在容器层中的工作数据也一起被删除,这没问题,下次再重新启动新容器即可。
但对于另一类应用这种方式就不合适了,它们有持久化数据的需求,容器启动时需要加载已有的数据,容器销毁时希望保留产生的新数据,也就是说,这类容器都是有状态的。
这就要用到Docker的另一种存储机制:Data Volume。
Data Volume之bind mount
Data Volume本质上是Docker Host文件系统中的目录或文件,能够直接被mount到容器的文件系统中。Data Volume有以下特点:
- Data Volume是目录或文件,而非没有格式化的磁盘(块设备)
- 容器可以读写volume中的数据
- volume数据可以被永久地保存,即时使用它的容器已经销毁
好,我们现在有数据层(镜像层和容器层)和volume都可以用来存放数据,具体使用的时候要怎样选择呢?考虑下面几个场景:
- Database软件 vs Database数据
- Web应用 vs 应用产生的日志
- 数据分析软件 vs input/output数据
- Apache Server vs 静态 HTML文件
相信大家都会做出这样的选择:
- 前者放在数据层中,因为这部分内容是无状态的,应该作为镜像的一部分。
- 后者放在Data Volume中。这是需要持久化的数据,并且应该与镜像分开存放。
还有个大家可能会关心的问题:如何设置volume的容量?
因为volume实际上是docker host文件系统的一部分,所以volume的容量取决于文件系统当前未使用的空间,目前还没有方法设置volume的容量。
在具体的使用上,docker提供了两种类型的volume:bind mount和docker managed volume。
bind mount
bind mount是将host上已存在的目录或文件mount到容器。
例如 docker host上有目录或$HOME/htdocs:
通过-v
将其mount到httpd容器:
-v
的格式为<host path>:<container path>
。/usr/local/apache2/htdocs就是apache server存放静态文件的地方。由于/usr/local/apache2/htdocs已经存在,原有数据会被影藏起来,取而代之的是host $HOME/htdocs/中的数据,这与linux mount
命令的行为是一致的。
curl显示当前主页确实是$HOME/htdocs/index.html中的内容。更新一下,看是否能生效:
host中的修改确实生效了,bind mount可以让host与容器共享数据。这在管理上是非常方便的。
下面我们将销毁容器,看看对bind mount有什么影响:
可见,即使容器没有了,bind mount也还在。这也合理,bind mount是host文件系统中的数据,只是借给了容器用用,哪能随便就删了啊。
另外,bind mount时还可以指定数据的读写权限,默认是可读可写,可指定为只读:
ro
设置了只读权限,在容器中是无法对bind mount数据进行修改的。只有host有权修改数据,提高了安全性。
除了bind mount目录,还可以单独指定一个文件:
使用bind mount单个文件的场景是:只需要向容器添加文件,不希望覆盖整个目录。在上面的例子中,我们将html文件加到apache中,同时也保留了容器原有的数据。
使用单一文件有一点要注意:host中的源文件必须要存在,不然会当作一个新目录bind mount给容器。
mount point有很多应用场景,比如我们可以将源代码目录mount到容器中,在host中修改代码就能看到应用的实时效果。再比如将mysql容器的数据放在bind mount里,这样host可以方便地备份和迁移数据。
bind mount的使用直观高效,易于理解,但它也有不足的地方:bind mount需要指定host文件系统的特定路径,这就限制了容器的可移植性,当需要将容器迁移到其他host,而该host没有要mount的数据或数据不再相同的路径时,操作将会失败。
移植性更好的方式是docker managed volume,下一节我们讨论。
docker managed volume
docker managed volume与bind mount在使用上的最大区别是不需要指定mount源,指明mount point就行了。还是以httpd容器为例:
我们通过-v
告诉docker需要一个data volume,并将其mount到/usr/local/apache2/htdocs。那么这个data volume具体在哪儿呢?
这个答案可以自容器的配置信息中找到,执行docker inspect
命令:
docker inspect <短ID/长ID>
输出很多信息,我们感兴趣的是Mounts
这一部分,这里会显示容器当前使用的所有data volume,包括bind mount 和docker managed volume。
Source
就是该volume在host上的目录。
原来,每当容器申请 mount docker managed volume时,docker都会在/var/lib/docker/volumes
下生成一个目录(例子中是"/var/lib/docker/volumes/1766f8600b85d2371458a5c5ced1d2b15a5a9ae8e4f07ebdedda8c321071769e/_data"),这个目录就是mount源。
下面继续研究这个volume,看看里面有什么东西:
这个命令在WSL2中不起作用,但是可以在docker desktop中找到数据。
volume的内容跟容器原有/usr/local/apache2/htdocs完全一样,这是怎么回事呢?
这是因为:如果mount point指向的是已有目录,原有数据会被复制到volume中。
但要明确一点:此时的/usr/local/apache2/htdocs已经不再是由storage driver管理的层数据了,它已经是一个data volume。我们可以像bind mount一样对数据进行操作,例如更新数据:
简单回顾一下docker managed volume的创建过程:
- 容器启动时,简单地告诉docker“我需要一个volume存放数据,帮我mount到目录/abc”
- docker 在/var/lib/docker/volumes中生成一个随机目录作为mount源
- 如果/abc已经存在,则将数据复制到mount源
- 将volume mount到/abc
除了通过docker inspect
查看volume,我们也可以用docker volume
命令:
目前,docker volume
只能查看docker managed volume,还看不到bind mount;同时也无法知道colume对应的容器,这些信息还得靠docker inspect
我们已经学习了两种data volume的原理和基本使用方法,下面做个对比:
- 相同点:两者都是host文件系统中的某个路径
- 不同点:
bind | docker managed volume | |
---|---|---|
volume位置 | 可任意指定 | /var/lib/docker/volumes/… |
对已有mount point影响 | 隐藏并替换为volume | 原有数据复制到volume |
是否支持单个文件 | 支持 | 不支持,只能是目录 |
权限控制 | 可设置为只读,默认为读写权限 | 无控制,均为读写权限 |
移植性 | 移植性弱,与host path绑定 | 移植性强,无需指定host目录 |
如何共享数据?
数据共享是volume的关键特性,本节我们详细讨论通过volume如何在容器与host之间,容器与容器之间共享数据。
容器与host共享数据
我们有两种类型的data volume,它们均可实现在容器与host之间共享数据,但方式有所区别。
对于bind mount是非常明确的:直接将要共享的目录mount到容器。具体请参考前面httpd的例子。
docker managed volume就要麻烦点。由于volume位于host中的目录,是在容器启动时才生成,所以需要将共享数据拷贝到volume中。请看如下例子:
docker cp
可以在容器和host之间拷贝数据,当然我们也可以直接通过Linux的cp
命令复制到/var/lib/docker/volumes/xxx
容器之间共享数据
第一种方式是将共享数据放在bind mount中,然后将其mount到多个容器,还是以httpd为例,不过这次的场景复杂些,我们要创建由3个httpd容器组成的web server集群,它们使用相同的html文件,操作如下:
- 将$HOME/htdocs mount到三个httpd容器。
- 查看当前主页内容
- 修改volume中的主页文件,再次查看并确认所有容器都使用了新的主页。
用volume container共享数据
volume container是专门为其他容器提供volume的容器。它提供的卷可以是bind mount,也可以是docker managed volume。下面我们创建一个volume container:
我们将容器命名为vc_data
(vc是volume container的缩写)。注意这里执行的是docker create
命令,这是因为volume container的作用只是提供数据,它本身不需要处于运行状态。容器mount了两个volume:
- bind mount,存放web server的静态文件
- docker managed volume,存放一些实用工具(当然现在是空的,这里只是做个示例)
通过docker inspect可以看到这两个volume。
其他容器可以通过--volumes-from
使用vc_data
这个volume container:
web1使用了vc_data,通过docker inspect web1
发现它使用的就是vc_data的volume,连mount point都是一样的,验证一下数据共享的效果:
可见,web1使用了volume container的volume,同理可以通过--volumes-from
设置其他容器的volume。
data-packed volume container
在上一节的例子中,volume container的数据归根到底还是在host里,有没有办法将数据完全放到volume container中,同时又能与其他容器共享呢?
当然可以,通常我们称这种容器为data-packed volume container。其原理是将数据打包到镜像中,然后通过docker managed volume共享。
我们用下面的Dockerfile构建镜像:
FROM busybox:latest
ADD htdocs /usr/local/apache2/htdocs
VOLUME /usr/local/apache2/htdocs
build新镜像datapacked:
用镜像创建data-packed volume container:
因为在Dockerfile中已经使用了VOLUME
指令,这里就不需要指定volume的mount point了。
启动httpd容器并使用data-packed volume container:
容器能够正确读取volume中的数据。data-packed volume container是自包含的,不依赖host提供数据,具有很强的移植性,非常适合只使用静态数据的场景,比如应用的配置信息、web server的静态文件等。
volume生命周期管理
Data Volume中存放的是重要的应用数据,如何管理volume对应用至关重要。前面我们主要关注的是volume的创建、共享和使用,本节将讨论如何备份、恢复、迁移和销毁volume。
备份
因为volume实际上是host文件系统中的目录和文件,所以volume的备份实际上是对文件系统的备份。
还记得我们之前是如何搭建本地Registry的吗?
docker run -d -p 5000:5000 -v /myregistry:/var/lib/registry registry:2
所有的本地镜像都存在host的/myregistry目录中,我们要做的就是定期备份这个目录。
恢复
volume的恢复也很简单,如果数据损坏了,直接用之前备份的数据拷贝到/myregistry就可以了
迁移
如果我们想使用更新版本的Registry,这就涉及到数据迁移,方法是:
-
docker stop
当前Registry容器 -
启动新版本容器并mount 原有volume
docker run -d -p 5000:5000 -v /myregistry:/var/lib/registry registry:latest
当然,在启动新容器前要确保新版本的容器的默认路径是否发生变化。
销毁
可以删除不再需要的volume,但一定要确保自己正在做什么,volume删除后数据是找不回来的。
docker不会销毁bind mount,删除数据的工作只能由host负责。对于docker managed volume,在执行docker rm
删除容器时可以带上-v
参数,docker 会将容器使用到的volume一并删除,但前提是没有其他容器mount到该volume,目的是保护数据,非常合理。
如果删除容器时没有带-v
呢?这样就会产生孤儿volume,好在docker提供了volume子命令可以对docker managed volume进行维护,请看下面的例子:
容器bbox使用的docker managed volume可以通过docker volume ls
查看到。
删除bbox:
因为没有使用-v
,volume遗留了下来。对于这样的孤儿volume,可以用docker volume rm
删除:
如果想批量删除孤儿volume,可以执行:
docker volume rm $(docker volume ls -q)
小结
本章学习了以下内容:
- docker为容器提供了两种存储资源:数据层和Data Volume。
- 数据层包括镜像层和容器层,由storage driver管理。
- Data Volume有两种类型:bind mount和docker managed volume。
- bind mount可实现容器与host之间,容器与容器之间共享数据。
- volume container是一种具有更好移植性的容器间数据共享方案,特别是data-packed volume container。
- 最后我们学习了如何备份、回复、迁移和销毁Data volume。
这章的内容只是单个docker host中的存储方案。而跨主机存储也是一个重要的主题,当然也更复杂。有机会再学习。