五、Docker 核心技术:容器数据持久化之数据卷

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 别把重要数据直接放进Docker容器里,因为容器就像一辆“临租车”,车一还(容器被删除),落在里面的东西就全没了。正确的做法是使用数据卷 (Volume),它好比一个属于你自己的、可插拔的“移动硬盘”。你可以把这个“硬盘”(具名数据卷)挂载到任何一辆“临租车”(容器)上使用。这样一来,就算车换了,你的数据也安然无恙,完美解决了数据库等应用的数据持久化问题。

Docker 容器本身是无状态且生命周期短暂的。当一个容器被删除时,它在可写层产生的所有数据都会随之消失。这对于需要持久化存储数据的应用 (如数据库、日志系统、用户上传内容) 来说是不可接受的。为了解决这个问题,Docker 提供了多种数据持久化方案,其中最重要、最推荐的就是数据卷

一、什么是容器数据卷

数据卷宿主机文件系统中一个 特殊的目录,它 由 Docker 管理 ( /var/lib/docker/volumes/ 目录下),并可以 直接映射一个或多个容器的 指定目录下。

数据卷的核心优势:
> 数据持久化:数据卷的 生命周期独立于 任何容器。即使 所有使用该数据卷的容器 都被删除,数据卷及其中的 数据依然 存在
数据共享多个容器可以 同时挂载同一个数据卷,从而 实现容器间的 数据共享和同步
高性能:数据卷 绕过了容器的 联合文件系统 (UnionFS),直接 读写宿主机的文件系统,具有 接近原生的I/O性能。
易于管理:Docker 提供了 专门的命令 ( docker volume ...) 来 创建、查看、删除数据卷,便于 备份、迁移和恢复

## 二、数据卷的使用

创建或运行容器时,我们主要使用 -v--mount 标志来挂载数据卷-v 语法更简洁--mount 语法更明确推荐在生产环境和复杂场景下使用 --mount

1. 匿名挂载

如果你在 -v 标志中只指定容器内的路径,Docker 会自动创建一个匿名的数据卷,并将其挂载到该路径。

语法:
bash -v /path/in/container

代码案例:
bash docker run -d -P --name nginx-anon -v /usr/share/nginx/html nginx
这个命令会创建一个新的、名字是随机哈希值的数据卷,并挂载到容器的 /usr/share/nginx/html 目录。 我们可以通过 docker inspect 查看这个 匿名数据卷具体信息

bash docker inspect nginx-anon
image.png


