Docker 容器在运行时会产生大量数据,这些数据如何持久化和管理是一个重要的话题。 本节我们将通过一个 Nginx Web 服务器的案例,来深入探讨 Docker 的三种数据管理方式。
Docker 提供了三种主要的数据管理方式:
让我们通过一个 Nginx Web 服务器的例子来理解这三种方式的区别。我们将在每种方式下执行相同的操作:创建一个 HTML 文件,然后测试数据的持久性。
在这个场景中,我们直接在容器内创建文件,看看数据会发生什么:
# 运行一个 nginx 容器
docker run -d --name web-default -p 8000:80 nginx
# 在容器中创建一个测试页面
docker exec -it web-default /bin/sh
echo "<h1>Hello from Default Storage</h1>" > /usr/share/nginx/html/index.html
exit
# 访问页面验证内容
curl http://localhost:8000
# 容器中文件目录获取
# 背景介绍:
# 容器目录是在宿主机目录下的一个子目录,通过chroot的linux技术实现;
# 容器的目录对宿主机隔离,一般禁止访问;
# 该命令用于获取 Docker 容器在宿主机上的实际存储路径。当容器使用联合文件系统(如 overlay2)时,容器文件系统由多个层组成, MergedDir 就是这些层最终合并后在宿主机上的实际目录路径
docker inspect -f '{{.GraphDriver.Data.MergedDir}}' <container_id>
cat 容器目录/usr/share/nginx/html/index.html
# 删除容器
docker stop web-default
docker rm web-default
# 用同样的配置重新运行容器
docker run -d --name web-default-2 -p 8000:80 nginx
# 再次访问页面,内容不存在
curl http://localhost:8000
Docker 使用 OverlayFS(联合文件系统) 来管理容器的文件系统,它由多个层组成:
┌─────────────────────────────────┐ │ 容器可写层(Container Layer) │ ← 你在容器中创建/修改的文件都在这里 │ 生命周期 = 容器生命周期 │ ← 容器删除时,这一层也被删除! ├─────────────────────────────────┤ │ 镜像只读层 3(Nginx 配置) │ ├─────────────────────────────────┤ │ 镜像只读层 2(Nginx 程序) │ ← 这些层是只读的,多个容器共享 ├─────────────────────────────────┤ │ 镜像只读层 1(基础系统) │ └─────────────────────────────────┘
工作原理:
docker run 时,Docker 在镜像层之上创建一个可写层docker rm 删除容器时,可写层随之删除,所有修改都丢失这就是为什么:
web-default 中创建的 index.html 存储在容器的可写层web-default 后,可写层被清除web-default-2 是一个全新的容器,有自己的空白可写层,所以看不到之前的文件Volume 和 Bind Mount 的本质:它们都是绕过可写层,将数据直接存储在容器外部(宿主机),因此不受容器生命周期影响。
在这个场景中,我们将主机上的目录直接挂载到容器中:
# 创建本地目录
mkdir nginx-content
# 运行 Nginx 容器并挂载本地目录
docker run -d --name web-bind \
-p 8081:80 \
-v $(pwd)/nginx-content:/usr/share/nginx/html nginx
# 在容器中创建一个测试页面
docker exec -it web-bind sh -c 'echo "<h1>Hello from Bind Mounts</h1>" > /usr/share/nginx/html/index.html'
# 访问页面验证内容
curl http://localhost:8081
# 删除容器
docker rm -f web-bind
# 用同样的配置重新运行容器
docker run -d --name web-bind-2 -p 8081:80 \
-v $(pwd)/nginx-content:/usr/share/nginx/html nginx
# 再次访问页面,内容仍然存在
curl http://localhost:8081
在这个场景中,我们使用 Docker 管理的卷来存储数据:
# 创建一个 Docker volume
docker volume create nginx_data
# 运行 Nginx 容器并挂载卷
docker run -d --name web-volume -p 8082:80 -v nginx_data:/usr/share/nginx/html nginx
# 在容器中创建一个测试页面
docker exec -it web-volume sh -c 'echo "<h1>Hello from Volume Storage</h1>" > /usr/share/nginx/html/index.html'
# 访问页面验证内容
curl http://localhost:8082
# 删除容器
docker rm -f web-volume
# 用同样的配置重新运行容器
docker run -d --name web-volume-2 -p 8082:80 \
-v nginx_data:/usr/share/nginx/html nginx
# 再次访问页面,内容仍然存在
curl http://localhost:8082
# 查看卷的详细信息
docker volume ls
docker volume inspect nginx_data
默认存储
Bind Mount
Volume
完成实验后,可以进行清理:
# 清理容器
docker rm -f web-default web-volume web-volume-2 web-bind web-bind-2
# 清理卷
docker volume rm nginx_data
# 清理本地目录
rm -rf nginx-content
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 数据库持久化 | Volume | 性能好、Docker 管理、便于备份 |
| 开发环境代码同步 | Bind Mount | 实时修改、IDE 友好 |
| 配置文件注入 | Bind Mount(只读) | 安全、灵活 |
| 临时缓存 | tmpfs | 内存存储、容器删除即清理 |
| 容器日志 | 默认 + 日志驱动 | 避免存储膨胀 |
我们将通过一个 MySQL 数据库的例子来演示如何使用 Volume 持久化数据。
# 创建一个数据卷,名称为 mysql_data
docker volume create mysql_data
# 列出所有卷
docker volume ls
# 查看卷信息
docker volume inspect mysql_data
# 运行 MySQL 容器并挂载卷
# 备注:MYSQL_ROOT_PASSWORD 是环境变量,用于设置 MySQL 的 root 用户密码
docker run -d \
--name mysql_db \
-e MYSQL_ROOT_PASSWORD=mysecret \
-v mysql_data:/var/lib/mysql \
mysql:8.0
# 进入容器创建测试数据
docker exec -it mysql_db mysql -uroot -pmysecret -h127.0.0.1
# 在 MySQL 中创建测试数据
mysql> CREATE DATABASE test_db;
mysql> USE test_db;
mysql> CREATE TABLE users (id INT, name VARCHAR(50));
mysql> INSERT INTO users VALUES (1, 'John Doe');
mysql> exit
# 删除原容器
docker rm -f mysql_db
# 使用同一个卷启动新容器
docker run -d \
--name mysql_db2 \
-e MYSQL_ROOT_PASSWORD=mysecret \
-v mysql_data:/var/lib/mysql \
mysql:8.0
# 验证数据是否存在
docker exec -it mysql_db2 \
mysql -uroot -pmysecret -e "USE test_db; SELECT * FROM users;"