2-5-Docker数据卷管理
1. 存储卷介绍
1.1 存储卷基础
Docker镜像由多个文件系统 ( 只读层 ) 叠加而成。Docker 会加载只读镜像层 , 并在镜像栈顶部添加一个读写层。当运行容器后 , 如果修改了某个已存在的文件 , 那么该文件将会从下面的只读层复制到上面的读写层 , 同时 , 该文件在只读层中仍然存在 , 此即“写时复制(COW)”机制。当我们删除 Docker容器 , 并通过镜像重新启动容器时 , 之前的更改的文件将会丢失。
于是 , Docker提出了Volume数据卷的概念。
简单来说 , 数据卷是一个可供一个或多个容器使用的特殊目录 , 用于持久化数据以及共享容器间的数据 , 它以正常的文件或目录的形式存在于宿主机上。 另外 , 其生命周期独立于容器的生命周期 , 即当你删除容器时 , 数据卷并不会被删除。

描述 : 如果一个文件在最底层是可见的 , 如果在layer1上标记为删除 , 最高的层是用户看到的Layer2的层 , 在layer0上的文件 , 在layer2上可以删除 , 但是只是标记删除 , 用户是不可见的 , 总之在到达最顶层之前 , 把它标记来删除 , 对于最上层的用户是不可见的 , 当标记一删除 , 只有用户在最上层建一个同名一样的文件 , 才是可见的。
类似于挂载一样 , 宿主机的/data/web目录与容器中的/container/data/web目录绑定关系 , 然后容器中的进程向这个目录中写数据时 , 是直接写在宿主机的目录上的 , 绕过容器文件系统与宿主机的文件系统建立关联关系 , 使得可以在宿主机和容器内共享数据库内容 , 让容器直接访问宿主机中的内容 , 也可以宿主机向容器供集内容 , 两者是同步的。
mount名称空间本来是隔离的 , 可以让两个本来是隔离的文件系统 , 在某个子路径上建立一定程度的绑定关系 , 从而使得在两个容器之间的文件系统的某个子路径上不再是隔离的 , 实现一定程度上共享的效果。
在宿主机上能够被共享的目录(可以是文件)就被称为volume。
对于修改删除等这类的操作 , 一般效率非常低 , 如果对于I/O要求比较高的应用 , 如redis在实现持化存储时 , 是在底层存储时的性能要求比较高。
假设底层运行一个存储库mysql ,mysql本来对于I/O的要求就比较高 , 如果mysql又是运行在容器中自己的文件系统之上时 , 也就是容器在停止时 , 就意味着删除 , 其实现数据存取时效率比较低 , 要避免这个限制要使用存储卷来实现。
容器原本存在的问题 :
存储于联合文件系统中 , 不易于宿主机访问 ;
容器间数据共享不便
删除容器其数据会丢失
1.2 存储卷作用
优点是容器中进程所生成的数据 , 都保存在存储卷上 , 从而脱离容器文件系统自身后 , 当容器被关闭甚至被删除时 , 都不用担心数据被丢失, 实现数据可以脱离容器生命周期而持久 , 当再次重建容器时 , 如果可以让它使用到或者关联到同一个存储卷上时 , 再创建容器 , 虽然不是之前的容器 , 但是数据还是那个数据 , 特别类似于进程的运行逻辑 , 进程本身不保存任何的数据 , 数据都在进程之外的文件系统上 , 或者是专业的存储服务之上 , 所以进程每次停止 , 只是保存程序文件 , 对于容器也是一样 ; 容器就是一个有生命周期的动态对象来使用 , 容器关闭就是容器删除的时候 , 但是它底层的镜像文件还是存在的 , 可以基于镜像再重新启动容器。
但是容器有一个问题 , 一般与进程的启动不太一样 , 就是容器启动时选项比较多 , 如果下次再启动时 , 很容器会忘记它启动时的选项 , 所以最好有一个文件来保存容器的启动 , 这就是容器编排工具的作用。一般情况下 , 是使用命令来启动操作docker , 但是可以通过文件来读 , 也就读文件来启动 , 读所需要的存储卷等 , 但是它也只是操作一个容器 , 这也是需要专业的容器编排工具的原因。
另一个优势就是容器就可以不置于启动在那台主机之上了 , 如几台主机后面挂载一个NFS , 在各自主机上创建容器 , 而容器上通过关联到宿主机的某个目录上 , 而这个目录也是NFS所挂载的目录中 , 这样容器如果停止或者是删除都可以不限制于只能在原先的宿主机上启动才可以 , 可以实现全集群范围内调试容器的使用 , 当再分配存储、计算资源时 , 就不会再局限于单机之上 , 可以在集群范围内建立起来 , 基本各种docker的编排工具都能实现此功能 , 但是后面严重依赖于共享存储的使用。
1.3 配合各服务应用状态分析
考虑到容器应用是需要持久存储数据的 , 可能是有状态的 , 如果考虑使用NFS做反向代理是没必要存储数据的 , 应用可以分为有状态和无状态 , 有状态是当前这次连接请求处理一定此前的处理是有关联的 , 无状态是前后处理是没有关联关系的 , 大多数有状态应用都是数据持久存储的 , 如mysql,redis有状态应用 , 在持久存储 , 如nginx作为反向代理是无状态应用 , tomcat可以是有状态的 , 但是它有可能不需要持久存储数据 , 因为它的session都是保存在内存中就可以的 , 会导致节点宕机而丢失session , 如果有必要应该让它持久 , 这也算是有状态的。
应用状态象限 : 是否有状态或无状态 , 是否需要持久存储 , 可以定立一个正轴坐标系 , 第一象限中是那些有状态需要存储的 , 像mysql ,redis等服务 , 有些有有状态但是无需进行存储的 , 像tomcat把会话保存在内存中时 , 无状态也无需要存储的数据 , 如各种反向代理服务器nginx ,lvs请求连接都是当作一个独立的连接来调度 , 本地也不需要保存数据 , 第四象限是无状态 , 但是需要存储数据是比较少见。

