5、docker存储卷
docker存储卷
本章要点:docker存储卷,存储卷两种类型(volume,bind),使用示例,共享卷组
Docker存储卷是实现容器数据持久化的核心机制,通过绑定宿主机目录与容器目录,解决数据与容器生命周期绑定的问题,支持跨主机数据共享。
存储卷基础概念
-
为什么要使用存储卷
假设我们通过镜像创建一个容器。容器一旦被销毁,容器内的数据将会被一并删除。但有些情况下,通过服务器上传的图片出会丢失。容器中的数据不是持久化状态的,此时我们就需要使用到存储卷。
-
如果不使用数据卷存在的问题
- 存储于联合文件系统中, 不易于宿主机访问;
- 容器间数据共享 不便;
- 删除容器其数据会丢失 ;
-
基本概念
Docker 存储卷是由 Docker 管理的特殊目录,其核心特性包括:
- 持久化:独立于容器生命周期,容器删除后数据仍保留 。
- 共享性:可被多个容器同时挂载,实现数据共享。
- 实时生效:卷中数据更改可实时同步到挂载的容器。
- 不影响镜像:卷的更改不会包含在镜像更新中。
-
作用
Docker镜像基于写时复制(COW)机制,容器修改文件需复制到读写层,效率低且数据随容器删除丢失 ,存储卷通过将宿主机目录与容器目录绑定,使数据直接写入宿主机,实现独立于容器生命周期的持久化
存储卷理论
-
存储卷(volume)
“卷”是容器上一个或多个“目录”,此类目录可绕过联合文件系统,与宿主机上某目录“绑定或关联", 存储卷的初衷是独立于容器的生命周期实现数据持久化,因此删除容器之时既不会删除卷,也不会对哪怕未被引用的卷做垃圾回收操作;
两种卷类型:
-
bind mount volume (数据卷): 容器内数据直接映射到本地主机环境
- 需要手动指定目录, 一对一目录指定 如 宿主机 /data 容器的 /data
-
docker-managed volume (数据卷容器): 使用特定容器维护数据卷
- docker 自行创建的管理卷, 只需要指定容器卷, 无需指定宿主机的目录,删除容器卷数据可能丢失
-
特性对比
类型 宿主机路径管理 适用场景 容器删除后卷状态 Docker-managed volume 自动分配(如 /var/lib/docker/volumes/...
)临时数据共享、无需手动管理路径 默认删除(需显式保留) Bind mount volume 手动指定(如 ~/data:/container/path
)固定路径数据持久化、跨容器共享 需手动删除宿主机目录
-
存储卷使用
数据卷是一个可供容器使用的特殊目录, 它将主机操作系统目录直接映射进容器,类似于Linux中mount操作, 特性如下:
-
数据卷可以在容器之间共享和重用,容器间传递数据将变得高效方便 ;
-
对数据卷内的数据修改会立马生效,无论是容器内操作还是本地操作;
-
对数据卷的更新不会影响镜像,解耦了应用和数据;
-
卷会一直存在,直到没有容器使用,可以安全卸载它
-
两种卷的管理以及创建方式
特性 Docker-managed volume Bind mount volume 管理方式 Docker 自动管理,默认路径 /var/lib/docker/volumes/卷名/_data
用户指定宿主机任意目录/文件路径 创建方式 docker volume create 卷名
或运行容器时自动创建运行容器时直接指定 -v 宿主机路径:容器内路径
适用场景 大多数持久化场景(如数据库数据存储) 需要指定存储位置(如挂载独立磁盘目录) 灵活性 无需关心具体存储路径,由 Docker 统一管理 路径完全由用户控制,更灵活但需手动维护
Docker-managed volume
-
特性
- 管理方式: Docker 自动管理,默认路径
/var/lib/docker/volumes/卷名/_data
- 创建方式:
docker volume create 卷名
或运行容器时自动创建 - 适用场景:大多数持久化场景(如数据库数据存储)
- 灵活性: 无需关心具体存储路径,由 Docker 统一管理
- 管理方式: Docker 自动管理,默认路径
-
语法说明
命令 说明 docker volume ls 查看当前节点创建的卷组 docker volume inspect 卷组名 查看卷组的详细信息 docker volume create 卷组名 创建卷组的详细信息 docker volume rm 卷组名 删除卷组 ⚠️ 如果卷组被挂载,则无法被直接删除
-
示例1 - 手动创建容器管理卷
1、创建卷名 ~]# docker volume create tomcat12、挂载到容器上 , 指定tomcat1名, -P是容器自动映射端口 -v指定挂载的容器卷 ~]# docker run -d --name tomcat1 -P -v tomcat1:/usr/local/tomcat/webapps/ edf8233555ae3、默认tomcat没有ROOT目录,我们这里示例先手动创建一个 ~]# docker exec -it tomcat1 bash root@ff6787d9978d:/usr/local/tomcat# cd webapps root@ff6787d9978d:/usr/local/tomcat/webapps# mkdir ROOT root@ff6787d9978d:/usr/local/tomcat/webapps# exit4、手动cp一个index.html用于测试, 访问 http://ip:自动映射端口 ~]# docker cp index.html tomcat1:/usr/local/tomcat/webapps/ROOT/5、这里开始操作本地存储卷 ~]# ls /var/lib/docker/volumes/tomcat1/_data/ ROOT 6、修改内容,访问 ~]# vi /var/lib/docker/volumes/tomcat1/_data/ROOT/index.html # index.html内容 hello world cp <---第一次用cp是这样 hello world volume <---第二次修改volume是这样7、我们也可以通过 inspect 查看挂载信息 ~]# docker inspect tomcat1"HostConfig": { "Binds": [ "tomcat1:/usr/local/tomcat/webapps/" ], ..... }"Mounts": [{"Type": "volume", "Name": "tomcat1","Source": "/var/lib/docker/volumes/tomcat1/_data","Destination": "/usr/local/tomcat/webapps","Driver": "local", "Mode": "z", "RW": true, "Propagation": ""}],8、删除容器 ~]# docker stop tomcat1 ~]# docker rm tomcat1 ~]# ls /var/lib/docker/volumes/tomcat1/_data/ <--我们手动创建的不会同时删除
-
示例2 - 仅指定挂载容器管理卷名称
1、挂载到容器上 , 指定tomcat2名, -P是容器自动映射端口 -v指定挂载的容器卷 ~]# docker run -d --name tomcat2 -P -v tomcat2:/usr/local/tomcat/webapps/ edf8233555ae2、这里开始操作本地存储卷, 操作完之后直接访问 http://ip:自动映射端口 ~]# mkdir /var/lib/docker/volumes/tomcat2/_data/ROOT/ ~]# cp index.html /var/lib/docker/volumes/tomcat2/_data/ROOT/3、我们也可以通过 inspect 查看挂载信息 ~]# docker inspect tomcat2"HostConfig": { "Binds": [ "tomcat1:/usr/local/tomcat/webapps/" ], ..... }"Mounts": [{"Type": "volume", "Name": "tomcat2","Source": "/var/lib/docker/volumes/tomcat2/_data","Destination": "/usr/local/tomcat/webapps","Driver": "local", "Mode": "z", "RW": true, "Propagation": ""}],4、删除容器 ~]# docker stop tomcat2 ~]# docker rm tomcat2 ~]# ls /var/lib/docker/volumes/tomcat2/_data/ <--自动创建的也不会同时删除
-
示例3 - 仅指定挂载容器管理卷 只用-v
1、挂载到容器上 , 指定tomcat3名, -P是容器自动映射端口 -v指定挂载的容器卷 ~]# docker run -d --name tomcat3 -P -v tomcat3:/usr/local/tomcat/webapps/ edf8233555ae2、我们也可以通过 inspect 查看挂载信息 ~]# docker inspect tomcat2"HostConfig": { "Binds": null , ..... }"Mounts": [{"Type": "volume", "Name": "b4137e424...","Source": "/var/lib/docker/volumes/b4137e424.../_data","Destination": "/usr/local/tomcat/webapps","Driver": "local", "Mode": "", "RW": true, "Propagation": ""}],# 跟示例2一样执行3,4 这里也不会删除管理卷
-
总结
- 示例1,2,3 不管是手动 create volume 还是 直接 -v 指定使用,删除容器的时候存储卷都会被保存
- 使用存储卷时应当见名知意,否则自动生成的卷组名称下次就不知道是啥了
- ⚠️ 示例1,2,3的mounts.type类型都是volume卷组
Bind mount volume
-
特性
- 管理方式: 用户指定宿主机任意目录/文件路径
- 创建方式:运行容器时直接指定
-v 宿主机路径:容器内路径
- 适用场景:需要指定存储位置(如挂载独立磁盘目录)
- 灵活性: 路径完全由用户控制,更灵活但需手动维护
-
示例1
1、准备本地资源 ~]# mkdir tomcat/ROOT -p ~]# echo "hello world bind" > tomcat/ROOT/index.html2、挂载容器, 只需要挂载到tomcat目录,tomcat:webapps同级,操作完之后直接访问 http://ip:自动映射端口 ~]# docker run -d --name tomcatb1 -P -v /root/tomcat:/usr/local/tomcat/webapps edf8233555ae3、我们也可以通过 inspect 查看挂载信息 ~]# docker inspect tomcatb1"HostConfig": { "Binds": [ "/root/tomcat:/usr/local/tomcat/webapps" ], ..... }"Mounts": [{"Type": "bind","Source": "root/tomcat","Destination": "/usr/local/tomcat/webapps","Driver": "", "Mode": "", "RW": true, "Propagation": "rprivate"}],
核心差异概述
来源AI
Docker-Managed Volume 和 Bind Mount Volume 都是 Docker 中用于数据持久化的机制,但它们在管理方式、存储位置、生命周期和数据行为上存在显著区别。简而言之:
- Docker-Managed Volume:由 Docker 自动管理,存储在 Docker 的默认目录中(如
/var/lib/docker/volumes
),更适合需要数据独立性和安全性的场景。 - Bind Mount Volume:直接绑定宿主机的指定路径到容器,数据与宿主机实时同步,适用于开发调试或需要直接访问宿主机文件的场景。
详细差异分析
-
定义与管理方式:
-
Docker-Managed Volume: docker原生管理的数据卷类型,当创建 Volume 时,Docker 会自动在宿主机上的
/var/lib/docker/volumes
目录下生成和管理子目录。这种方式称为 “Docker-Managed Volume”,因为它完全由 Docker 控制,无需用户手动指定宿主机路径-
示例语法
docker run -v 卷组名:容器挂载点 镜像名
-
-
Bind Mount Volume:这是一种直接挂载机制,将宿主机的特定路径(如用户自定义目录)映射到容器内部。它不依赖 Docker 的内部管理,而是基于 Linux 的 mount 系统调用实现。数据完全由宿主机文件系统控制
-
示例语法:
docker run -v 本地目录:挂载点 镜像名/id
-
-
关键区别:
- Volume 是 Docker 托管的,更抽象和安全;
- Bind Mount 更底层,直接暴露宿主机文件系统。
-
-
存储位置:
- Docker-Managed Volume:数据存储在 Docker 管理的固定位置(默认
/var/lib/docker/volumes
),用户无需关心具体路径。Docker 负责创建和维护这些目录。 - Bind Mount Volume:数据存储在用户指定的宿主机绝对路径下(如
/home/user/data
)。这意味着用户可以完全控制文件位置,但也增加了路径错误的风险。 - 影响:
- Volume 更适合生产环境,确保数据隔离;
- Bind Mount 适合开发时快速访问宿主机文件。
- Docker-Managed Volume:数据存储在 Docker 管理的固定位置(默认
-
生命周期与数据持久性:
- Docker-Managed Volume:生命周期独立于容器。即使容器被删除或重启,Volume 数据仍然保留在宿主机上,除非用户显式删除 Volume(使用
docker volume rm
)。当容器首次挂载时,如果宿主机目录为空,Docker 会从容器复制数据到 Volume。 - Bind Mount Volume:生命周期与容器紧密绑定。容器停止后,宿主机数据仍存在(因为它属于宿主机文件系统),但挂载关系会解除。重新挂载时,数据直接从宿主机加载。如果宿主机路径不存在,Docker 会创建它,但这可能导致容器内原有文件被覆盖或清空。
- 关键区别:
- Volume 提供更好的数据持久性和独立性;
- Bind Mount 更易导致数据不一致(例如,宿主机路径变化会影响容器)。
- Docker-Managed Volume:生命周期独立于容器。即使容器被删除或重启,Volume 数据仍然保留在宿主机上,除非用户显式删除 Volume(使用
-
数据行为与一致性:
- Docker-Managed Volume:不会覆盖容器内原有文件。当挂载时,如果 Volume 为空,Docker 会将容器内的文件复制到 Volume;之后的数据修改在 Volume 和容器间同步,但不会影响容器镜像的原始内容。卸载后,容器文件恢复原状。
- Bind Mount Volume:会直接覆盖容器内的目录或文件。挂载后,容器内路径的内容被宿主机路径的内容替代。卸载时,容器内原有文件会还原(如果宿主机数据被修改,容器内数据也会同步)。这可能导致数据丢失风险,特别是在路径配置错误时。
- 高级特性:
- Bind Mount 支持额外参数如
bind-propagation=rslave
,用于控制挂载传播(如共享子挂载),而 Volume 不支持此类参数。
- Bind Mount 支持额外参数如
-
性能与适用场景:
- Docker-Managed Volume:性能较高,因为 Docker 优化了存储访问。适用于需要数据备份、迁移或多容器共享的场景(如数据库持久化)。
- Bind Mount Volume:性能依赖于宿主机文件系统,可能略低。适用于开发环境,例如实时编辑代码(宿主机修改即时反映在容器中),但不推荐用于生产环境,因为它降低了可移植性。
共享卷组
Docker 的
--volumes-from
用于实现容器间数据共享,通过挂载其他容器的数据卷,使当前容器可访问目标容器中已配置的卷(非所有文件),且共享方向为单向继承,父容器删除不影响已共享数据。
-
核心作用与共享对象
核心是让一个容器共享另一个容器中已定义的数据卷(即通过
-v
挂载的目录或命名卷),而非容器内所有文件 例如,若容器 A 挂载了/volume01
和/volume02
两个数据卷,容器 B 通过--volumes-from A
启动后,仅会共享这两个目录,其他未挂载的文件不会同步 -
使用规则与特性
规则/特性 说明 共享方向 单向继承:B 共享 A 的数据卷,A 不会共享 B 的新增卷 多容器共享 支持链式共享,如 C 可通过 --volumes-from B
间接共享 A 的数据卷父容器删除影响 父容器删除后,子容器仍可访问共享卷数据(因卷本质挂载于宿主机) 权限继承 子容器继承父容器对数据卷的读写权限(如父卷为只读,子容器也只读) -
示例
# 创建名为share1的卷组 ]# docker volume create share1# 语法说明 -d 后台 -P 大P随机映射端口 -v指定volume --name指定名称 ed...镜像id ]# docker run -d -P -v share1:/usr/local/tomcat/webapps --name tomcats1 edf8233555ae# --volumes-from tomcats1 多容器共享 ]# docker run -d -P --volumes-from tomcats1 --name tomcats2 edf8233555ae ]# docker run -d -P --volumes-from tomcats2 --name tomcats3 edf8233555ae# tomcats1 挂载 share1卷组, tomcats2共享tomcats1, tomcats3共享tomcats2 # 生成一个 jsp页面,代码最下面,也可以自已用ai生成玩玩, 用浏览器就能看到 对应容器主机名了# 干掉 tomcats2 用来查看 链式共享是否还正常 docker rm $(docker stop tomcats2) # 删完之后 tomcats3 父容器删除无影响# 这样其实会产生一个问题, 如果A在写时 B也在写,那么A先保存然后B在保存,B会覆盖掉A的数据, 这里如果写入时需要保证写入的次序问题
-
备份\恢复
# 说明 --rm 这种备份的我们一次性用, 卷组共享tomcast3, -v 本地挂载到容器backup目录 , tar zcf /backup/backup.tar.gz -C /usr/local/tomcat/webapps ROOT ,不要目录只备份ROOT目录 docker run --rm --volumes-from tomcats3 -v ./:/backup --name tomcats4 edf8233555ae tar zcf /backup/backup.tar.gz -C /usr/local/tomcat/webapps ROOT# 这个可以用来临时更新开发给的一些代码,如果你没有ci/cd工具用于发布,手动搞的话 # 恢复也一样,先挂载数据卷容器, 然后 tar xf 解压到指定目录 docker run --volumes-from data -v $(pwd):/backup --name w2 ubuntu tar xf /backup/backup.tar.gz
vi /var/lib/docker/volumes/share1/_data/ROOT/index.jsp
<%@ page import="java.net.InetAddress" %>
<%@ page import="java.io.IOException" %>
<%String hostname = "unknown";try {hostname = InetAddress.getLocalHost().getHostName(); } catch (Exception e) {hostname = "error: " + e.getMessage(); }
%><!DOCTYPE html>
<html>
<head><title>Tomcat Container Hostname</title><style>body { font-family: Arial, sans-serif; text-align: center; margin-top: 100px; }.hostname { font-size: 2em; color: #333; }</style>
</head>
<body><h1> Tomcat docker</h1><p> the docker id name: </p><div class="hostname"><%= hostname %></div>
</body>
</html>