volume是存在于一个或多个容器中的特定文件或文件夹,这个目录以独立于联合文件系统的形式在宿主机中存在,并为数据库共享与持久化提供便利.

数据卷类型

Docker提供两种不同的方式将容器内的数据映射到Docker宿主机上,来实现数据的持久性:

  • 绑定挂载(Bind Mount)
  • 卷(Volumes)

其中Docker 卷又主要有两种类型:

  • 匿名卷
  • 命名卷

一般推荐使用 Bind Mount方式持久化数据。

使用容器数据卷非常简单,只需在使用docker run命令运行容器的时候加上 -v 选项即可。

绑定挂载(Bind Mount)

bind mount自docker早期便开始为人们使用了,用于将host上的文件或目录被挂载到容器中。挂载时需要我们指定文件或目录在 host 上的完整路径。其使用方式为:

1
docker run -d -v 主机目录:容器目录 镜像名称

通过这种方式运行命令后,会在宿主机上自动创建相应的目录,在宿主机上该目录下创建或者修改文件都会自动同步到容器中去。删除容器后,数据卷还存在,这样保证了容器中相关数据的持久化。

需要注意的是:使用Bind Mount,宿主机文件夹将覆盖容器文件夹内容,因此如果宿主机文件夹为空,需要先将容器中要映射的文件复制到宿主机文件夹中。

此外,需要记住的是在 Dockerfile 中不能创建绑定卷。

卷(Volumes)

匿名数据卷

在运行镜像的时候,不指定宿主机数据卷即可创建匿名数据卷,例如:

1
docker run -d --name redis   -p 6379:6379   -v /data redis:latest redis-server /usr/local/etc/redis/redis.conf

这里的-v并没有指定宿主机目录,可以通过docker容器ID查看在宿主机上的具体路径。

1
docker inspect 容器ID | grep -a10 Mount

匿名数据卷的优点在于,它们不需要用户手动管理。如果容器启动时使用了 --rm 选项,容器停止时,容器被自动删除,匿名卷也会自动被删除。

直接通过docker rm删除容器匿名卷不会被删除,只有通过docker stop停止容器才会将匿名卷删除

但是如果容器启动时没有--rm选项,即使使用命令 docker stop/rm my_container 停止了容器,匿名卷不会被自动删除。

命名数据卷

对于匿名数据卷,只需要在-v的时候指定一个名称即可,如果容器被删除,命名数据卷会保留。

1
docker run -d --name redis -p 6379:6379 -v redis-data:/data -v redis:latest redis-server /usr/local/etc/redis/redis.conf

不能在 Dockerfile 内创建命名卷,只能在运行容器时使用-v 选项指定

读写权限

默认情况下,卷和绑定挂载都是可读写的,这意味着容器不仅可以读取主机上挂载的数据,还可以修改这些数据。如果需要自定义数据卷的读写权限,可以在Docker 命令中的挂载选项后面添加:xx(ro:只读、rw:可读可写)来实现,例如:

1
docker run -d --name redis -p 6379:6379 -v /data/redis/data:/data -v /data/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf:ro   redis:latest redis-server /usr/local/etc/redis/redis.conf

在运行这个redis容器后,redis容器只能读取/usr/local/etc/redis/redis.conf,不能进入容器修改它,这种方式适用于所有-v

如果不加,默认是rw,关于rwro的区别:

  • ro
    • 文件:容器内不能修改,提示read-only
    • 文件夹:容器内不能新增、删除、修改文件夹中的文件,会提示read-only
  • rw
    • 文件:宿主机和容器内修改都会互相同步,但容器内不允许删除,如果宿主机删除文件,那么容器不会同步。
    • 文件夹:宿主机或者容器增删改都可以。

多容器之间共享数据

