Docker 与存储
写在前面的话
容器的默认状态难以从容器中搬运出来。
容器的默认状态是基于联合文件系统的,也就是需要存储驱动的支持,效率会比直接写到宿主机文件系统里要差一点。
所以此处 storage driver,就正对应 network driver 了。
把数据写入到宿主机上的方法有:volumes 和 bind mounts。如果在 linux 上,还有 tmpfs mount。注意,这些东西不是 storage driver。
他们之间的关系是:
在操作系统和 docker 层面,可以认为 volumes 是有实际存储的设备(虽然被虚拟化)的抽象(在 k8s 抽象里是 volumeMounts),而 mount 是带有 path 的一个挂载点(在 k8s 抽象里是 mountPath)。
Volumes
在宿主机文件系统上,Volumes 使用一块专属的路径来存储 docker 文件,如/var/lib/docker/volumes/
,其他进程不应该碰这些文件。
Volumes 是 Docker 持久化数据最佳的选择。
常见创造数据卷的方法
只能迟至容器创建时把一个数据卷和容器关联起来。
- 是
docker volume create
。 - 容器/服务创建时,使用
-v
/--mount
参数
也就是说,不能追加在容器里创建数据卷。
有数据共享需求,可以考虑使用docker cp
一类的命令来解决。
数据共享
多个容器可以通过共享数据卷的方式来共享数据。即使所有的容器都不在使用卷了,卷对于 docker 进程而言依然是活跃的,除非使用docker volume prune
命令来打扫卷。也就是说,卷的生命周期,和容器的生命周期是互不干涉的。
命名与匿名
当创造卷的时候,卷可以是命名的,也可以是匿名的。匿名的卷只是没有显式地被命名,但 docker 本身还是会给它起一个随机名字。
远程存储数据
卷允许使用卷驱动(volume driver,不是 storage driver),这样我们可以在远程文件系统,或者云系统上存储数据。
常用命令
创建容器的时候创建卷,新用户应该使用 mount 选项而不是使用v 选项。
1 |
|
如果是创建容器的时候,容器内部的目录已经有数据,而绑定一个 volume 进去,则会自动把内部容器的数据拷贝到 volume 里面。
bind mounts
可以让数据在宿主机文件系统中的所有部分,也可以让其它进程修改。它是 docker 早期就存在的技术。
bind mounts 在宿主机上的路径并不被要求早已存在,它会在需要的时候被创建出来,也就是所谓的pre-populate data。当然这也要求宿主机有一些特定的目录结构存在,如果这个目录结构不存在,那么还是要回来寻求 volume 的帮助,因为 volume 使用的路径完全可以被 docker 守护进程把控。
额外的副作用
docker 容器有完全地修改 bind mount 内文件内容的权限。所以如果宿主机里有什么敏感文件的话,不要放在这里面,否则会产生安全隐患。这也是为什么推荐使用 volumes 的原因。
常见命令
1 |
|
volume vs bind mount
- 如果只是简单地想在把容器的状态持久化到宿主机上,应该直接使用数据卷,这样新的容器也能继承老容器的状态。更抽象地说,如果需要在容器和容器之间转移状态,可以使用数据卷。
- 如果只是要备份数据,可以考虑停下容器,把数据卷里的内容拷贝出来。
- 数据卷在不同的宿主系统上是通用的。
- 如果想进一步在宿主机和容器之间共享状态,那么可以考虑使用 bind mount。
tmpfs mounts
这个是在内存里面做 bind mounts。所以本质上是不持久化的。
存储驱动(storage driver)相关
分层架构
存储驱动总是基于联合文件系统,其基本架构如图:
可以看到,多个容器之所以能够基于一个镜像诞生,是因为镜像的层次是 RO 的,而每个容器的 RW layer 是彼此独立的。
如果有跨镜像的同份数据共享,请使用 volume。
大小计算
使用 docker ps -s 命令,我们可以看到两个 size:
- size。容器的可写层在磁盘上的数据大小。
- virtual size。镜像的只读数据加上 size 的大小。
其他更细致的计算请查看《Container size on disk》。
docker 除了 layer 以外,还有其他消耗磁盘的地方。比如写日志,有专门的日志驱动,这需要另外计算大小。
COW 问题
为了最大效率地(for maximum efficiency)利用磁盘,跨层之间是使用 COW(Copy On Write)策略来利用文件的。也就是说,当上层(包括可写层在内)的层要使用下层的文件,应该先直接访问,在有需要修改的需求时,才将该文件拷贝一份放到本层里。
通常,应该让容器自己的 writable layer 变得非常薄。
层次存储的位置
通常在目录/var/lib/docker/
下,例如:
1 |
|
常见的存储驱动
- aufs
- overlay
- overlay2
- btrfs
- ZFS
- others
docker 选择驱动的优先级列表:
btrfs/zfs -> overlay2 -> overlay -> aufs/devicemapper
在当代,默认的存储驱动总是 overlay2,而以前则是 aufs。
选择存储驱动要考虑的因素主要有:
- 是否零配置。
- 操作系统是否支持。
- 稳定性。