概述

数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性:

  1. 数据卷可以在容器之间共享和重用
  2. 对数据卷的修改会立马生效
  3. 对数据卷的更新,不会影响镜像.
  4. 数据卷默认会一直存在,即使容器被删除

Docker 提供了三种适用于不同场景的文件系统挂载方式:Bind MountVolumeTmpfs Mount

  • Bind Mount 能够直接将宿主操作系统中的目录和文件挂载到容器内的文件系统中,通过指定容器外的路径和容器内的路径,就可以形成挂载映射关系,在容器内外对文件的读写,都是相互可见的。
  • Volume 也是从宿主操作系统中挂载目录到容器内,只不过这个挂载的目录由 Docker 进行管理,我们只需要指定容器内的目录,不需要关心具体挂载到了宿主操作系统中的哪里。
  • Tmpfs Mount 支持挂载系统内存中的一部分到容器的文件系统里,不过由于内存和容器的特征,它的存储并不是持久的,其中的内容会随着容器的停止而消失。

图片.png

bind mount

--volume , -v

bind mount类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的 数据卷,bind Mounting通过docker run -v方式启动。

  1. -v host-dir:container-dir:[rw|wo]

-v 容器目录表示挂载一个容器目录为匿名数据卷,类似与dockerfile 中的VOLUME 参数,主要用于数据卷容器。

-v 本地目录:容器目录 如果不存在,Docker 会自动在主机上创建该目录。必须是绝对路径
rw|ro:用于控制volume的读写权限

Hello world

创建挂在文件目录

  1. mkdir -p /webapp/html

在/webapp/html目录地下 创建index.html文件

  1. <html>
  2. <head><title>hello docker</title></head>
  3. <body>
  4. <h1 style="text-align:center" >welcome to nginx </h1>
  5. </body>
  6. </html>
  1. docker run -d -p 8088:80 -v /webapp/html:/usr/share/nginx/html nginx

浏览器中输入http:ip:8088
图片.png查看当前nginx 容器中的文件,和我们在宿主机上的文件是一个的

  1. $docker ps
  2. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  3. a1a65fd1282a nginx "nginx -g 'daemon of…" 16 hours ago Up 16 hours 0.0.0.0:8088->80/tcp agitated_moser
  4. $docker exec -it a1a65fd1282a /bin/sh
  5. # cat /usr/share/nginx/html/index.html
  6. <html>
  7. <head><title>hello docker</title></head>
  8. <body>
  9. <h1 style="text-align:center" >welcome to nginx </h1>
  10. </body>
  11. </html>

注意的是:使用bind Mounting方式做数据卷的映射时,首次docker run -v 运行,如果本机的文件夹是没有内容的,docker容器中的文件夹是有内容的,则本机的会覆盖dokcer容器中的,也就是容器中原本有内容的也会没有内容

  1. $docker run -d -v ~/docker/nginx/html:/usr/share/nginx/html -p 8088:80 --name nginx nginx
  2. eb5e354b83f4005d45454a8e3faac750436bc39aa23f8cc1ba7b8140acdf4d20

由于宿主机上~/docker/nginx/html 这个目录底下没得index.html 文件造成容器相应的目录的index消失

图片.png
查看容器目录,index.文件是不存在的

  1. docker exec -it nginx /bin/sh
  2. # cd /usr/share/nginx/html
  3. # ls

因此完整的bind 一定要保证文件是存在的

挂载案例

MySQL

  1. $ docker run -p 3306:3306 --name mysql \
  2. -v /usr/local/docker/mysql/conf:/etc/mysql \
  3. -v /usr/local/docker/mysql/logs:/var/log/mysql \
  4. -v /usr/local/docker/mysql/data:/var/lib/mysql \
  5. -e MYSQL_ROOT_PASSWORD=123456 \
  6. -d mysql:5.7
  7. 03209b03258f9e9d0cb88b7847f4b2b0b0db79517ff66df1d629106fcfc7c64d

参数说明

  • —name:容器名
  • -p:映射宿主主机端口
  • -v:挂载宿主目录到容器目录
  • -e:设置环境变量,此处指定root密码
  • -d:后台运行容器

查看当前启动的容器

  1. $ docker ps
  2. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  3. 03209b03258f mysql:5.7 "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:3306->3306/tcp, 33060/tcp mysql