想要让多容器之间共享数据,只能用卷,绑定挂载不支持。使用方法就是将命名数据卷挂在上去即可。常用案例就是wordpress + certbot,具体片段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
webserver:
depends_on:
- wordpress
image: nginx:1.27.1-alpine
container_name: webserver
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- wordpress:/var/www/html
- ./nginx-conf:/etc/nginx/conf.d
- certbot-etc:/etc/letsencrypt
networks:
- app-network

certbot:
depends_on:
- webserver
image: certbot/certbot
container_name: certbot
volumes:
- certbot-etc:/etc/letsencrypt
- wordpress:/var/www/html

这里nginx和certbot需要互相访问。

数据卷常用命令

创建数据卷

1
docker volume create 数据卷名称

查看数据卷

1
docker volume ls

删除数据卷

1
docker volume rm 数据卷名

删除所有没被挂载到容器上的数据卷

1
docker volume prune

查看指定数据数据卷信息

1
docker volume inspect 数据卷名称

文件挂载的相关问题

在启动docker容器时,动态的瓜子啊一些文件已覆盖镜像中原有的文件,但如果挂载一个宿主机上不存在的文件夹或者我呢见到容器中会出现什么问题呢?

例如我们创建容器的命令:

1
docker run -v /path-to-folder/A:/path-to-folder/B test-image

文件夹挂载

宿主机上文件夹存在,且非空

这个时候无论容器中的文件夹B是否存在,A都会完全覆盖B的内容:

宿主机 容器 挂载结果
存在的非空文件夹A 不存在的文件夹B 先在容器中创建文件夹B,再将A文件夹中的所有文件拷贝到B中
存在的非空文件夹A 存在的非空文件夹B 先将容器文件夹B的原有内容清空,再将A中文件拷贝到B中

宿主机上文件夹存在,但为空

容器中对应的文件夹内容会被清空

宿主机上文件夹不存在

容器中对应的文件夹内容被清空:

宿主机 容器 挂载结果
不存在的文件夹A 存在的非空文件夹B 在宿主机上创建文件夹A,容器中文件夹B的内容会被清空
不存在的文件夹A/B/C 存在的非空文件夹B 在宿主机上创建文件夹A/B/C,容器中的文件夹B的内容被清空。

可以看出总之host上文件夹一定会覆盖container中文件夹。

文件挂载

docker 禁止用主机上不存在的文件挂载到container中已经存在的文件。

宿主机上文件不存在

如果宿主机上的文件xxxA.js不存在,容器中存在的文件xxxxB.js,此时挂载会报错,并且在宿主机上生成两个空目录 xxxA.js和xxxB.js,并且容器无法启动。

宿主机上文件存在

宿主机 容器 挂载结果
存在的文件xxxA.js 存在的文件xxxB.js 容器中文件名xxxB.js保持不变,但文件内容会被xxxA.js的内容覆盖
存在的文件xxxA.js 不存在的文件xxxB.js 容器中会新建一个xxxB.js,其内容就是宿主机上的xxxA.js的内容,xxxB.js所在文件夹下的所有其他文件维持不变

总结

从上面的分析可知,文件夹挂载以整个文件夹为单位进行文件覆盖,故可在需要将大量文件挂载进container时使用,另外,如果挂载一个空文件夹或者不存在的文件夹,一般是做逆向使用: 即容器启动后,可能会在容器内挂载点的文件夹下生成一些文件(如日志),此时,在对应的host上的文件夹内就能直接看到。

文件挂载由于只会覆盖单个文件而不会影响container中同一文件夹下的其他文件,常常被用来挂载配置文件,以在运行时,动态的修改默认配置。

容器中类似mysql,redis这样的数据目录可以映射到宿主机目录的原因是,目录里的内容不是在容器启动前就存在的,而是在容器启动后生成的。如果试着在宿主机/path-to-folder/A中事先放一些东西,那么你会发现容器启动后,原来的文件和容器启动后生成的文件是并存的。

本文转载自:小白快速上手 Docker 03 | Docker数据卷 - 知乎