docker存储卷

本章要点:docker存储卷,存储卷两种类型(volume,bind),使用示例,共享卷组,volume挂载nfs

Docker存储卷是实现容器数据持久化的核心机制,通过绑定宿主机目录与容器目录,解决数据与容器生命周期绑定的问题,支持跨主机数据共享。参考:官网示例Docker多主机环境下NFS数据卷配置详解]

存储卷基础概念

  • 为什么要使用存储卷

    假设我们通过镜像创建一个容器。容器一旦被销毁,容器内的数据将会被一并删除。但有些情况下,通过服务器上传的图片出会丢失。容器中的数据不是持久化状态的,此时我们就需要使用到存储卷。

  • 如果不使用数据卷存在的问题

    • 存储于联合文件系统中, 不易于宿主机访问;
    • 容器间数据共享 不便;
    • 删除容器其数据会丢失 ;
  • 基本概念

    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 volume ls 查看当前节点创建的卷组
    docker volume inspect 卷组名 查看卷组的详细信息
    docker volume create 卷组名 创建卷组的详细信息
    docker volume rm 卷组名 删除卷组

    ⚠️ 如果卷组被挂载,则无法被直接删除

  • 示例1 - 手动创建容器管理卷

    1、创建卷名
    ~]# docker volume create tomcat1
    
    2、挂载到容器上 , 指定tomcat1名, -P是容器自动映射端口  -v指定挂载的容器卷
    ~]# docker run -d --name tomcat1 -P -v tomcat1:/usr/local/tomcat/webapps/ edf8233555ae
    
    3、默认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# exit
    
    4、手动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/ edf8233555ae
    
    2、这里开始操作本地存储卷, 操作完之后直接访问 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/ edf8233555ae
    
    2、我们也可以通过 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.html
    
    2、挂载容器, 只需要挂载到tomcat目录,tomcat:webapps同级,操作完之后直接访问 http://ip:自动映射端口
    ~]# docker run -d --name tomcatb1 -P -v /root/tomcat:/usr/local/tomcat/webapps edf8233555ae
    
    3、我们也可以通过 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:直接绑定宿主机的指定路径到容器,数据与宿主机实时同步,适用于开发调试或需要直接访问宿主机文件的场景。
详细差异分析
  1. 定义与管理方式:

    • 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 更底层,直接暴露宿主机文件系统。
  2. 存储位置:

    • Docker-Managed Volume:数据存储在 Docker 管理的固定位置(默认 /var/lib/docker/volumes),用户无需关心具体路径。Docker 负责创建和维护这些目录。
    • Bind Mount Volume:数据存储在用户指定的宿主机绝对路径下(如 /home/user/data)。这意味着用户可以完全控制文件位置,但也增加了路径错误的风险。
    • 影响
      • Volume 更适合生产环境,确保数据隔离;
      • Bind Mount 适合开发时快速访问宿主机文件。
  3. 生命周期与数据持久性:

    • Docker-Managed Volume:生命周期独立于容器。即使容器被删除或重启,Volume 数据仍然保留在宿主机上,除非用户显式删除 Volume(使用 docker volume rm)。当容器首次挂载时,如果宿主机目录为空,Docker 会从容器复制数据到 Volume。
    • Bind Mount Volume:生命周期与容器紧密绑定。容器停止后,宿主机数据仍存在(因为它属于宿主机文件系统),但挂载关系会解除。重新挂载时,数据直接从宿主机加载。如果宿主机路径不存在,Docker 会创建它,但这可能导致容器内原有文件被覆盖或清空。
    • 关键区别
      • Volume 提供更好的数据持久性和独立性;
      • Bind Mount 更易导致数据不一致(例如,宿主机路径变化会影响容器)。
  4. 数据行为与一致性:

    • Docker-Managed Volume:不会覆盖容器内原有文件。当挂载时,如果 Volume 为空,Docker 会将容器内的文件复制到 Volume;之后的数据修改在 Volume 和容器间同步,但不会影响容器镜像的原始内容。卸载后,容器文件恢复原状。
    • Bind Mount Volume:会直接覆盖容器内的目录或文件。挂载后,容器内路径的内容被宿主机路径的内容替代。卸载时,容器内原有文件会还原(如果宿主机数据被修改,容器内数据也会同步)。这可能导致数据丢失风险,特别是在路径配置错误时。
    • 高级特性
      • Bind Mount 支持额外参数如 bind-propagation=rslave,用于控制挂载传播(如共享子挂载),而 Volume 不支持此类参数。
  5. 性能与适用场景:

    • 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
    

volume挂载nfs服务

nfs服务安装

  • nfs安装详见文档

  • 挂载示例

    # 官方给出的示例,按标准填就行
    docker volume create --driver local \
      --opt type=nfs \
      --opt o=addr=10.4.50.139,rw \
      --opt device=:/opt/volumetest \
      nfs1
    
    
  • 查看

    [
        {
            "CreatedAt": "2025-10-30T11:08:50+08:00",
            "Driver": "local",
            "Labels": null,
            "Mountpoint": "/var/lib/docker/volumes/nfs1/_data",
            "Name": "nfs1",
            "Options": {
                "device": "/opt/volumetest/",
                "o": "addr=10.4.50.139,rw",
                "type": "nfs"
            },
            "Scope": "local"
        }
    ]
    
  • 检查-示例1

    # 当我们创建完之后,本地目录跟挂载点目录都是空的,这里正常
    ~]# ls /var/lib/docker/volumes/nfs1/_data/
    ~]# ls /opt/volumetest/
    
    # 我们在创建一个空目录发现nfs没有挂载, 此时不管它,我们在创建容器
    ~]# mkdir /var/lib/docker/volumes/nfs1/_data/ROOT
    ~]# ls /opt/volumetest/
    ~]# 
    
    # 运行容器
    ~]# docker run -d -P -v nfs1:/usr/local/tomcat/webapps --name vv1 tomcat:8.5.100
    616ef926b7c23c7990b078d643-------
    
    # 检查上一步创建的目录会被nfs目录挂载
    ~]# ls /var/lib/docker/volumes/nfs1/_data/ROOT
    ls: 无法访问/var/lib/docker/volumes/nfs1/_data/ROOT: 没有那个文件或目录
    
    # 此时不管是在挂载或者volume卷中创建都会同步
    ~]# mkdir /var/lib/docker/volumes/nfs1/_data/ROOT
    ~]# ls /opt/volumetest/
    ROOT
    
  • 示例2

    ~]# docker rm $(docker stop vv1)
    # 保留上一步示例留下的ROOT目录,在vim index.jsp,代码在最下
    ~]# ls /opt/volumetest/ROOT/
    index.jsp
    ~]# docker run -d -P -v nfs1:/usr/local/tomcat/webapps --name vv1 tomcat:8.5.100
    08373c24f97be45----
    ~]# docker ps  # 查看映射的端口
    ~]# curl  http://127.0.0.1:10244/index.jsp
     <div class="hostname">08373c24f97b</div>
    
  • 总结一下

    • 直接创建的nfs1,如果没有挂载容器,那么它占用的是本地存储,只有当容器运行时才会挂载nfs
    • 使用nfs前,应当将资源放到nfs中而不是volume
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>
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