运维起来比较难的是有状态且需要持久的 , 需要大量的运维经验和大量的操作步骤才能操作起来的 , 如做一个Mysql主从需要运维知识、经验整合进去才能实现所谓的部署 , 扩展或缩容 , 出现问题后修复 , 必须要了解集群的规模有多大 , 有多少个主节点 , 有多少个从节点 , 主节点上有多少个库 , 这些都要一清二楚 , 才能修复故障 , 这些就强依赖于运维经验 , 无状态的如nginx一安装就可以了 , 并不复杂 , 对于无状态的应用可以迅速的实现复制 , 在运维上实现自动化是很容易的 , 对于有状态的现状比较难脱离运维人员来管理 , 即使是k8s在使用上也暂时没有成熟的工具来实现。
总之 : 对于有状态的应用的数据 , 不使用存储卷 , 只能放在容器本地 , 效率比较低 , 而导致一个很严重问题就是无法迁移使用 , 而且随着容器生命周期的停止 , 还不能把它删除 , 只能等待下次再启动状态才可以 , 如果删除了数据就可能没了 , 因为它的可写层是随着容器的生命周期而存在的 , 所以只要持久存储数据 , 存储卷就是必需的。
docker存储卷难度 : 对于docker存储卷运行起来并不太麻烦 , 如果不自己借助额外的体系来维护 , 它本身并没有这么强大 , 因为docker 存储卷是使用其所在的宿主机上的本地文件系统目录 , 也就是宿主机有一块磁盘 , 然后容器所使用的目录 , 只是关联到宿主机磁盘上的某个目录而已 , 也就是容器在这宿主机上停止或删除 , 是可以重新再创建的 , 但是不能调度到其他的主机上 , 这也是docker本身没有解决的问题 , 所以docker存储卷默认就是docker所在主机的本地文件系统 , 但是自己搭建一个共享的NFS来存储docker存储的数据 , 也可以实现 , 但是这个过程强依赖于运维人员的能力。
2. 数据卷类型
Docker提供了3种不同的方式将数据从宿主机挂载到容器中。

2.1 volume
volume : Docker管理宿主机文件系统的一部分 , 默认位于 /var/lib/docker/volumes 目录下, 也是最常用的方式。

看上图 , 所有的Docker容器数据都保存在 /var/lib/docker/volumes 目录下。若容器运行时未指定数据卷 , Docker创建容器时会使用默认的匿名卷 ( 名称为一堆很长的ID ) 。
注意 :
Mac系统中 ,Docker是基于虚拟机的 , 必须登录到虚拟机里面 , 登录后在目录/var/lib/docker/volumes/下即可找到数据卷。
2.2 bind mount
bind mount : 意为可以存储在宿主机中的任意位置 , 在宿主机上的路径要人工的指定一个特定的路径 , 在容器中也需要指定一个特定的路径 , 两个已知的路径建立关联关系。需要注意的是 , bind mount在不同的宿主机系统时不可移植的 , 比如Windows和 Linux的目录结构是不一样的 , bind mount所指向的host目录也不一样。这也是为什么bind mount不能出现在Dockerfile 中的原因所在 , 因为这样Dockerfile就不可移植了。

