Docker的镜像是由一系列的只读层组合而来,当启动一个容器时,Docker加载镜像的所有只读层,并在最上层加入一个读写层。这个设计使得Docker可以提高镜像构建、存储和分发的效率,节省了时间和存储空间,然而也存在一些问题:
为了解决这些问题,Docker引入了数据卷(volume)机制。Volume 是存在于一个或多个容器中的特定文件或文件夹,这个目录以独立于联合文件系统的形式在宿主机中存在,并为数据的共享和持久化提供便利。
Usage: docker volume COMMAND
Manage volumes
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
创建一个名为 vol_simple的存储卷:
docker volume create --name vol_simple
查看数据卷的信息:
docker volume inspect vol_simple
示例:
king@king-server:~$ docker volume inspect vol_simple
[
{
"CreatedAt": "2024-01-27T11:37:11Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/vol_simple/_data",
"Name": "vol_simple",
"Options": null,
"Scope": "local"
}
]
Docker run 和 docker create 通过指定-v参数 可以为容器挂载一个数据卷。
#没有指定volume卷,只指定了容器中的挂载点/data
docker run -d -it -v /data ubuntu /bin/bash
# 查看随机创建的volume位置,查看mounts的内容
docker inspect {ID}
# 创建一个指定名字的volume ,并挂载到容器中的/data目录
docker run -d -v vol_simple:/data ubuntu
示例:
fly@fly:~$ docker run -dit -v vol_simple:/data ubuntu
e86204b7a02ccb62e6cf4f00b7246ac0d70aadf0d9915df5418ad73f0bf8f00a
fly@fly:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e86204b7a02c ubuntu "bash" 4 seconds ago Up 2 seconds charming_bhaskara
fly@fly:~$ docker inspect charming_bhaskara
...
"Mounts": [
{
"Type": "volume",
"Name": "vol_simple",
"Source": "/var/lib/docker/volumes/vol_simple/_data",
"Destination": "/data",
"Driver": "local",
"Mode": "z",
"RW": true,
"Propagation": ""
}
],
...
测试数据持久性:
fly@fly:~$ docker exec -it charming_bhaskara bash
root@e86204b7a02c:/# ls
bin boot data dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
root@e86204b7a02c:/# echo abcd >> /data/1.txt
root@e86204b7a02c:/# cat data/1.txt
abcd
root@e86204b7a02c:/# exit
exit
fly@fly:~$ docker stop charming_bhaskara
charming_bhaskara
fly@fly:~$ docker rm charming_bhaskara
charming_bhaskara
fly@fly:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
22634e72e7a3 registry:2 "/entrypoint.sh /etc…" 3 weeks ago Up 21 minutes 0.0.0.0:5000->5000/tcp, :::5000->5000/tcp registry
fly@fly:~$ sudo cat /var/lib/docker/volumes/vol_simple/_data/1.txt
abcd
使用docker run 或者 docker create 创建新容器是,可以使用-v标签为容器添加volume。可以将自行创建或者有Docker创建的volume挂载到容器中,也可以将宿主机上的目录或者文件作为volume挂载到容器中。
将宿主机中指定目录作为volume挂载到容器中的/data目录下,文件夹必须使用绝对路径,如果宿主机中不存在指定的目录,则会创建一个空文件夹;如果宿主机文件夹已经存在,容器可以通过访问挂载点/data 从而访问宿主机文件夹中的所有内容。如果容器下原本已经存在/data文件夹,且不为空,那么容器中文件夹下原有的内容将被隐藏,来保持与宿主机中的文件夹一致。
挂载方式:
docker volume create --name vol_simple
docker run -d -v vol_simple:/data ubuntu /bin/bash
docker run -d -v /data ubuntu /bin/bash
docker run -d -v $HOME/data:/data ubuntu /bin/bash
挂载权限,ro表示只读,rw表示读写,默认为rw:
# ro表示只读,rw表示读写,默认为rw
docker run -it -v $HOME/data:/data:ro ubuntu /bin/bash
# 通过修改文件验证,因为是只读,所以下面会报错
echo 123456abcdefg >> /data/test
同时挂载多个数据卷:
docker run -it -v $HOME/data:/data:ro -v /data1 -v /data2 ubuntu /bin/bash
使用dockerfile VOLUME 指令,与docker run -v 不同的是,dockerfile指令不能挂载主机中指定的文件夹。这时为了保证Dockerfile的可移植性,因为不能保证所有的宿主机都有对应的文件夹。如果镜像中存才/data文件夹,这个文件夹中的内容将全部被复制到宿主机上对应文件夹中,并且根据容器中的文件设置合适的权限和所有者。
注意:
在Dockerfile中使用VOLUME指令后的代码,如果尝试对这个volume进行修改,这些修改都不会生效。因为:volume 是独立于rootfs的存储,镜像构建过程,每一层类似docker commit 提交临时镜像为镜像层,docker commit不会对挂载的volume进行保存。
示例1:
VOLUME指定了挂载点之后,再对挂载点进行操作,由于docker commit 提交临时镜像不会对volume进行保存,所以该data下的file并没有被保存,更不会同步到宿主机。
FROM ubuntu
RUN useradd fly2
VOLUME /data
RUN touch /data/file
RUN chown -R fly2:fly2 /data
构建:
docker build -t test1:v1 -f Dockerfile .
docker run -d -it test1:v1 /bin/bash
查看挂载点信息 :
docker inspect 容器ID
查看宿主机存储卷目录 :
sudo ls 宿主机存储卷目录
示例二:
由于挂载volume时,/data目录已经存在,所以/data中的文件以及它们的权限和所有者设置都会被复制到volume中。
FROM ubuntu
RUN useradd fly2
RUN mkdir /data && touch /data/file
RUN chown -R fly2:fly2 /data
VOLUME /data
构建:
docker build -t test1:v2 -f Dockerfile1 .
docker run -d -it test1:v2 /bin/bash
查看挂载点信息:
docker inspect 容器ID
查看宿主机存储卷目录 :
sudo ls 宿主机存储卷目录
示例三:
通过CMD和ENTRYPOINT指令,在容器启动时执行挂载点下文件的初始化。
FROM ubuntu
RUN useradd fly2
VOLUME /data
CMD touch /data/file && chown -R fly2:fly2 /data
说明:
在dockerfile中使用数据卷时,如果我们仅仅想在启动容器时有这样一个空的数据卷,那么dockerfile中关于数据卷这块怎么写都行;如果我们确确实实需要初始化一些内容到我们的数据卷里头,我们先得将内容提交到镜像(比如先创建文件),然后再去创建数据卷。
在使用docker run 或docker create创建新容器时,可以使用–volumes-from 标签使得容器与已有容器共享volume。可以使用多个–volumes-from标签,使得容器与多个已有容器共享volume。
一个容器挂载了一个volume,即使这个容器停止运行,该volume仍然存在,其他容器也可以使用–volumes-from与这个容器共享volume。
共享数据卷作用:
如果有一些数据,比如配置文件、数据文件等,要在多个容器之间共享,一种常见的做法是创建一个数据容器,其他的容器与之共享volume。
示例:创建一个带数据卷的容器。
创建目录和数据卷:
mkdir $HOME/data
mkdir $HOME/readOlny_data
docker volume create vol_simple
docker volume create vol_simple1
创建一个数据容器 包含已创建数据卷,只读数据卷,私有数据卷等 :
docker run -d -it -v vol_simple:/vol_simple -v $HOME/data:/data -v $HOME/readOlny_data:/readOlny_data:ro --name share_data ubuntu /bin/bash
docker run -d -it -v vol_simple1:/vol_simple1 --name share_data1 ubuntu /bin/bash
进入容器查看 :
docker exec -it 容器ID /bin/bash
运行新的容器通过数据容器共享数据卷 容器名:
volume_from1 docker run -d -it --volumes-from share_data --volumes-from share_data1 --name volume_from1 ubuntu /bin/bash
运行新的容器通过数据容器共享数据卷 容器名:
volume_from2 docker run -d -it --volumes-from share_data --volumes-from share_data1 --name volume_from2 ubuntu /bin/bash
说明:
如果创建容器时从容器中挂载了volume,在/var/lib/docker/volumes下会生成与volume对应的目录(可使用docker inspect 命令查看容器信息找到对应的信息)。
删除volume的三种方式:
注意:
volume 作为数据的载体,在很多情况下需要对其中的数据进行备份、迁移,或是从已有数据恢复。
最简单的备份还原的方式:
用–volumes-from实现备份与还原:
docker run --rm --volumes-from share_data -v $(pwd):/backup ubuntu tar cvf /backup/data.tar /data
docker run -d -it --name vol_bck -v /data ubuntu /bin/bash
docker run --rm --volumes-from vol_bck -v $(pwd):/backup ubuntu tar xvf /backup/data.tar -C /