登录Mysql,创建数据库test

  1. mysql root@127.0.0.1:(none)> show databases;
  2. +--------------------+
  3. | Database |
  4. +--------------------+
  5. | information_schema |
  6. | mysql |
  7. | performance_schema |
  8. | sys |
  9. +--------------------+
  10. 4 rows in set
  11. Time: 0.053s
  12. mysql root@127.0.0.1:(none)> CREATE DATABASE test
  13. Query OK, 1 row affected
  14. Time: 0.007s

删除MySQL数据库

  1. $ docker stop mysql
  2. $ docker rm mysql
  3. $ docker rmi mysql5.7

再次创建并允许容器

  1. sudo docker run --name mysql -p 3306:3306 -v ~/data/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7

登录MySQL 数据库 刚才我们创建的test数据库还是存在的

  1. mysql root@127.0.0.1:(none)> show databases;
  2. +--------------------+
  3. | Database |
  4. +--------------------+
  5. | information_schema |
  6. | mysql |
  7. | performance_schema |
  8. | sys |
  9. | test |
  10. +--------------------+
  11. 5 rows in set
  12. Time: 0.042s

Nginx

  1. $ docker run --name nginx -p 80:80 -v ~/docker-nginx/nginx.conf:/etc/nginx/nginx.conf -v ~/docker-nginx/log:/var/log/nginx -v ~/docker-nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf -d nginx

—name 给你启动的容器起个名字,以后可以使用这个名字启动或者停止容器
-p 映射端口,将docker宿主机的80端口和容器的80端口进行绑定
-v 挂载文件用的,第一个-v 表示将你本地的nginx.conf覆盖你要起启动的容器的nginx.conf文件,第二个表示将日志文件进行挂载,就是把nginx服务器的日志写到你docker宿主机的/home/docker-nginx/log/下面
第三个-v 表示的和第一个-v意思一样的。

volums

数据卷的本质其实依然是宿主操作系统上的一个目录,只不过这个目录存主要由Docker 管理。
volum在宿主机的目录地址在/var/lib/docker/volumes//_data/

hello world

创建一个reids容器

  1. docker run --name redis -p 6379:6379 -d redis
  2. a01c7e55204ba3520eba33bdbd036e2f5294cabbcc627fef042bb2c4608dfbd7

查看当前redis容器的详细信息docker inspect redis,下面是关于挂载数据卷的信息

  1. "Mounts": [
  2. {
  3. "Type": "volume",
  4. "Name": "0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943",
  5. "Source": "/var/lib/docker/volumes/0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943/_data",
  6. "Destination": "/data",
  7. "Driver": "local",
  8. "Mode": "",
  9. "RW": true,
  10. "Propagation": ""
  11. }
  12. ],

查看当前数据卷列表,和我们在reids中的名称是一致的

  1. $ docker volume ls
  2. DRIVER VOLUME NAME
  3. local 0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943

查看当前数据卷的详细信息和我们查看redis的详细信息是一样的

  1. docker inspect 0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943
  2. [
  3. {
  4. "CreatedAt": "2019-10-23T15:44:21+08:00",
  5. "Driver": "local",
  6. "Labels": null,
  7. "Mountpoint": "/var/lib/docker/volumes/0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943/_data",
  8. "Name": "0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943",
  9. "Options": null,
  10. "Scope": "local"
  11. }
  12. ]

进入宿主机上的当前目录底下创建test.txt

  1. # cd /var/lib/docker/volumes/0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943/_data
  2. # echo "hello redis">>test.txt

进入redis容器中,可以看到我们在宿主机上创建的test.txt文件

  1. # docker exec -it redis /bin/sh
  2. # cd /data
  3. # ls
  4. test.txt
  5. # cat test.txt
  6. hello redis

删除当前redis 容器,数据卷数据不会被删除

  1. # docker rm -f redis
  2. redis
  3. # docker volume ls
  4. DRIVER VOLUME NAME
  5. local 0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943

操作命令

  1. $ docker volume --help
  2. Commands:
  3. create Create a volume
  4. inspect Display detailed information on one or more volumes
  5. ls List volumes
  6. prune Remove all unused local volumes
  7. rm Remove one or more volumes

create 创建数据卷
inspect 显示数据卷的详细信息
ls 列出所有的数据卷
prune 删除所有未使用的 volumes,并且有 -f 选项
rm 删除一个或多个未使用的 volumes,并且有 -f 选项

创建

  1. $ docker volume create vol-test
  2. vol-test

查看

数据卷使用的驱动程序为默认的 “local”,表示数据卷使用宿主机的本地存储;数据卷的挂载点,默认是本机 /var/lib/docker/volumes 下的一个目录。

  1. $ docker volume ls
  2. DRIVER VOLUME NAME
  3. local vol-test

