Docker 数据持久化
概览
默认情况下,在 docker 容器(container)内创建的文件或产生的数据都只是保存在容器的可写层,这意味着当容器不存在时,容器内产生的数据也没有保存下来。
Docker 提供两种容器数据持久化的方法,使用这两种方法即使容器不存在时,数据也能持久化下来:
- Bind mount:bind mount 可以是宿主机(host)文件系统的任意目录或文件,除了 docker 容器可以访问,宿主机上也可以对这些目录或文件读写操作
- Volume:volume 数据持久化在宿主机文件系统
/var/lib/docker/volumes/
目录下,volume 数据由 docker 进行管理,外部不要对 volume 数据进程修改。 Volume 方式是 docker 官方推荐的数据持久化方式。
另外,如果 docker 运行在 Linux 宿主机,还可以使用 tmpfs mount。tmpfs mount 的数据只是保存在宿主机的内存中,并没有持久化到硬盘。
Docker 容器与宿主机数据共享的方式如下图所示:
无论使用哪种方式,对于 docker 容器来说,这些数据都被看作成一个目录或文件,数据的使用方式是一样的。
Bind mount
Bind mount 方式是 docker 早期使用的容器与宿主机数据共享的方式,可以实现将宿主机上的文件或目录挂载(mount)到 docker 容器中使用。
相对于 volume 方式,bind mount 方式存在不少的局限。例如,bind mount 在 Linux 和 Windows 操作系统下不可移植。因此 docker 官方推荐使用 volume 方式。
为了便于说明 docker 数据共享的特点,我们以 busybox 镜像为例进行操作演示。
例如,使用 docker run -v
命令来将将宿主机的 /Users/lihao/code/docker/busybox
目录挂载到容器的 /databind
目录:
1 | docker run -it --name container1 -v /Users/lihao/code/docker/busybox:/databind busybox |
这时,在宿主机上创建两个文件 file1.txt
和 file2.txt
,再在容器内执行命令:
1 | cd /databind |
可以同步看到刚创建的两个文件:
file1.txt file2.txt
对于 bind mount,有几点需要注意。
-v
宿主机目录路径必须以/
或~/
开头,否则 docker 会将其当成是 volume 而不是 bind mount- 如果宿主机上的目录不存在,docker 会自动创建该目录
- 如果容器中的目录不存在,docker 会自动创建该目录
- 如果容器中的目录已有内容,那么 docker 会使用宿主机上目录的内容覆盖容器目录的内容
Volume
与 bind mount 不同,volume 由 docker 创建和管理,docker 所有的 volume 都保存在宿主机文件系统的 /var/lib/docker/volumes
目录下(注意,mac 操作系统以虚拟机运行 docker,因此并不存在该目录,可以参考 stackoverflow)。
Docker 引入 volume 的原因有:
- 删除容器时,volume 不会被删除
- 在不同的容器之间共享 volume (存储 / 数据)
- 容器与存储分离
- 将 volume 存储在远程主机或云上
可以使用 docker run -v
参数为启动容器加载一个 volume,例如:
1 | docker run -it -v /data --name container1 busybox |
这样,我们就启动了一个名为 container1
的容器,由于使用了 -it
参数,我们以交互方式进行了容器内部。
这时,执行命令:
1 | ls |
可以看到容器中 /data
目录:
bin data dev etc home proc root sys tmp usr var
接着,我们在容器 /data
目录创建 file1.txt
文件:
1 | cd data |
退出容器后,执行 docker ps -a
可以看到 container1
容器的退出状态。
现在,我们 inpect
一下容器,看下 docker 在启动容器时做了什么:
1 | docker inspect container1 |
可以看到一大串 JSON 格式的输出,我们重点关注 Mounts
字段的输出。
1 | "Mounts": [ |
可以看到,当容器 container1
加载一个 volume (/data)时,docker 在目录 /var/lib/docker/volumes/
创建一个新的目录,用来存储容器中产生的文件。同时,我们注意到 /data
的 RW 属性为 true
,即可读可写。
我们重新启动 container1
容器,并进入容器:
1 | docker restart container1 |
在容器执行命令:
1 | ls |
可以看到 /data
目录仍在:
bin data dev etc home proc root sys tmp usr var
之前创建的 file1.txt
文件也在:
file1.txt
这说明,即使容器关闭,之前在 volume 存储的文件仍然会保留下来。
然后,我们退出容器,并将容器 container1
删除。
1 | docker rm container1 |
这时,使用命令 docker volume ls
查看当前的 volume,可以看到 9ee296c604fcb8e89c726b3ce07b55efe700763058c992821a81ca1c30f11cc5
volume 仍然存在。也就是说,即使容器不存在了,volume 仍可在宿主机上保存下来。
local 9ee296c604fcb8e89c726b3ce07b55efe700763058c992821a81ca1c30f11cc5
如果需要在其他容器也使用这个 volume,可以使用以下命令加载指定的 volume:
1 | docker run -it -v 9ee296c604fcb8e89c726b3ce07b55efe700763058c992821a81ca1c30f11cc5:/data busybox |
可以使用 docker volume rm
删除指定的 volume。
挂载指定的 volume
上面的 docker run -v
命令中,我们并没有指定 volume 的名称,这样 docker 会默认给我们创建一个匿名的 volume。
我们也可以挂载指定名称的 volume:
1 | docker run -it -v my-volume:/data --name container1 busybox |
这样,我们在启动容器 container1
时,将挂载一个名为 my-volume
的 volume,并挂载到容器的 /data
目录。对于 docker 来说,如果 my-volume
不存在,那么 docker 就会自动创建该 volume,并挂载到 /data
目录。
退出容器后,执行
1 | docker volume inspect my-volume |
可以看到 my-volume
的 JSON 输出信息:
1 | [ |
可以看到,名为 my-volume
的 volume 在宿主机的目录为 /var/lib/docker/volumes/my-volume/_data
。
除了让 docker 帮我们自动创建 volume,我们也可以自行创建 volume:
1 | docker volume create my-volume2 |
然后将这个手工创建的 volume 挂载到容器:
1 | docker run -it -v my-volume2:/data --name container1 busybox |