2.3 tmpfs mount
tmpfs mount : 挂载存储在宿主机的内存中 , 而不会写入宿主机的文件系统 , 一般不用此种方式。
如果在Linux上运行Docker , 那么还有第三种选择 : tmpfs挂载。不会持久化到宿主机。目的是为了避免写入数据到容器存储层还有一个方案。
与卷和绑定挂载不同 , tmpfs挂载是临时的 , 只存留在主机内存中。当容器停止时 , tmpfs 挂载将被删除 , 在那里写入的文件不会被持久化。
tmpfs挂载的局限性不同于卷和绑定挂载 , 不能在容器之间共享
tmpfs挂载。这个功能只有在
Linux上运行Docker时才可用。
--tmpfs和--mount行为之间的差异--tmpfs标记不允许指定任何可配置选项。--tmpfs标记不能用于集群服务。对于集群服务 , 您必须使用--mount。
在容器中使用 tmpfs 挂载
要在容器中使用
tmpfs挂载 , 请使用--tmpfs标记, 或者使用带有type=tmpfs和destination选项的--mount标记。没有用于tmpfs挂载的源(source)。示例1 : (
--mount) , 在Nginx容器中的/usr/share/nginx/html创建一个tmpfs挂载$ docker run -d \ -it \ --name tmptest \ --mount type=tmpfs,destination=/usr/share/nginx/html \ nginx:latest $ docker exec -it tmptest /bin/bash $ df -h示例2 : (
--tmpfs)$ docker run -d \ -it \ --name tmptest2 \ --tmpfs /usr/share/nginx/html \ nginx:latest
通过运行
docker inspect tmptest来验证挂载是否是tmpfs挂载 , 查看Mounts部分:$ docker inspect tmptest|grep -i -C3 Tmpfs
指定 tmpfs 选项
tmpfs挂载允许两个配置选项 , 两个选项都不是必需的。 如果需要指定这些选项 , 则必须使用--mount标记 , 因为--tmpfs标记不支持。tmpfs-size tmpfs 挂载的大小(以字节为单位)。默认无限制。 tmpfs-mode tmpfs 的八进制文件模式。例如 , 700 或 0770。默认为 1777 或全局可写。示例:下面的示例将
tmpfs-mode设置为1770, 因此在容器中它不是全局可读的。$ docker run -d \ -it \ --name tmptest3 \ --mount type=tmpfs,destination=/usr/share/nginx/html,tmpfs-mode=1770 \ nginx:latest docker volume create --driver local \ --opt type=tmpfs \ --opt device=tmpfs \ --opt o=size=100m,uid=1000 \ foo
3. 数据卷使用
3.1 命令使用
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 unused local volumes
rm Remove one or more volumes
update Update a volume (cluster volumes only)3.1.1 创建存储卷
创建一个名为vol_simple的存储卷 :
docker volume create --name vol_simple查看数据卷的信息 :
docker volume inspect vol_simple示例 :
fly@fly:~$ docker volume create --name vol_simple
vol_simple
fly@fly:~$ docker volume inspect vol_simple
[
{
"CreatedAt": "2023-01-04T11:59:24Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/vol_simple/_data",
"Name": "vol_simple",
"Options": {},
"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
abcd3.1.2 挂载数据卷
3.1.2.1 基础使用
使用docker run或者docker create创建新容器是 , 可以使用-v标签为容器添加volume。可以将自行创建或者有Docker创建的volume挂载到容器中 , 也可以将宿主机上的目录或者文件作为volume挂载到容器中。
将宿主机中指定目录作为volume挂载到容器中的/data目录下 , 文件夹必须使用绝对路径 , 如果宿主机中不存在指定的目录 , 则会创建一个空文件夹 ; 如果宿主机文件夹已经存在 , 容器可以通过访问挂载点/data从而访问宿主机文件夹中的所有内容。如果容器下原本已经存在/data文件夹 , 且不为空 , 那么容器中文件夹下原有的内容将被隐藏 , 来保持与宿主机中的文件夹一致。
挂载方式 :
创建名为
vol_simple的数据卷 , 并挂载到容器中的/data目录下 :docker volume create --name vol_simple docker run -d -v vol_simple:/data ubuntu /bin/bash创建一个随机的
ID的volume, 并将其挂载到容器中的/data目录下 :docker run -d -v /data ubuntu /bin/bash关联宿主机目录 :
docker run -d -v $HOME/data:/data ubuntu /bin/bash使用
--mount挂载数据卷docker run -d -it --name=test-nginx -p 8011:80 --mount source=test-vol,target=/usr/share/nginx/html nginx:1.13.12--mount source=test-vol,target=/usr/share/nginx/html: 将test-vol数据卷挂载到容器中的/usr/share/nginx/html目录上;挂载权限 ,
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指定数据卷挂载 :# dockerfile 添加指令 VOLUME /data # 指定多个数据卷的挂载点 VOLUME ["/data1","/data2"]使用
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 宿主机存储卷目录示例2 : 由于挂载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 宿主机存储卷目录
[root@harbor ubuntu]# ls /var/lib/docker/volumes/5a03cd48d24dab9c8b82df7579b0f76135b0b0e282e6a67f79a7ea3dcc9ed913/_data
file示例3 : 通过CMD和ENTRYPOINT指令 , 在容器启动时执行挂载点下文件的初始化。
FROM ubuntu
RUN useradd fly2
VOLUME /data
CMD touch /data/file && chown -R fly2:fly2 /data3.1.2.2 -v与--mount使用区别
都是挂载命令 , 使用 -v 挂载时 , 如果宿主机上没有指定文件不会报错 , 会自动创建指定文件;当使用 --mount时 , 如果宿主机中没有这个文件会报错找不到指定文件 , 不会自动创建指定文件。
容器运行成功后 , 进入到 /var/lib/docker/volumes 目录下 , 验证数据是否挂载成功 :

可以看到已经有了 50x.html 、 index.html 两个Nginx页面相关数据 , 说明数据卷挂载成功了。挂载成功后 , 我们不论是修改 /var/lib/docker/volumes 下的数据 , 还是进入到容器中修改 /usr/share/nginx/html 下的数据 , 都会同步修改对应的挂载目录 , 类似前端开发中双向绑定的作用。
下面 , 我们停止并删除刚刚运行的Nginx容器, 看看数据卷中的数据是否会跟着被删除 :

可以发现数据卷相关数据都还在 , 表明数据卷的生命周期独立于容器。另外 , 若下次再创建Nginx容器 , 还可以复用这个数据卷 , 复用性以及扩张性都非常不错。
-v|--volume[=[[HOST-DIR:]CONTAINER-DIR[:OPTIONS]]]创建
bind装载。如果指定-v /HOST-DIR:/CONTAINER-DIR,Docker将主机中的/HOST-DR绑定到Docker容器中的/COCONTAINER-DIR。如果省略了“HOST-DIR” ,Docker会自动在主机上创建新的volume卷。OPTIONS是一个逗号分隔的列表[rw|ro][z|Z][[r]shared|[r]slave|[r]private][delegated|cached|consistent][nocopy]
CONTAINER-DIR必须是绝对路径 , 例如/src/docs。HOST-DIR可以是绝对路径或名称值。名称值必须以字母数字开头 , 后面跟着A -z0-9、_(下划线)、.(句号)或-(连字符)。绝对路径以/(正斜杠)开头。如果你提供的
HOST-DIR是一个绝对路径 ,Docker会绑定挂载到你指定的路径。如果您提供了一个名称 ,Docker将以该名称创建一个volume卷。例如 , 您可以为HOST-DIR值指定/foo或foo。如果你提供了/foo值 ,Docker会创建一个bind挂载。如果你提供了foo规范 ,Docker会创建一个命名卷。您可以指定多个
-v选项来将一个或多个挂载挂载到容器。要在其他容器中使用相同的挂载 , 可以使用--volumes-from选项。您可以在附加冒号后面为每个绑定挂载提供附加选项。
A:ro或:rw后缀分别以只读或读写方式挂载卷。默认情况下 , 卷以读写方式挂载。您还可以指定挂载的一致性要求 , 可以是:consistent(默认)、:cached或:delegated。多个选项之间用逗号分隔 , 例如:ro,cached。像
SELinux这样的标签系统要求在安装到容器中的卷内容上放置适当的标签。如果没有标签 , 安全系统可能会阻止在容器内运行的进程使用内容。默认情况下 ,Docker不修改操作系统设置的标签。要在容器上下文中更改标签 , 可以向卷挂载添加两个后缀
:z或:Z之一。这些后缀告诉Docker重新标记共享卷上的文件对象。z选项告诉Docker两个容器共享卷内容。因此 ,Docker用共享内容标签来标记内容。共享卷标签允许所有容器读/写内容。Z选项告诉Docker用一个私有的非共享标签来标记内容。只有当前容器可以使用私有卷。默认情况下,绑定挂载的卷是私有的。这意味着在容器内完成的任何挂载在主机上都不可见,反之亦然。可以通过指定卷挂载传播属性来更改此行为。在容器内的卷下创建卷共享挂载将在主机上可见,反之亦然。创建从属卷只支持单向挂载传播,即在该卷下的主机上进行的挂载将在容器内可见,而不是其他方式。
要控制卷的挂载传播属性,可以使用
:[r]shared、:[r]slave或:[r]private传播标志。传播属性只能为bind挂载的卷指定,不能为内部卷或命名卷指定。要使挂载传播工作,源挂载点(源目录挂载的挂载点)必须具有正确的传播属性。对于共享卷,源挂载点必须是共享的。对于从卷,源挂载要么是共享的,要么是从卷。使用
df <source-dir>找出源挂载,然后使用findnt -o TARGET,PROPAGATION <source-mount- dir>找出源挂载的传播属性。如果findnt实用程序不可用,那么可以在/proc/self/mountinfo中查看源挂载点的挂载条目。查看可选字段并查看是否指定了任何传播属性。shared:X表示挂载是共享的,master:X表示挂载是从属的,如果没有,则表示挂载是私有的。要更改挂载点的传播属性,请使用
mount命令。例如,如果想绑定挂载源目录/foo,可以执行mount --bind /foo /foo和mount --make-private --make-shared /foo。这将把/foo转换为一个共享挂载点。或者,可以直接更改源挂载的传播属性。假设/是/foo的源挂载,然后使用mount --make-shared /将/转换为共享挂载。注意:当使用
systemd来管理Docker守护进程的启动和停止时,在systemd单元文件中有一个选项来控制Docker守护进程本身的挂载传播,称为MountFlags。此设置的值可能导致Docker看不到挂载点上的挂载传播更改。例如,如果该值为slave,则可能无法在卷上使用shared或rshared传播。要禁用数据从容器路径自动复制到卷,请使用
nocopy标志。nocopy标志可以在绑定挂载和命名卷上设置。--mount type=TYPE,TYPE-SPECIFIC-OPTION[,...]将文件系统挂载到容器上
现在支持的挂载类型由三种:
bind、volume和tmpfstype=bind,source=/path/on/host,destination=/path/in/container type=volume,source=my-volume,destination=/path/in/container,volume-label="color=red",volume-label="shape=round" type=tmpfs,tmpfs-size=512M,destination=/path/in/container共有参数:
src,source: 挂载源地址。bind类型必选dst,destination,target: 挂载目的地ro,readonly: true or false (default).
注意:根据内核版本的不同,将绑定挂载设置为
readonly可能不会使其子挂载变为只读。参见bind-recursive。bind类型相关选项bind-propagation:shared,slave,private,rshared,rslave, orrprivate(default)consistency:consistent(default),cached, ordelegated. 仅适用于MAC版dockerbind-recursive:enabled(default),disabled,writable, orreadonly:read-only.如果设置为
enabled, 子挂载将递归绑定挂载,并尝试递归创建子挂载只读的。如果设置为
disabled, 子挂载不会递归绑定挂载如果设置为
writable, 子挂载将递归绑定挂载,但递归不会设置为只读如果设置为
readonly,子挂载将被递归绑定挂载,并强制递归只读。
bind-nonrecursive(已弃用):true或false(默认)。设置true等同于bind-recursive=disabled。 设置false等同于bind-recursive=enabled。
volume类型相关参数volume-driver:volume-driver插件名称volume-lable: 卷的元数据信息volume-nocopy:true(默认)或false。如果设置为false,则引擎将挂载路径下的现有文件和目录复制到卷中,允许主机访问它们。volume-opt: 特定于给定的卷驱动程序
tmpfs类型参数:tmpfs-size: 以字节为单位的tmpfs挂载大小。在Linux中默认是无限的。tmpfs-mode:tmpfs权限模式(8进制)。(例如:700或0700。)Linux默认为1777。
3.1.3 共享数据卷
在使用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只读共享点不能被写入。
可以同时共享多个容器的数据卷。
docker inspect查看容器元数据。
3.1.4 删除数据卷
如果创建容器时从容器中挂载了volume , 在/var/lib/docker/volumes下会生产与volume对应的目录 ( 可使用docker inspect命令查看容器信息找到对应的信息 ) 。 使用docker rm删除容器并不会删除与volume对应的目录 , 这些目录会占据不必要的存储空间。
删除volume的三种方式 :
使用
docker volume rm <volume_name>删除数据卷。使用
docker rm -v <container_name>删除容器时一并删除它挂载的数据卷。在运行容器时使用
docker run --rm标签会在容器停止运行时删除容器以及容器所挂载的volume。
注意 :
在使用第一种
docker volume rm删除时 , 只有当没有任何容器使用该volume的时候 , 才能被删除成功。另外两种删除方式 , 只会对挂载在该容器上的未指定名称 ( 匿名的 ) 的
volume进行删除 , 而会对用户指定名称的 ( 具名的 )volume进行保留。如果
volume是在创建容器时从宿主机中挂载的 , 无论对容器进行任何操作都不会导致其在宿主机中被删除 , 如果不需要这些文件 , 只能手动删除。 ( 从宿主机中挂载是指docker run -v dir:dir这种模式 ) 。
4. 数据卷类型的使用说明
4.1 bind mount使用
4.1.1 基础使用
通过bind mount模式可以挂载到宿主机的任意位置 , 示例如下 :
docker run -d -it --name=test-nginx -p 8011:80 -v /docker/nginx1:/usr/share/nginx/html nginx:1.13.12参数说明 :
-v /docker/nginx1:/usr/share/nginx/html: 将宿主机中的/docker/nginx1目录挂载到容器中的/usr/share/nginx/html目录;
容器运行成功后 , 进入容器中 :
docker exec -it test-nginx /bin/bash
从上图可以看到 , 与volume不同 , bind mount这种方式会隐藏目录中的内容 ( 非空情况下 ) , 这里的 /usr/share/nginx/html 目录下的html文件被隐藏了 , 所以我们看不到。
但是 , 我们可以将宿主机中该目录中的文件立刻挂载到容器中 , 下面验证一下 :
新建一个
index.html:
再次进入容器 , 查看挂载目录内容 :

4.1.2 使用示例
4.1.2.1 将宿主机的代码映射到tomcat容器指定的目录下
先查看
tomcat容器里边的代码内容[root@centos-7 app1]``# docker run -it -p 8080:8080 tomcat-app1:v1 # 启动tomcat-app1:v1容器到运行的
tomcat-app1:v1的容器中查看代码内容[root@centos-7 ~]# docker ps #查看运行的容器 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 21f9df7830a5 tomcat-app1:v1 "/apps/tomcat/bin/ru…" About a minute ago Up About a minute 0.0.0.0:8080->8080/tcp, 8443/tcp nervous_grothendieck [root@centos-7 ~]# docker exec -it 21f9df7830a5 bash # 进入正在运行的容器内部 [root@21f9df7830a5 /]# ls apps bin data dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var [root@21f9df7830a5 /]# cd /data/tomcat/webapps/ [root@21f9df7830a5 webapps]# cat app1/index.html #查看容器缺省的代码内容 tomcat web app1 page访问网页也可以看到此时的内容

在宿主机上先创建一个存放代码的目录
[root@centos-7 ~]# cd /data/tomcat/app1/ [root@centos-7 app1]# echo 20200116 welcome to Shanghai > index.html将宿主机的代码数据映射到
tomcat访问的数据目录下 , 并映射端口 , 此时volume映射的优先级高[root@centos-7 app1]# docker run -it -v /data/tomcat/app1/:/data/tomcat/webapps/app1 -p 8080:8080 tomcat-app1:v1查看网页效果 , 此时就是宿主机的代码覆盖了容器内部的数据代码

4.1.2.2 实现宿主机配置文件挂载到容器中
在宿主机修改
catalina.sh脚本文件 , 并存放到指定的目录下[root@centos-7 bin]# mkdir /data/tomcat/bin [root@centos-7 bin]#cd /data/tomcat/bin # 在此目录下将脚本文件存放在此创建一个挂载容器
log日志的目录[root@centos-7 bin]``# mkdir /data/tomcat/logs将目录和
catalina.sh脚本分别加上权限# chmod 777 /data/tomcat/logs # 目录先加上777权限``# chmod +x /data/tomcat/bin/catalina.sh # 加上执行权限查看
catalina.sh配置文件内容 , 最主要的部分在下面
开始挂载此文件到容器中 , 并设置为只读文件 , 避免在容器中进行修改此文件 , 且将容器中的
log日志进行挂载 , 避免容器坏掉而log日志在容器中 , 导致数据丢失[root@centos-7 logs]# docker run -it -v /data/tomcat/bin/catalina.sh:/apps/tomcat/bin/catalina.sh -v /data/tomcat/logs/:/apps/tomcat/logs/ -p 8282:8080 tomcat-app1:v1此时就可以在宿主机查看容器里边的
log日志[root@centos-7 bin]# ll /data/tomcat/logs/ total 16 -rw-rw-r-- 1 2020 2020 7355 Jan 16 18:03 catalina.2020-01-16.log -rw-rw-r-- 1 2020 2020 7866 Jan 16 18:03 catalina.out -rw-rw-r-- 1 2020 2020 0 Jan 16 18:03 host-manager.2020-01-16.log -rw-rw-r-- 1 2020 2020 0 Jan 16 18:03 localhost.2020-01-16.log -rw-rw-r-- 1 2020 2020 0 Jan 16 18:03 localhost_access_log.2020-01-16.txt -rw-rw-r-- 1 2020 2020 0 Jan 16 18:03 manager.2020-01-16.log此时
catalina.sh的文件已经存放到文件中[root@centos-7 bin]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 95d4f7d917bc tomcat-app1:v1 "/apps/tomcat/bin/ru…" 56 seconds ago Up 55 seconds 8443/tcp, 0.0.0.0:8282->8080/tcp wizardly_hypatia [root@centos-7 bin]# docker exec -it 95d4f7d917bc bash [root@95d4f7d917bc /]# cd /apps/tomcat/bin/ [root@95d4f7d917bc bin]# ls bootstrap.jar commons-daemon-native.tar.gz daemon.sh setclasspath.bat startup.bat tool-wrapper.bat catalina-tasks.xml commons-daemon.jar digest.bat setclasspath.sh startup.sh tool-wrapper.sh catalina.bat configtest.bat digest.sh shutdown.bat tomcat-juli.jar version.bat catalina.sh configtest.sh run_tomcat.sh shutdown.sh tomcat-native.tar.gz version.sh [root@95d4f7d917bc bin]# vim catalina.sh
4.2 数据卷容器
4.2.1 基础使用
创建数据卷容器
运行一个容器 , 并创建一个名为
dbdata的数据卷 :docker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres
容器运行成功后 , 会发现该数据卷容器处于停止运行状态 , 这是因为数据卷容器并不需要处于运行状态 , 只需用于提供数据卷挂载即可。
挂载数据卷
--volumes-from命令支持从另一个容器挂载容器中已创建好的数据卷。docker run -d --volumes-from dbdata --name db1 training/postgres docker run -d --volumes-from dbdata --name db2 training/postgres docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7348cb189292 training/postgres "/docker-entrypoint. 11 seconds ago Up 10 seconds 5432/tcp db2 a262c79688e8 training/postgres "/docker-entrypoint. 33 seconds ago Up 32 seconds 5432/tcp db1还可以使用多个
--volumes-from参数来从多个容器挂载多个数据卷。也可以从其他已经挂载了数据卷的容器来挂载数据卷。如果删除了挂载的容器 ( 包括
dbdata、db1和db2) , 数据卷并不会被自动删除。如果想要删除一个数据卷 , 必须在删除最后一个还挂载着它的容器时使用docker rm -v命令来指定同时删除关联的容器。
4.2.2 使用示例
创建共享存储卷容器
创建一个
volume-server的容器名 , 并将宿主机的访问页面index.html、catalina.sh、logs日志都映射到此日志中[root@centos-7 ~]# docker run -it -d --name volume-server -v /data/tomcat/bin/catalina.sh:/apps/tomcat/bin/catalina.sh:ro -v /data/tomcat/logs:/apps/tomcat/logs -v /data/tomcat/app1/:/data/tomcat/webapps/app1/ tomcat-app1:v1 230d219e035c3fec1145c32060fed37e1e12154df0d010b34f614740f31ef9bd # 添加一个server服务器的容器 , 并将宿主机的文件映射到此容器中--volume-from选项来复制容器中存储卷创建
volume-client客户端的容器 , 用--volume-from选项复制上一个volume-server容器里边的存储卷[root@centos-7 ~]# docker run -it -d --name volume-client --volumes-from volume-server -p 8282:8080 tomcat-app1:v1 1f40e1b7b4eb9192260557ebef78aec51f239e66f994b2ae42b1c18a9849843b #此时映射的volume-client容器内容都是从colume-server容器来的进入
colume-client的容器内 , 也可以看到宿主机的文件内容[root@centos-7 ~]# docker exec -it 1f40e1b7b4eb9192260557ebef78aec51f239e66f994b2ae42b1c18a9849843b bash [root@1f40e1b7b4eb /]# cat /data/tomcat/webapps/index.html 20200116 welcome to Shanghai # 可以看到是宿主机的index.html文件 [root@1f40e1b7b4eb /]# ls -l /apps/tomcat/logs/ total 92 -rw-rw-r-- 1 tomcat tomcat 42631 Jan 16 19:21 catalina.2020-01-16.log -rw-rw-r-- 1 tomcat tomcat 45697 Jan 16 19:21 catalina.out -rw-rw-r-- 1 tomcat tomcat 0 Jan 16 18:03 host-manager.2020-01-16.log -rw-rw-r-- 1 tomcat tomcat 0 Jan 16 18:03 localhost.2020-01-16.log -rw-rw-r-- 1 tomcat tomcat 0 Jan 16 18:03 localhost_access_log.2020-01-16.txt -rw-rw-r-- 1 tomcat tomcat 0 Jan 16 18:03 manager.2020-01-16.log此时访问
volume-client的页面 , 实际也是来自于volume-server容器的内容
再启动一个容器复制
volume-server容器的存储卷此时新建一个
volume-client容器也能访问宿主机的代码网页[root@centos-7 ~]# docker run -it -d --name volume-client1 --volumes-from volume-server -p 8383:8080 tomcat-app1:v1 # 只需要修改映射端口 , 客户端的名称即可 ce0e8bd7e33249b8b9ec59e8c7c7678086958b62cac4bb16c43e92577aa0d77f进入到
volume-client2的容器中 , 也可以看到宿主机的访问页面[root@centos-7 ~]# docker exec -it ce0e8bd7e33249b8b9ec59e8c7c7678086958b62cac4bb16c43e92577aa0d77f bash #进入volume-client2容器中 [root@ce0e8bd7e332 /]# cat /data/tomcat/webapps/index.html # 查看此客户端的页面 20200116 welcome to Shanghai此时映射的端口 :
8383照样也能访问网页
此时挂载后的
client客户端不会受server端容器的宕机而导致不能访问 , 但是新的容器就不能基于volume-server服务容器进行创建新的容器。[root@centos-7 ~]# docker ps # 查看image镜像ID CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES b4044ab23dc0 tomcat-app1:v1 "/apps/tomcat/bin/ru…" 38 seconds ago Up 37 seconds 8443/tcp, 0.0.0.0:8383->8080/tcp volume-client1 80556d0c9945 tomcat-app1:v1 "/apps/tomcat/bin/ru…" About a minute ago Up About a minute 8443/tcp, 0.0.0.0:8282->8080/tcp volume-client 54dd95b8d95c tomcat-app1:v1 "/apps/tomcat/bin/ru…" About a minute ago Up About a minute 8080/tcp, 8443/tcp volume-server [root@centos-7 ~]# docker rm -f 54dd95b8d95c #删除server服务端的ID 54dd95b8d95c此时客户端的网页还是可以访问 , 此时 , 实验就实现了一个容器去共享存储卷 , 用
--volume-from选项来复制此volume-server的文件 , 实现存储卷共享的作用。
总结
在当前环境下 , 即使把提供卷的容器
Server删除 , 已经运行的容器Client依然可以使用挂载的卷 , 因为容器是通过挂载访问数据的 , 但是无法创建新的卷容器客户端 , 但是再把卷容器Server创建出来后即可创建新的卷容器Client此方式可以用于线上数据库 、共享数据目录等环境 , 即使数据卷容器被删除了 , 其他已经运行的容器依然可以挂载使用。
数据卷容器可以作为共享的方式为其他容器提供文件共享 , 类似于
NFS共享 , 可以在生产中启动一个实例挂载本地的目录 , 然后其他的容器分别挂载此容器的目录 , 即可保证各容器之间的数据一致性。
参考链接
Docker之七---Docker存储卷与容器卷详解 - 一叶知秋~~ - 博客园 (cnblogs.com)