详细信息

  1. $ docker volume inspect vol-test
  2. [
  3. {
  4. "CreatedAt": "2019-10-20T08:17:42Z",
  5. "Driver": "local",
  6. "Labels": {},
  7. "Mountpoint": "/var/lib/docker/volumes/vol-test/_data",
  8. "Name": "vol-test",
  9. "Options": {},
  10. "Scope": "local"
  11. }
  12. ]

删除

  1. $ docker volume rm vol-test
  2. vol-test

数据卷 是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用下面的命令。

  1. docker rm -v 容器

对于无主的数据卷可能会占据很多空间,要清理请使用以下命令,但是一定要谨慎使用

  1. $ docker volume prune

自定义数据卷名称

但是默认创建的data volume name 的名字很长,使用起来很不方便。我们可以指定volume name具体含义的名称,如下面的格式所示:volume-name:表示卷名,如果该卷不存在,docker将自动创建。container-dir是容器内的目录

  1. -v volume-name:container-dir:[rw|wo]

创建名称是redis-data的数据卷的redis 容器

  1. $ docker run --name redis -v redis-data:/data -p 6379:6379 -d redis
  2. cba7e73789fd8a31a9191a1d5c3242107c4bb6f7d37b64c95273c024d6cf8dc5
  3. $ docker volume ls
  4. DRIVER VOLUME NAME
  5. local redis-data

进入当前的容器我们进行简单的数redis据操作

  1. $ docker exec -it redis /bin/sh
  2. # redis-cli
  3. 127.0.0.1:6379> SET hello world
  4. OK
  5. 127.0.0.1:6379> GET hello
  6. "world"
  7. 127.0.0.1:6379>

删除当前的reids

  1. docker rm $(docker stop redis)

重新挂载当前的数据卷 数据还依旧可以使用

  1. $ docker run -v redis-data:/data -p 6379:6379 -d redis
  2. 305c383be655df189884c5b1c32229a81e96fe051261c08c23f3953f7b5b0d53
  3. $ docker exec -it 305c383be6 /bin/sh
  4. # redis-cli
  5. 127.0.0.1:6379> GET hello
  6. "world"
  7. 127.0.0.1:6379>

数据的覆盖问题

  • 如果挂载一个空的数据卷到容器中的一个非空目录中,那么这个目录下的文件会被复制到数据卷中。
  • 如果挂载一个非空的数据卷到容器中的一个目录中,那么容器中的目录中会显示数据卷中的数据。如果原来容器中的目录中有数据,那么这些原始数据会被隐藏掉。

mount

上面我们讲到了使用 -v 选项来挂载存在容易混淆的问题,其主要原因是挂载的方式和配置随着 Docker 的不断发展日渐丰富,而 -v 选项的传参方式限制了它能使用的场景。
其实在 Docker 里为我们提供了一个相对支持丰富的挂载方式,也就是通过 --mount 这个选项配置挂载。

  1. $ sudo docker run -d --name webapp webapp:latest --mount 'type=volume,src=appdata,dst=/webapp/storage,volume-driver=local,volume-opt=type=nfs,volume-opt=device=<nfs-server>:<nfs-path>' webapp:latest

--mount 中,我们可以通过逗号分隔这种 CSV 格式来定义多个参数。其中,通过 type 我们可以定义挂载类型,其值可以是:bind,volume 或 tmpfs。另外,--mount 选项能够帮助我们实现集群挂载的定义,例如在这个例子中,我们挂载的来源是一个 NFS 目录。

Dockerfile中数据卷

在 Dockerfile 中我们可以使用 VOLUME 指令向容器添加数据卷:

  1. VOLUME /data

VOLUM 指令等于如下的效果

  1. -v container-dir:[rw|wo]

container-dir 表示容器内部对应的目录,如果该目录不存在,Docker 也会在容器内部创建该目录。

  1. $docker run -itd -v /test ubuntu:18.04 /bin/sh
  2. 30a55338ce6770d2de3c2224f4652faa9c2ec1630aa045636f22e490c27585e2
  3. $ docker docker exec -it 30a55338ce6 /bin/sh
  4. # cd /test
  5. # ls

通过查看inspect

  1. $docker inspect ubuntu

