概述
数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性:
- 数据卷可以在容器之间共享和重用
- 对数据卷的修改会立马生效
- 对数据卷的更新,不会影响镜像.
- 数据卷默认会一直存在,即使容器被删除
Docker 提供了三种适用于不同场景的文件系统挂载方式:Bind Mount、Volume 和 Tmpfs Mount。
- Bind Mount 能够直接将宿主操作系统中的目录和文件挂载到容器内的文件系统中,通过指定容器外的路径和容器内的路径,就可以形成挂载映射关系,在容器内外对文件的读写,都是相互可见的。
- Volume 也是从宿主操作系统中挂载目录到容器内,只不过这个挂载的目录由 Docker 进行管理,我们只需要指定容器内的目录,不需要关心具体挂载到了宿主操作系统中的哪里。
- Tmpfs Mount 支持挂载系统内存中的一部分到容器的文件系统里,不过由于内存和容器的特征,它的存储并不是持久的,其中的内容会随着容器的停止而消失。
bind mount
--volume , -v
bind mount类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的 数据卷
,bind Mounting通过docker run -v
方式启动。
-v host-dir:container-dir:[rw|wo]
-v 容器目录表示挂载一个容器目录为匿名数据卷,类似与dockerfile 中的VOLUME 参数,主要用于数据卷容器。
-v 本地目录:容器目录 如果不存在,Docker 会自动在主机上创建该目录。必须是绝对路径。
rw|ro:用于控制volume的读写权限
Hello world
创建挂在文件目录
mkdir -p /webapp/html
在/webapp/html目录地下 创建index.html文件
<html>
<head><title>hello docker</title></head>
<body>
<h1 style="text-align:center" >welcome to nginx </h1>
</body>
</html>
docker run -d -p 8088:80 -v /webapp/html:/usr/share/nginx/html nginx
浏览器中输入http:ip:8088
查看当前nginx 容器中的文件,和我们在宿主机上的文件是一个的
$docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1a65fd1282a nginx "nginx -g 'daemon of…" 16 hours ago Up 16 hours 0.0.0.0:8088->80/tcp agitated_moser
$docker exec -it a1a65fd1282a /bin/sh
# cat /usr/share/nginx/html/index.html
<html>
<head><title>hello docker</title></head>
<body>
<h1 style="text-align:center" >welcome to nginx </h1>
</body>
</html>
注意的是:使用bind Mounting方式做数据卷的映射时,首次docker run -v
运行,如果本机的文件夹是没有内容的,docker容器中的文件夹是有内容的,则本机的会覆盖dokcer容器中的,也就是容器中原本有内容的也会没有内容
$docker run -d -v ~/docker/nginx/html:/usr/share/nginx/html -p 8088:80 --name nginx nginx
eb5e354b83f4005d45454a8e3faac750436bc39aa23f8cc1ba7b8140acdf4d20
由于宿主机上~/docker/nginx/html 这个目录底下没得index.html 文件造成容器相应的目录的index消失
查看容器目录,index.文件是不存在的
docker exec -it nginx /bin/sh
# cd /usr/share/nginx/html
# ls
因此完整的bind 一定要保证文件是存在的
挂载案例
MySQL
$ docker run -p 3306:3306 --name mysql \
-v /usr/local/docker/mysql/conf:/etc/mysql \
-v /usr/local/docker/mysql/logs:/var/log/mysql \
-v /usr/local/docker/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
-d mysql:5.7
03209b03258f9e9d0cb88b7847f4b2b0b0db79517ff66df1d629106fcfc7c64d
参数说明
- —name:容器名
- -p:映射宿主主机端口
- -v:挂载宿主目录到容器目录
- -e:设置环境变量,此处指定root密码
- -d:后台运行容器
查看当前启动的容器
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
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
mysql root@127.0.0.1:(none)> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set
Time: 0.053s
mysql root@127.0.0.1:(none)> CREATE DATABASE test
Query OK, 1 row affected
Time: 0.007s
删除MySQL数据库
$ docker stop mysql
$ docker rm mysql
$ docker rmi mysql:5.7
再次创建并允许容器
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数据库还是存在的
mysql root@127.0.0.1:(none)> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+
5 rows in set
Time: 0.042s
Nginx
$ 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/
hello world
创建一个reids容器
docker run --name redis -p 6379:6379 -d redis
a01c7e55204ba3520eba33bdbd036e2f5294cabbcc627fef042bb2c4608dfbd7
查看当前redis容器的详细信息docker inspect redis,下面是关于挂载数据卷的信息
"Mounts": [
{
"Type": "volume",
"Name": "0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943",
"Source": "/var/lib/docker/volumes/0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943/_data",
"Destination": "/data",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
查看当前数据卷列表,和我们在reids中的名称是一致的
$ docker volume ls
DRIVER VOLUME NAME
local 0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943
查看当前数据卷的详细信息和我们查看redis的详细信息是一样的
docker inspect 0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943
[
{
"CreatedAt": "2019-10-23T15:44:21+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943/_data",
"Name": "0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943",
"Options": null,
"Scope": "local"
}
]
进入宿主机上的当前目录底下创建test.txt
# cd /var/lib/docker/volumes/0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943/_data
# echo "hello redis">>test.txt
进入redis容器中,可以看到我们在宿主机上创建的test.txt文件
# docker exec -it redis /bin/sh
# cd /data
# ls
test.txt
# cat test.txt
hello redis
删除当前redis 容器,数据卷数据不会被删除
# docker rm -f redis
redis
# docker volume ls
DRIVER VOLUME NAME
local 0c891ee3b5f78a37735a84d5e736672e04e951c6101d94f6a0f8cd2dc5e49943
操作命令
$ docker volume --help
Commands:
create Create a volume
inspect Display detailed information on one or more volumes
ls List volumes
prune Remove all unused local volumes
rm Remove one or more volumes
create 创建数据卷
inspect 显示数据卷的详细信息
ls 列出所有的数据卷
prune 删除所有未使用的 volumes,并且有 -f 选项
rm 删除一个或多个未使用的 volumes,并且有 -f 选项
创建
$ docker volume create vol-test
vol-test
查看
数据卷使用的驱动程序为默认的 “local”,表示数据卷使用宿主机的本地存储;数据卷的挂载点,默认是本机 /var/lib/docker/volumes 下的一个目录。
$ docker volume ls
DRIVER VOLUME NAME
local vol-test
详细信息
$ docker volume inspect vol-test
[
{
"CreatedAt": "2019-10-20T08:17:42Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/vol-test/_data",
"Name": "vol-test",
"Options": {},
"Scope": "local"
}
]
删除
$ docker volume rm vol-test
vol-test
数据卷 是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用下面的命令。
docker rm -v 容器
对于无主的数据卷可能会占据很多空间,要清理请使用以下命令,但是一定要谨慎使用
$ docker volume prune
自定义数据卷名称
但是默认创建的data volume name 的名字很长,使用起来很不方便。我们可以指定volume name具体含义的名称,如下面的格式所示:volume-name:表示卷名,如果该卷不存在,docker将自动创建。container-dir是容器内的目录
-v volume-name:container-dir:[rw|wo]
创建名称是redis-data的数据卷的redis 容器
$ docker run --name redis -v redis-data:/data -p 6379:6379 -d redis
cba7e73789fd8a31a9191a1d5c3242107c4bb6f7d37b64c95273c024d6cf8dc5
$ docker volume ls
DRIVER VOLUME NAME
local redis-data
进入当前的容器我们进行简单的数redis据操作
$ docker exec -it redis /bin/sh
# redis-cli
127.0.0.1:6379> SET hello world
OK
127.0.0.1:6379> GET hello
"world"
127.0.0.1:6379>
删除当前的reids
docker rm $(docker stop redis)
重新挂载当前的数据卷 数据还依旧可以使用
$ docker run -v redis-data:/data -p 6379:6379 -d redis
305c383be655df189884c5b1c32229a81e96fe051261c08c23f3953f7b5b0d53
$ docker exec -it 305c383be6 /bin/sh
# redis-cli
127.0.0.1:6379> GET hello
"world"
127.0.0.1:6379>
数据的覆盖问题
- 如果挂载一个空的数据卷到容器中的一个非空目录中,那么这个目录下的文件会被复制到数据卷中。
- 如果挂载一个非空的数据卷到容器中的一个目录中,那么容器中的目录中会显示数据卷中的数据。如果原来容器中的目录中有数据,那么这些原始数据会被隐藏掉。
mount
上面我们讲到了使用 -v
选项来挂载存在容易混淆的问题,其主要原因是挂载的方式和配置随着 Docker 的不断发展日渐丰富,而 -v
选项的传参方式限制了它能使用的场景。
其实在 Docker 里为我们提供了一个相对支持丰富的挂载方式,也就是通过 --mount
这个选项配置挂载。
$ 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 指令向容器添加数据卷:
VOLUME /data
VOLUM 指令等于如下的效果
-v container-dir:[rw|wo]
container-dir 表示容器内部对应的目录,如果该目录不存在,Docker 也会在容器内部创建该目录。
$docker run -itd -v /test ubuntu:18.04 /bin/sh
30a55338ce6770d2de3c2224f4652faa9c2ec1630aa045636f22e490c27585e2
$ docker docker exec -it 30a55338ce6 /bin/sh
# cd /test
# ls
通过查看inspect
$docker inspect ubuntu
此时Source 和Destination形成对应关系
"Mounts": [
{
"Type": "volume",
"Name": "3e388391e560d903a8514722424514f9052ddff2789b2f83a0a9bb62ebfddac2",
"Source": "/var/lib/docker/volumes/3e388391e560d903a8514722424514f9052ddff2789b2f83a0a9bb62ebfddac2/_data",
"Destination": "/data",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
在使用 docker build 命令生成镜像并且以该镜像启动容器时会挂载一个数据卷到 /data 目录。根据我们已知的数据覆盖规则,如果镜像中存在 /data 目录,这个目录中的内容将全部被复制到宿主机中对应的目录中,并且根据容器中的文件设置合适的权限和所有者。
FROM ubuntu:18.04
RUN mkdir /data && echo "hello world" >/data/test.txt
VOLUME /data
$ docker build ./ -t baxiang/ubuntu
$ docker run -it baxiang/ubuntu /bin/sh
# cat /data/test.txt
hello world
在 Dockerfile 中使用 VOLUME 指令之后的代码,如果尝试对这个数据卷进行修改,这些修改都不会生效!下面是一个这样的例子:
FROM ubuntu:18.04
VOLUME /data
RUN echo "hello world" >/data/test.txt
通过这个 Dockerfile 创建镜像并启动容器后,并且能够看到 /data 目录挂载的数据卷。但是 /data 目录内并没有文件 test.txt
$ docker build ./ -t baxiang/ubuntu
Step 1/3 : FROM ubuntu:18.04
---> cf0f3ca922e0
Step 2/3 : VOLUME /data
---> Using cache
---> 6bf404b3f460
Step 3/3 : RUN echo "hello world" >/data/test.txt
---> Running in 3fd4a530dc1d
Removing intermediate container 3fd4a530dc1d
---> f6563e1f93dc
Successfully built f6563e1f93dc
Successfully tagged baxiang/ubuntu:latest
$ docker run -it baxiang/ubuntu /bin/sh
# ls /data
# cat /data/test.txt
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 run —volumes-from
$ docker create --name mysql-data -v /var/lib/mysql ubuntu:18.04
8bd45c28bbcf6b2b7b4ba97badfdfa689e3d3fdb0c021a8112ea62cb6cd7a445
docker inspect mysql-data
"Mounts": [
{
"Type": "volume",
"Name": "0e614c45c25944eecd71fc169d6f7870fc74da4de0eecb12f2aea1e39d11bfde",
"Source": "/var/lib/docker/volumes/0e614c45c25944eecd71fc169d6f7870fc74da4de0eecb12f2aea1e39d11bfde/_data",
"Destination": "/var/lib/mysql",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
docker run -d --name mysql57 -e MYSQL_ROOT_PASSWORD=123456 -p3306:3306 --volumes-from mysql-data mysql:5.7
对数据句进行操作
$ mycli -uroot -h127.0.0.1
Password:
Version: 1.8.1
Chat: https://gitter.im/dbcli/mycli
Mail: https://groups.google.com/forum/#!forum/mycli-users
Home: http://mycli.net
Thanks to the contributor - Norbert Spichtig
mysql root@127.0.0.1:(none)> create database test
Query OK, 1 row affected
Time: 0.003s
mysql root@127.0.0.1:(none)> use test
You are now connected to database "test" as user "root"
Time: 0.001s
mysql root@127.0.0.1:test> CREATE TABLE IF NOT EXISTS `book`(
`id` INT UNSIGNED AUTO_INCREMENT,
`title` VARCHAR(100) NOT NULL,
`author` VARCHAR(40) NOT NULL,
`submission_date` DATE,
PRIMARY KEY ( `id` )
)
Query OK, 0 rows affected
Time: 0.015s
mysql root@127.0.0.1:test>
详细信息
docker inspect mysql57
"Mounts": [
{
"Type": "volume",
"Name": "93f73b41a592391f36f062b0355a14be43dabd274a4a461e694b29895ecafe85",
"Source": "/var/lib/docker/volumes/93f73b41a592391f36f062b0355a14be43dabd274a4a461e694b29895ecafe85/_data",
"Destination": "/var/lib/mysql",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
通过--rm
选项,我们可以让容器在停止后自动删除,而不需要我们再使用容器删除命令来删除它,这对于我们使用一些临时容器很有帮助
docker run --rm --volumes-from mysql-data -v ~/mysql-backup:/backup ubuntu:18.04 tar cvf /backup/backup.tar /var/lib/mysql
在本地目录下
cd ~/mysql-backup
$ mysql-backup ls
backup.tar
mycli -uroot -h127.0.0.1
Password:
Version: 1.8.1
Chat: https://gitter.im/dbcli/mycli
Mail: https://groups.google.com/forum/#!forum/mycli-users
Home: http://mycli.net
Thanks to the contributor - Nathan Taggart
mysql root@127.0.0.1:(none)> use test
You are now connected to database "test" as user "root"
Time: 0.003s
mysql root@127.0.0.1:test> drop table book;
You're about to run a destructive command.
Do you want to proceed? (y/n): y
Your call!
Query OK, 0 rows affected
Time: 0.011s
mysql root@127.0.0.1:test> show tables;
+----------------+
| Tables_in_test |
+----------------+
0 rows in set
Time: 0.015s
mysql root@127.0.0.1:test>
如果要恢复数据卷中的数据,我们也可以借助临时容器完成。
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
数据又恢复了
$ mycli -uroot -h127.0.0.1
Password:
mysql 5.7.28
mycli 1.19.0
Chat: https://gitter.im/dbcli/mycli
Mail: https://groups.google.com/forum/#!forum/mycli-users
Home: http://mycli.net
Thanks to the contributor - Mikhail Borisov
mysql root@127.0.0.1:(none)> use test
You are now connected to database "test" as user "root"
Time: 0.001s
mysql root@127.0.0.1:test> show tables;
+----------------+
| Tables_in_test |
+----------------+
| book |
+----------------+
1 row in set
Time: 0.016s
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