任何有意义的应用都会产生或者使用一些数据。而容器本身是无状态的。我们使用数据卷(volumes)来解决这个问题。数据卷允许容器使用,产生和修改数据。数据卷比容器拥有更高等级的生命周期,当使用volume的容器结束时,volume依然存在。
1. 创建和挂载数据卷
1.1 修改容器内数据
在正式开始介绍数据卷之前,我们先看看直接在容器内进行数据修改操作会发生什么。
在容器内新增一个文件:
docker container run --name demo \
alpine /bin/sh -c 'echo "This is a test" > sample.txt'
容器与基础镜像进行比较:
docker container diff demo
结果类似如下:
A /sample.txt
当停止并移除这个容器后,你新建的sample.txt文件也会随之删除,再也无法继续使用。
1.2 创建数据卷
如下命令创建一个名为my-data的数据卷:
docker volume create my-data
默认的数据卷驱动称之为本地驱动,它将数据存储在本机的文件系统上。如果你需要了解数据卷的详细信息,可使用docker volume inspect
命令进行核查。
docker volume inspect my-data
得到类似如下结果:
[
{
"CreatedAt": "2018-10-29T13:58:22+08:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-data/_data",
"Name": "my-data",
"Options": {},
"Scope": "local"
}
]
tips:docker还支持以插件形式使用的,第三方的一些数据卷驱动,例如:云存储,NFS,SDN(软件定义网络)存储。只需要在创建数据卷时,使用--driver参数进行指定即可。
1.3 挂载数据卷
当我们创建了数据卷之后,就可以挂载使用它了。
docker container run --name test -it \
> -v my-data:/data alpine /bin/sh
启动并进入容器后,执行如下命令,在容器内新建文件:
/ # cd /data
/data # echo "some data" > data.txt
/data # echo "some more data" > data1.txt
/data # exit
回到主机,我们在/var/lib/docker/volumes/my-data/_data/
目录下,可以看到:
[root@node2 ~]# ll /var/lib/docker/volumes/my-data/_data/
total 8
-rw-r--r-- 1 root root 15 Oct 29 14:13 data1.txt
-rw-r--r-- 1 root root 10 Oct 29 14:13 data.txt
[root@node2 ~]# cat /var/lib/docker/volumes/my-data/_data/data.txt
some data
[root@node2 ~]# cat /var/lib/docker/volumes/my-data/_data/data1.txt
some more data
接下来,我们直接在主机的数据卷目录下创建文件,然后新启动一个容器,挂载这个数据卷,看看是什么效果。
[root@node2 ~]# cd /var/lib/docker/volumes/my-data/_data/
[root@node2 _data]# echo "This file we create on the host" > host_data.txt
[root@node2 _data]# docker container rm test
test
[root@node2 _data]# docker container run --name test2 -it \
> -v my-data:/app/data \
> centos:7 /bin/bash
Unable to find image 'centos:7' locally
7: Pulling from library/centos
aeb7866da422: Pull complete
Digest: sha256:67dad89757a55bfdfabec8abd0e22f8c7c12a1856514726470228063ed86593b
Status: Downloaded newer image for centos:7
[root@9015fe11c4dd /]# ls /app/data
data.txt data1.txt host_data.txt
[root@9015fe11c4dd /]# cat /app/data/host_data.txt
This file we create on the host
1.4 删除数据卷
我们可以使用docker volume rm
来删除数据卷。特别提示:由于该操作是不可逆的,当你确认数据卷已经进行了备份,或者该数据卷确实没有其他用途的时候,你可以进行删除操作。但是,如果该数据卷如果还在使用中,是无法进行删除的。
[root@node2 _data]# docker volume rm my-data
Error response from daemon: remove my-data: volume is in use - [9015fe11c4dd6450b2587e5b36979b64796541e7d67656003d8decae1fcc735e]
[root@node2 _data]# docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9015fe11c4dd centos:7 "/bin/bash" 8 minutes ago Exited (0) 4 minutes ago test2
78a0c2a1ac94 alpine "/bin/sh" 3 days ago Exited (0) 3 days ago sample
ce3d8b9082c5 alpine "/bin/sh" 3 days ago Exited (0) 3 days ago upbeat_kirch
48a55ac2ee94 nginx:alpine "nginx -g 'daemon of…" 3 days ago Up 3 days 0.0.0.0:8080->80/tcp nginx-test
0f8cf054c73d alpine "echo 'hello world'" 3 days ago Exited (0) 3 days ago angry_colden
f5ed73fec895 hello-world "/hello" 3 days ago Exited (0) 3 days ago dreamy_leakey
[root@node2 _data]# docker container rm 9015fe11c4dd
9015fe11c4dd
[root@node2 _data]# docker volume rm my-data
my-data
2. 在容器之间分享数据
有些时候,容器之间是需要进行数据交互的。有可能A容器产生的数据,将用于给B容器进行读取使用,但为了避免冲突(同时对文件的修改),我们可以在挂载数据卷的时候设置一些读写权限。
[root@node2 ~]# docker volume create shared-data
shared-data
# 创建一个写文件的容器
[root@node2 ~]# docker container run --name writer -it \
> -v shared-data:/data \
> alpine /bin/sh
/ # cd /data
/data # touch data-from-writer.txt
/data # ls
data-from-writer.txt
# 创建一个只能读的容器,并挂载shared-data数据卷
[root@node2 ~]# docker container run --name only-reader -it \
> -v shared-data:/data:ro \
> centos:7 /bin/bash
[root@cf058c0af6af /]# cd /data/
[root@cf058c0af6af data]# touch data.txt
touch: cannot touch 'data.txt': Read-only file system
3. 使用主机目录进行挂载
除了数据卷,我们还可以将主机上的某个目录直接挂载到容器内,如下:
docker container run -d -v /my-web:/usr/share/nginx/html -p 8080:80 nginx:alpine
4. 在Dockerfile中定义数据卷
对于一些应用,例如数据库,我们肯定是需要进行数据持久化的,但是面对不同的使用者和不同的使用环境,我们的官方镜像是如何在面对不同环境时,主动的去定义一个数据卷呢?答案是:在Dockerfile中就定义好数据卷,这样生成的镜像在启动时,就会自动的创建数据卷。
# 列出目前已有的volume
[root@node2 ~]# docker volume ls
DRIVER VOLUME NAME
local shared-data
# 创建一个mongo容器
[root@node2 ~]# docker container run -d --name my-mongo mongo:3.7
e65ac72b9c3fc7abdcc05121b55c87543baccc3b27bf17be6ad0ec1212211141
# 查看自动添加的volume
[root@node2 ~]# docker volume ls
DRIVER VOLUME NAME
local 065a116f6ef50622aa3e1f674f81cc61d13e4618b6f726b5fd77f4813fcc5e84
local 10242f5b75f1ba88e1e2abdbb2cc874887f4181353ea62ca6f2c6735c32b0e7b
local shared-data
我们也可以使用docker image inspect
命令来审查镜像的数据卷相关配置。
[root@node2 ~]# docker image inspect \
> --format='{{json .ContainerConfig.Volumes}}' \
> mongo:3.7 | jq
{
"/data/configdb": {},
"/data/db": {}
}
上面的结果显示,mongo:3.7这个image会创建两个volume。
我们也可以通过docker container inspect
命令来审查当前运行容器的数据卷挂载情况。
[root@node2 ~]# docker container inspect --format '{{json .Mounts}}' my-mongo | jq
[
{
"Type": "volume",
"Name": "065a116f6ef50622aa3e1f674f81cc61d13e4618b6f726b5fd77f4813fcc5e84",
"Source": "/var/lib/docker/volumes/065a116f6ef50622aa3e1f674f81cc61d13e4618b6f726b5fd77f4813fcc5e84/_data",
"Destination": "/data/configdb",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
},
{
"Type": "volume",
"Name": "10242f5b75f1ba88e1e2abdbb2cc874887f4181353ea62ca6f2c6735c32b0e7b",
"Source": "/var/lib/docker/volumes/10242f5b75f1ba88e1e2abdbb2cc874887f4181353ea62ca6f2c6735c32b0e7b/_data",
"Destination": "/data/db",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
]
5. 关于使用volume还是bind mounts(本机目录)的建议
首先请参考这几篇官方文章:
Manage data in Docker
Use volumes
Use bind mounts
以下内容为谷歌翻译:
卷的好处:
- 在多个运行容器之间共享数据。如果未显式创建它,则会在第一次将其装入容器时创建卷。当该容器停止或被移除时,该卷仍然存在。多个容器可以同时安装相同的卷,可以是
读写也可以是只读。仅在您明确删除卷时才会删除卷。 - 当Docker主机不能保证具有给定的目录或文件结构时。Volumes可帮助您将Docker主机的配置与容器运行时分离。
- 如果要将容器的数据存储在远程主机或云提供程序上,而不是本地存储。
- 当您需要备份,还原或将数据从一个Docker主机迁移到另一个Docker主机时,卷是更好的选择。您可以使用卷停止容器,然后备份卷的目录(例如/var/lib/docker/volumes/<volume-name>)。
绑定挂载的好处:
通常,您应该尽可能使用卷。绑定适用于以下类型的用例:
- 将配置文件从主机共享到容器。这就是Docker默认通过/etc/resolv.conf从主机安装到每个容器中来为容器提供DNS解析的方式 。
- 在Docker主机上的开发环境和容器之间共享源代码或构建工件。例如,您可以将Maven target/ 目录安装到容器中,每次在Docker主机上构建Maven项目时,容器都可以访问重建的工件。
如果您以这种方式使用Docker进行开发,您的生产Dockerfile会将生产就绪工件直接复制到映像中,而不是依赖于绑定装载。 - 当Docker主机的文件或目录结构保证与容器所需的绑定装载一致时。
6. 关于使用-v参数还是--mount参数的建议
docker新用户,我们建议使用--mount,但是这个参数只能在docker 17.06版本以后才可以使用。
--mount参数使用如下:
$ docker service create \
--mount 'type=volume,src=<VOLUME-NAME>,dst=<CONTAINER-PATH>,volume-driver=local,volume-opt=type=nfs,volume-opt=device=<nfs-server>:<nfs-path>,"volume-opt=o=addr=<nfs-address>,vers=4,soft,timeo=180,bg,tcp,rw"'
--name myservice \
<IMAGE>
--mount与-v的区别:
As opposed to bind mounts, all options for volumes are available for both --mount and -v flags.
When using volumes with services, only --mount is supported.