在使用Docker的时候,我们常常需要对数据进行持久化,或者在多个容器中进行数据共享。
Docker镜像
docker的镜像是由多个只读的文件系统叠加在一起形成的。当我们在启动一个容器的时候,docker会加载这些只读层并会在这些只读层的上面(栈顶)增加一个读写层。这是如果修正在运行的容器中的已有文件,那么这个文件将会从只读层复制到读写层。该文件的只读版本还在,只是被上面读写层的该文件的副本隐藏。当删除容器或者重新启动时,之前的更改会消失。在Docker中,只读层及在顶部的读写层的组合被称为Union File System (联合文件系统)。
数据卷
为了实现数据保存和数据共享,Docker提出了Volume这个概念,简单的说就是绕过默认的联合文件系统,而以正常的文件或者目录的形式存于宿主机上,被称作数据卷。
在Docker中,要想实现数据的持久化(所谓的Docker的数据持久化即数据不随着容器Containerde 结束而结束),需要讲数据从宿主机挂载到容器中。
挂载方式分类
目前Docker提供了三种不同的方式将数据从宿主机挂载(mount)到容器中>>>即当我们删除容器时,为了保留容器运行期间产生的数据,通过数据卷volume技术,实现容器内部与linux宿主机上文件目录共享的技术。
- volumes
- bind mounts
- tmpfs mounts
一般而言,volumes是最好的选择。
三种挂载方式的区别:数据在宿主机上是如何存在的?
Volumes由Docker管理,存储在宿主机的某个地方(在linux上默认是/var/lib/docker/volumes/)。非Docker应用程序不能改动这一位置的数据。Volumes是Docker最好的数据持久化方法。
Bind mounts的数据可以存放在宿主机的任何地方。数据甚至可以是重要的系统文件或目录。非Docker应用程序可以改变这些数据。
tmpfs mounts的数据只存储在宿柱苣的内存中,不会写入到宿主机的文件系统。
特性:
- 可以在容器之间共享和重用
- 对数据卷内数据的修改会立马生效,无论是在容器内操作还是在本地操作
- 对数据卷的更新不会影响镜像,解耦开应用和数据
- 卷会一直存在,知道没用容器使用后就可以安全的手动卸载掉
挂载方式详解
Volumes:
由Docker进程进行创建和管理。可以通过命令docker volume create来创建一个指定的卷,也可以由Docker进程在创建容器或服务的过程中来创建;
当你为一个容器创建一个volume时,这个volume将会被存储到宿主机的一个目录下(默认为/var/lib/docker/volumes/)。当你挂载这个volume到一个容器中时,就是在挂载这个目录到容器中。这和bind mount的工作机制很相似,当然,除了volume是被Docker所管理并与宿主机其他核心功能隔离之外。
一个volume可以同时被挂载到多个容器中。当没有任何容器在使用这个volume时,这个volume也仍然可以被Docker进程所使用,而不是被自动删除。当然,你可以使用命令手动删除volume:docker volume prune
当你挂载一个volume时,可以选择为他命名(named),也可以不命名(anonymous)。如果不命名的话,当这个volume首次被挂载到一个容器中时,Docker进程会分配给它一个随机的名字,以保证在宿主机操作系统中这个volume的名字唯一。除了名字外,命名和不命名的volume没有区别。
Volume也支持使用volume drivers,以帮助你将数据保存到远程主机或者云上。
Bind mounts:
在docker的早期版本中就存在的功能,与volumes相比,他的功能比较局限。当使用 bind mounts时,宿主机的目录或者文件被挂载到容器中。容器将按照挂载目录或文件的绝对路径来使用或者修改宿主机中的数据。宿主机中的目录或文件不需要预先存在,在需要的时候会自动创建。使用Bind mounts在性能上是非常好的,但这依赖于宿主机的有一个结构化的文件系统。如果你要创建一个新的Docker应用,我们仍推荐使用named volume的方式,因为你无法通过Docker Cli来管理bind mounts。(警告:bind mounts是一把双刃剑,因为使用bind mounts的容器可以通过容器内部的进程对主机文件系统进行修改,包括创建、修改和删除重要的系统文件和目录,这个功能虽然强大,但显然也会造成安全方案的影响,包括影响到宿主机上Docker以外的进程)。
tmpfs mounts:
在这种挂载方式下,容器中的应用数据将不会持久化到硬盘上,其中的数据只能在某个容器的生存周期内被使用,或者用于保存一些不需要持久存储或这一个铭感的数据信息。
适用场景
volume:
- 多个容器间需要共享数据。如果volume没有手动被创建,他将会首次挂载到某个容器之前被自动创建,当容器被停止或者删除时,这个volume不会随之被删除。多个容器可以同时以rw或ro的方式挂载这个volume。只有手动指定删除volume,他才会被删除。
- 当宿主机并没有专用于Docker的文件系统结果时,使用volume可以使宿主机的配置于容器的运行解耦。
- 当你希望将数据保存到远程主机或者云上。
- 当你希望在不同的宿主机直接备份/恢复/迁移数据时,volume是一个很好的选择。你可以停止运行使用volume的容器,然后直接备份volume所在的目录即可。
bind mounts:
- 将宿主机的系统配置文件共享给容器,例如:Docker为容器提供DNS配置的默认方式,即通过bind mounts的方式将宿主机的/etc/resolv.conf文件挂载到容器中。
- 将宿主机开发环境的源代码或者实验结果共享给容器。例如:你在宿主机上进行一个项目的Maven测试,每次你再宿主机上对maven项目进行了更改,容器就可以直接获取更改后的结果。
- 当你可以确定宿主机的文件系统结构应该于容器内部完全一致时。
tmpfs:
- 当你处于安全原因或者容器性能优化的原因(如需要协议大量的不持久的状态数据时),不需要容器的数据长久保存时可以使用这种方式。
volume与bind mounts挂载影响
- 如果你使用volume的方式挂载了一个空的volume到某个容器的一个非空目录中,则容器中的这个非空目录中已存在的内容会被拷贝到这个volume中。
- 如果你使用bind mounts的方式挂载一个非空目录到容器的一个非空目录,(或者使用volumes的方式挂载了一个非空的volume到容器的一个非空目录中,)则容器中这个非空目录下的内容将暂时被挂载过来的volume中的内容所覆盖(但是未被删除),当取消挂载后,容器中那个非空目录中的文件仍然存在。
Docker Volumes修改不生效原因
由Docker 数据卷的特性可知:对数据卷内数据的修改会立马生效,无论是在容器内操作还是在本地操作。
如果修改宿主机上的数据后,发现容器内没有同步更新(反之,亦然),可能是由于以下几个原因导致的:
- 权限问题:宿主机上的目录可能没有正确的权限设置,导致容器无法访问或修改这个目录中的文件。使用chmod命令修改权限。
- 挂载路径错误:可能是由于挂载路径的设置不正确,导致容器无法正确映射到宿主机的目录。通过docker inspect 命令查看容器的挂载信息。
- 缓存问题:Docker有可能会缓存挂载的数据,导致宿主机数据修改后,容器内没有及时更新。可以重启docker服务或者重新启动容器来清除缓存。
docker run 使用挂载
volume(-v)
--mount
mount参数
docker inspect redis
Mounts部分的信息:
- Type: volume
- Source:就是在宿主机上的具体的目录位置
- Destination: 容器中的挂载路径
- Driver: volume的驱动程序类型
- RW: 是否是读写模式
- Propagation:传播方式
参数类型 | 描述 |
shared | 任何挂载了此目录的容器都会收到实时更新,而且更新是双向的:物理机到容器,容器到物理机。作用范围是当前目录,不包括子目录。 |
slave | 任何挂载了此目录的容器都会收到实时更新,但是更新是单项的:物理机到容器。作用范围是当前目录,不包括子目录。 |
private | 任何挂在了此目录的容器都不会收到实时更新。 |
rshared | 任何挂载了此目录的容器都会收到实时更新,而且更新是双向的:物理机到容器,容器到物理机。作用范围包括子目录。 |
rslave | 任何挂载了此目录的容器都会收到实时更新,但更新是单向的:物理机到容器。作用范围包括子目录。 |
rprivate | 任何挂在了此目录的容器都不会收到实时更新。 |
docker-compose volumes语法
volumes卷配置有个简短的语法格式,定义为:[SOURCE:]TARGET[:MODE]
SOURCE可以是主机系统上的命名卷或者(相对/绝对)路径
TARGET是容器中的绝对路径
MODE是一个挂载选项,可以是只读或者读写,这个参数是可选的。ro(readonly只读)或rw(默认:readwrite 可读可写)