此时Source 和Destination形成对应关系

  1. "Mounts": [
  2. {
  3. "Type": "volume",
  4. "Name": "3e388391e560d903a8514722424514f9052ddff2789b2f83a0a9bb62ebfddac2",
  5. "Source": "/var/lib/docker/volumes/3e388391e560d903a8514722424514f9052ddff2789b2f83a0a9bb62ebfddac2/_data",
  6. "Destination": "/data",
  7. "Driver": "local",
  8. "Mode": "",
  9. "RW": true,
  10. "Propagation": ""
  11. }
  12. ],

在使用 docker build 命令生成镜像并且以该镜像启动容器时会挂载一个数据卷到 /data 目录。根据我们已知的数据覆盖规则,如果镜像中存在 /data 目录,这个目录中的内容将全部被复制到宿主机中对应的目录中,并且根据容器中的文件设置合适的权限和所有者。

  1. FROM ubuntu:18.04
  2. RUN mkdir /data && echo "hello world" >/data/test.txt
  3. VOLUME /data


  1. $ docker build ./ -t baxiang/ubuntu
  2. $ docker run -it baxiang/ubuntu /bin/sh
  3. # cat /data/test.txt
  4. hello world

在 Dockerfile 中使用 VOLUME 指令之后的代码,如果尝试对这个数据卷进行修改,这些修改都不会生效!下面是一个这样的例子:

  1. FROM ubuntu:18.04
  2. VOLUME /data
  3. RUN echo "hello world" >/data/test.txt

通过这个 Dockerfile 创建镜像并启动容器后,并且能够看到 /data 目录挂载的数据卷。但是 /data 目录内并没有文件 test.txt

  1. $ docker build ./ -t baxiang/ubuntu
  2. Step 1/3 : FROM ubuntu:18.04
  3. ---> cf0f3ca922e0
  4. Step 2/3 : VOLUME /data
  5. ---> Using cache
  6. ---> 6bf404b3f460
  7. Step 3/3 : RUN echo "hello world" >/data/test.txt
  8. ---> Running in 3fd4a530dc1d
  9. Removing intermediate container 3fd4a530dc1d
  10. ---> f6563e1f93dc
  11. Successfully built f6563e1f93dc
  12. Successfully tagged baxiang/ubuntu:latest
  13. $ docker run -it baxiang/ubuntu /bin/sh
  14. # ls /data
  15. # cat /data/test.txt
  16. cat: /data/test.txt: No such file or directo

要解释这个现象需要我们了解通过 Dockerfile 创建镜像的过程:
Dockerfile 中除了 FROM 指令的每一行都是基于上一行生成的临时镜像运行一个容器,执行一条指令并执行类似 docker commit 的命令得到一个新的镜像。这条类似 docker commit 的命令不会对挂载的数据卷进行保存。
所以上面的 Dockerfile 最后两行执行时,都会在一个临时的容器上挂载 /data,并对这个临时的数据卷进行操作,但是这一行指令执行并提交后,这个临时的数据卷并没有被保存。因而我们最终通过镜像创建的容器所挂载的数据卷是没有被最后两条指令操作过的。我们姑且叫它 “Dockerfile 中数据卷的初始化问题”。

bind mount与docker managed volum区别:
bind mount必须指明mount源,docker managed volum不需要指明mount源;
bind mount指明mount源,宿主机的目录会覆盖容器的目录,原有数据将被隐藏起来,
docker managed volum指向的是容器内已有目录,原有数据会被复制到 volume 中。
Docker—— Data - 图4

数据卷容器

数据卷容器就是创建一个纯数据容器,该容器单纯就是存储数据,然后其他容器启动时直接使用该数据容器。docker run —volumes-from

  1. $ docker create --name mysql-data -v /var/lib/mysql ubuntu:18.04
  2. 8bd45c28bbcf6b2b7b4ba97badfdfa689e3d3fdb0c021a8112ea62cb6cd7a445

docker inspect mysql-data

  1. "Mounts": [
  2. {
  3. "Type": "volume",
  4. "Name": "0e614c45c25944eecd71fc169d6f7870fc74da4de0eecb12f2aea1e39d11bfde",
  5. "Source": "/var/lib/docker/volumes/0e614c45c25944eecd71fc169d6f7870fc74da4de0eecb12f2aea1e39d11bfde/_data",
  6. "Destination": "/var/lib/mysql",
  7. "Driver": "local",
  8. "Mode": "",
  9. "RW": true,
  10. "Propagation": ""
  11. }
  12. ],
  1. docker run -d --name mysql57 -e MYSQL_ROOT_PASSWORD=123456 -p3306:3306 --volumes-from mysql-data mysql:5.7