在输出的 "Mounts" 部分,你会看到类似这样的信息:
json "Mounts": [ { "Type": "volume", "Name": "a1b2c3d4...", // 随机生成的长哈希值 "Source": "/var/lib/docker/volumes/a1b2c3d4.../_data", "Destination": "/usr/share/nginx/html", ... } ]
image.png


缺点:匿名挂载的数据卷名称不直观,难以管理和复用

### 2.具名挂载
这是最推荐的数据卷使用方式。你可以为数据卷指定一个有意义的名称,方便后续的引用、共享和管理

语法:
> -v 方式: volume-name:/path/in/container
> --mount 方式: type=volume,source=volume-name,target=/path/in/container

*代码案例:

步骤一:创建具名数据卷 (可选,Docker会在挂载时自动创建)

docker volume create my-nginx-data

步骤二:使用具名数据卷运行容器

  • 使用 -v 标志:
    docker run -d -P --name nginx-named-v -v my-nginx-data:/usr/share/nginx/html nginx
    
  • 使用 --mount 标志 (推荐):
    docker run -d -P --name nginx-named-mount --mount type=volume,source=my-nginx-data,target=/usr/share/nginx/html nginx
    
  • 在这两个例子中,名为 my-nginx-data数据卷挂载到了容器的 /usr/share/nginx/html 目录。
  • 现在,你可以删除并重建 nginx-named-vnginx-named-mount 容器,但只要重新挂载 my-nginx-data 数据卷,网站的数据 (如 index.html) 就会保持不变

数据卷管理命令:

# 列出所有数据卷
docker volume ls

# 查看某个数据卷的详细信息
docker volume inspect my-nginx-data

# 删除一个数据卷 (前提是没有容器正在使用它)
docker volume rm my-nginx-data

# 删除所有不再被任何容器使用的悬空数据卷 (dangling volumes)
docker volume prune

image.png

image.png

3. 数据卷 vs 绑定挂载

除了由 Docker 管理的数据卷,Docker 还支持另一种强大的挂载方式——绑定挂载。它允许我们将宿主机上任意的一个文件或目录直接映射到容器中。

绑定挂载语法 (使用 -v):
bash -v /path/on/host:/path/in/container
与数据卷的核心区别及选择:
特性 数据卷 绑定挂载
管理方 由 Docker 管理,位于 Docker 的专用存储区域 (/var/lib/docker/volumes/)。 由用户管理,可以是宿主机文件系统中的任意路径
可移植性 。数据卷的定义与宿主机的目录结构无关,便于在不同环境中迁移。 。依赖于宿主机上特定的目录结构,不易迁移。
性能 在 Linux 上通常性能更高,因为它为数据I/O进行了优化。 性能也很好,但可能受宿主机文件系统权限等因素影响。
权限 Docker 自动处理权限。 可能存在宿主机与容器内用户权限不匹配的问题。
适用场景 推荐用于生产环境和所有需要持久化应用数据的场景,如数据库文件、应用日志等。 适用于开发环境,如将源代码目录挂载到容器中进行实时代码调试;或共享宿主机配置文件到容器。
代码示例:直观感受数据卷与绑定挂载的行为差异

这个示例将清晰地展示数据卷独立于容器生命周期的特性,以及绑定挂载下宿主机与容器实时同步

场景一:使用数据卷的持久性演示

  1. 运行一个容器,使用具名数据卷并写入数据:
    docker run -d --name vol-test-container -v my-persistent-data:/data ubuntu sleep infinity
    docker exec vol-test-container sh -c "echo 'This data is in a volume' > /data/message.txt"
    
    image.png
  • 此时,名为 my-persistent-data 的数据卷中已经包含了 message.txt 文件。
  1. 在容器内删除文件,验证宿主机数据卷不受影响:(为模拟容器内误操作)
docker exec vol-test-container rm /data/message.txt

验证容器内文件已删除

docker exec vol-test-container ls /data

image.png

(此时应无输出)

关键点:此时 my-persistent-data 这个数据卷本身在宿主机上仍然包含 message.txt 文件。容器的删除操作仅仅是在容器的可写层记录了“该文件已删除”的标记并未真正删除数据卷中的源文件。

  1. 删除容器,然后创建一个新容器挂载同一个数据卷:
    ```bash
    docker stop vol-test-container
    docker rm vol-test-container

创建一个全新的容器,挂载之前的数据卷

docker run --name vol-test-checker -it -v my-persistent-data:/data ubuntu


4.  **在新容器中查看数据:**
    当你进入 `vol-test-checker` 容器的交互式终端后,查看 `/data` 目录:
```bash
# 在 vol-test-checker 容器的shell中执行
ls /data
# 输出应为:message.txt

cat /data/message.txt
# 输出应为:This data is in a volume

结论:这个实验有力地证明了,数据卷中的数据是独立且持久的。即使容器内的文件被看似“删除”,或者整个容器删除,数据卷中的原始数据安然无恙,可以被新的容器重新挂载和使用。

场景二:使用绑定挂载的实时同步演示

  1. 在宿主机上创建一个目录和文件:

    mkdir -p ./host-data
    echo "Initial data from host" > ./host-data/sync.txt
    
  2. 运行一个容器,将宿主机目录绑定挂载到容器中:

    docker run -d --name bind-test-container -v $(pwd)/host-data:/data ubuntu tail -f /dev/null
    
    • tail -f /dev/null 是一个让容器保持运行的技巧。
    • 现在,宿主机的 ./host-data 目录与容器的 /data 目录实时同步

image.png

  1. 验证容器内可以看到宿主机文件:
    docker exec bind-test-container cat /data/sync.txt
    # 输出应为:Initial data from host
    

在这里插入图片描述

  1. 宿主机上修改文件内容:
    echo "Host updated the file" >> ./host-data/sync.txt
    
  1. 容器内立即查看变化:
    docker exec bind-test-container cat /data/sync.txt
    # 输出现在应包含两行:
    # Initial data from host
    # Host updated the file
    
    在这里插入图片描述
  1. 容器内删除文件:

    docker exec bind-test-container rm /data/sync.txt
    
  2. 宿主机上验证文件是否也被删除:

    ls ./host-data/
    # (此时应无输出,文件已被删除)
    

    在这里插入图片描述

    结论:绑定挂载建立了宿主机和容器之间文件系统的直接链接。任何一方对挂载目录中内容的修改或删除,都会立即、真实地反映在另一方。这种实时同步的特性使其非常适合开发时共享源代码

    4. 综合案例:使用具名数据卷持久化 MySQL 数据

这个案例将演示如何创建一个 MySQL 容器,并将其数据目录 /var/lib/mysql 持久化到一个具名数据卷中,从而实现数据库数据的安全存储

步骤一:创建具名数据卷
为了清晰管理,我们先创建一个名为 mysql-data 的数据卷。

docker volume create mysql-data

步骤二:运行 MySQL 容器并挂载数据卷
我们将运行一个 MySQL 8.0 容器,并设置 root 密码

docker run -d \
--name my-mysql \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=123456 \
--mount type=volume,source=mysql-data,target=/var/lib/mysql \
mysql:8.0
  • -d: 后台运行容器。
  • --name my-mysql: 为容器命名。
  • -p 3306:3306: 将宿主机的 3306 端口映射到容器的 3306 端口。
  • -e MYSQL_ROOT_PASSWORD=...: 通过环境变量设置 MySQL 的 root 用户密码。
  • --mount ...: 核心部分。将我们创建的 mysql-data 数据卷挂载到容器内部存放数据库文件标准路径 /var/lib/mysql

步骤三:验证数据持久化

  1. 进入容器并创建数据
    使用 docker exec 进入正在运行的 MySQL 容器,并登录到数据库。

    docker exec -it my-mysql mysql -uroot -pmysecretpassword
    

    在 MySQL 命令行中,创建一个新的数据库和表,并插入一些数据

    CREATE DATABASE testdb;
    USE testdb;
    CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(50));
    INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob');
    EXIT;
    

    在这里插入图片描述
    主机也能正常连接上
    在这里插入图片描述

  2. 删除容器
    现在,我们模拟一次容器故障或升级删除这个 MySQL 容器。

    docker stop my-mysql
    docker rm my-mysql
    

    此时,容器已经不存在了。

  3. 重新创建容器,挂载同一个数据卷
    我们再次运行一个 MySQL 容器,使用相同的命令,确保它挂载的还是 mysql-data 这个数据卷。

    docker run -d \
    --name my-mysql-new \
    -p 3306:3306 \
    -e MYSQL_ROOT_PASSWORD=mysecretpassword \
    --mount type=volume,source=mysql-data,target=/var/lib/mysql \
    mysql:5.7
    
  4. 验证数据是否恢复
    等待新容器 my-mysql-new 完全启动后,再次进入这个新容器。

    docker exec -it my-mysql-new mysql -uroot -pmysecretpassword
    

    在 MySQL 命令行中,检查我们之前创建的数据库和数据是否存在。

    USE testdb;
    SELECT * FROM users;
    

    你会看到输出

    +----+-------+
    | id | name  |
    +----+-------+
    |  1 | Alice |
    |  2 | Bob   |
    +----+-------+
    

    证明了,即使容器被删除存储在数据卷中的数据被完美地保留了下来,并在新容器得以恢复

总结: 容器数据卷是 实现Docker数据持久化首选方案。通过 使用具名数据卷,我们可以 安全地存储 应用数据解耦数据与容器的 生命周期,并 轻松实现数据的 共享、备份和恢复,为在 生产环境运行有状态应用提供了 坚实的基础

---

## 练习题

题目一:创建与查看数据卷
写出一条命令,创建一个名为 app-config 的具名数据卷,然后写出另一条命令来查看这个数据卷的详细信息。

题目二:匿名挂载
写出一条命令,以后台模式运行一个 ubuntu 容器,并为容器内的 /data 目录进行匿名挂载。

题目三:具名挂载 (使用 -v)
写出一条命令,以后台模式运行一个名为 my-redisredis 容器,并使用 -v 标志将一个名为 redis-data 的具名数据卷挂载到容器的 /data 目录。

题目四:具名挂载 (使用 --mount)
使用 --mount 标志重写上一题的命令,实现完全相同的效果。

题目五:绑定挂载
写出一条命令,运行一个临时的、交互式的 alpine 容器,并将宿主机当前目录下的 app 子目录 (假设为 ./app) 绑定挂载到容器的 /app 目录。容器启动后执行 ls /app 命令。

题目六:数据共享

  1. 首先,运行一个名为 writer-containerbusybox 容器,将一个名为 shared-volume 的数据卷挂载到 /shared。容器启动后,向 /shared/message.txt 文件写入 "Hello from writer"。
  2. 然后,运行另一个名为 reader-containerbusybox 容器,同样挂载 shared-volume 数据卷到 /shared,并读取 /shared/message.txt 文件的内容。
    (请分别写出这两个 docker run 命令)

题目七:数据卷清理
写出一条命令,可以一次性删除所有当前未被任何容器使用的Docker数据卷。

题目八:数据卷数据备份
假设 mysql-data 数据卷中包含了重要的数据库文件,你希望对其进行备份。请描述一种简单的、利用另一个临时容器来备份该数据卷中所有文件到宿主机 /backup 目录的思路或命令。

答案与解析

答案一:
创建数据卷:

docker volume create app-config

查看详细信息:

docker volume inspect app-config

解析: docker volume create 用于创建具名数据卷,docker volume inspect 用于查看其元数据,包括在宿主机上的实际存储路径。

答案二:

docker run -d --name ubuntu-anon -v /data ubuntu

解析: -v 标志后只跟了容器内的路径 /data,这会触发Docker创建一个匿名数据卷并挂载到此路径。

答案三:

docker run -d --name my-redis -v redis-data:/data redis

解析: -v 标志使用 [volume_name]:[container_path] 的格式来进行具名挂载。如果 redis-data 数据卷不存在,Docker会自动创建它。

答案四:

docker run -d --name my-redis --mount type=volume,source=redis-data,target=/data redis

解析: --mount 标志使用更明确的键值对语法。type=volume 指定类型,source 指定数据卷名称,target 指定容器内路径。

答案五:

docker run --rm -it --mount type=bind,source=$(pwd)/app,target=/app alpine ls /app

解析: --mount type=bind 指定了绑定挂载。source=$(pwd)/app 表示宿主机当前工作目录下的 app 目录。--rm 使容器退出后自动删除,-it 提供交互式终端。容器启动后直接执行 ls /app 命令。

答案六:

  1. 运行 writer-container
    docker run --name writer-container -v shared-volume:/shared busybox sh -c "echo 'Hello from writer' > /shared/message.txt"
    
  2. 运行 reader-container
    docker run --name reader-container -v shared-volume:/shared busybox cat /shared/message.txt
    

    解析: 两个容器都挂载了同一个具名数据卷 shared-volume。第一个容器向卷中写入文件,第二个容器可以立即读取到这个文件,实现了数据共享。

答案七:

docker volume prune

解析: docker volume prune 是一个方便的命令,用于清理不再被任何(包括已停止的)容器引用的数据卷,可以释放磁盘空间。

答案八:

docker run --rm \
--mount type=volume,source=mysql-data,target=/dbdata,readonly \
--mount type=bind,source=/backup,target=/backup_host \
ubuntu \
tar czvf /backup_host/mysql-backup-$(date +%Y%m%d).tar.gz -C /dbdata .
目录
相关文章
|
20天前
|
关系型数据库 MySQL Shell
三、Docker常用命令
把 Docker 玩转,就像一个建筑师,需要掌握两套核心工具:一套用来管理你的“图纸”(镜像),另一套用来管理你用图纸盖好的“房子”(容器)。
191 2
|
18天前
|
Java 开发者
Java高级技术深度解析:性能优化与架构设计
本文深入解析Java高级技术,涵盖JVM性能调优、并发编程、内存模型与架构设计。从G1/ZGC垃圾回收到CompletableFuture异步处理,剖析底层机制与实战优化策略,助力构建高性能、高可用的Java系统。
159 47
|
10天前
|
消息中间件 安全 NoSQL
阿里云通过中国信通院首批安全可信中间件评估
近日,由中国信通院主办的 2025(第五届)数字化转型发展大会在京举行。会上,“阿里云应用服务器软件 AliEE”、“消息队列软件 RocketMQ”、“云数据库 Tair”三款产品成功通过中国信通院“安全可信中间件”系列评估,成为首批获此认证的中间件产品。此次评估覆盖安全可信要求、功能完备性、安全防护能力、性能表现、可靠性与可维护性等核心指标,标志着阿里云中间件产品在多架构适配与安全能力上达到行业领先水平。
337 198
|
19天前
|
IDE PHP 开发工具
PHP严格类型声明:告别类型“惊喜”的利器
PHP严格类型声明:告别类型“惊喜”的利器
188 115
|
19天前
|
PHP C语言 开发者
告别循环!用这些PHP数组函数提升你的代码效率
告别循环!用这些PHP数组函数提升你的代码效率
221 115
|
20天前
|
缓存 Java Maven
六、Docker 核心技术:Dockerfile 指令详解
想亲手给你的应用程序打造一个专属的“集装箱”吗?Dockerfile就是你的说明书!它其实就是一个简单的文本文件,你可以在里面像搭积木一样,用FROM、COPY、RUN这些指令,一步步告诉Docker如何打包你的应用。最后,通过多阶段构建的小技巧,还能给镜像“减肥”,让它变得轻巧又高效。快来学习用Dockerfile变身打包达人吧!
244 4
|
19天前
|
数据采集 API 数据处理
Python异步编程入门:告别卡顿,提升程序效率
Python异步编程入门:告别卡顿,提升程序效率
180 114
|
10天前
|
Java API 数据处理
掌握Java Stream API:告别繁琐循环,拥抱函数式编程
掌握Java Stream API:告别繁琐循环,拥抱函数式编程
161 118
|
7天前
|
存储 人工智能 前端开发
以无头 CMS 为核心构建适配全场景的内容中台
内容中台是品牌内容的数字家园,但只有具备灵活性、可扩展性和用户中心性才能发挥价值。无头CMS为搭建这样的内容中台提供了坚实基础,让团队摆脱传统平台的束缚,专注于核心——创作能引发共鸣、驱动成果的内容。
165 113
|
25天前
|
安全 Java API
超越基础:每个Java开发者都应了解的三个现代特性
超越基础:每个Java开发者都应了解的三个现代特性
225 118