对数据句进行操作

  1. $ mycli -uroot -h127.0.0.1
  2. Password:
  3. Version: 1.8.1
  4. Chat: https://gitter.im/dbcli/mycli
  5. Mail: https://groups.google.com/forum/#!forum/mycli-users
  6. Home: http://mycli.net
  7. Thanks to the contributor - Norbert Spichtig
  8. mysql root@127.0.0.1:(none)> create database test
  9. Query OK, 1 row affected
  10. Time: 0.003s
  11. mysql root@127.0.0.1:(none)> use test
  12. You are now connected to database "test" as user "root"
  13. Time: 0.001s
  14. mysql root@127.0.0.1:test> CREATE TABLE IF NOT EXISTS `book`(
  15. `id` INT UNSIGNED AUTO_INCREMENT,
  16. `title` VARCHAR(100) NOT NULL,
  17. `author` VARCHAR(40) NOT NULL,
  18. `submission_date` DATE,
  19. PRIMARY KEY ( `id` )
  20. )
  21. Query OK, 0 rows affected
  22. Time: 0.015s
  23. mysql root@127.0.0.1:test>

详细信息

  1. docker inspect mysql57
  2. "Mounts": [
  3. {
  4. "Type": "volume",
  5. "Name": "93f73b41a592391f36f062b0355a14be43dabd274a4a461e694b29895ecafe85",
  6. "Source": "/var/lib/docker/volumes/93f73b41a592391f36f062b0355a14be43dabd274a4a461e694b29895ecafe85/_data",
  7. "Destination": "/var/lib/mysql",
  8. "Driver": "local",
  9. "Mode": "",
  10. "RW": true,
  11. "Propagation": ""
  12. }
  13. ],

通过--rm 选项,我们可以让容器在停止后自动删除,而不需要我们再使用容器删除命令来删除它,这对于我们使用一些临时容器很有帮助

  1. docker run --rm --volumes-from mysql-data -v ~/mysql-backup:/backup ubuntu:18.04 tar cvf /backup/backup.tar /var/lib/mysql

在本地目录下

  1. cd ~/mysql-backup
  2. $ mysql-backup ls
  3. backup.tar
  1. mycli -uroot -h127.0.0.1
  2. Password:
  3. Version: 1.8.1
  4. Chat: https://gitter.im/dbcli/mycli
  5. Mail: https://groups.google.com/forum/#!forum/mycli-users
  6. Home: http://mycli.net
  7. Thanks to the contributor - Nathan Taggart
  8. mysql root@127.0.0.1:(none)> use test
  9. You are now connected to database "test" as user "root"
  10. Time: 0.003s
  11. mysql root@127.0.0.1:test> drop table book;
  12. You're about to run a destructive command.
  13. Do you want to proceed? (y/n): y
  14. Your call!
  15. Query OK, 0 rows affected
  16. Time: 0.011s
  17. mysql root@127.0.0.1:test> show tables;
  18. +----------------+
  19. | Tables_in_test |
  20. +----------------+
  21. 0 rows in set
  22. Time: 0.015s
  23. mysql root@127.0.0.1:test>

如果要恢复数据卷中的数据,我们也可以借助临时容器完成。

  1. docker run --rm --volumes-from mysql-data -v ~/mysql-backup:/backup ubuntu:18.04 tar xvf /backup/backup.tar -C /var/lib/mysql --strip-components 3

数据又恢复了

  1. $ mycli -uroot -h127.0.0.1
  2. Password:
  3. mysql 5.7.28
  4. mycli 1.19.0
  5. Chat: https://gitter.im/dbcli/mycli
  6. Mail: https://groups.google.com/forum/#!forum/mycli-users
  7. Home: http://mycli.net
  8. Thanks to the contributor - Mikhail Borisov
  9. mysql root@127.0.0.1:(none)> use test
  10. You are now connected to database "test" as user "root"
  11. Time: 0.001s
  12. mysql root@127.0.0.1:test> show tables;
  13. +----------------+
  14. | Tables_in_test |
  15. +----------------+
  16. | book |
  17. +----------------+
  18. 1 row in set
  19. Time: 0.016s
  20. mysql root@127.0.0.1:test>

参考

https://docs.docker.com/storage/
https://github.com/findsec-cn/docker/blob/master/5.Docker-Volume.md
https://www.cnblogs.com/sparkdev/p/8504050.html
https://www.jianshu.com/p/969106f8d816
https://www.jianshu.com/p/8b62b5518c04