第3章:Docker存储卷:Volume、Bind Mount、tmpfs,数据持久化与共享策略
聊到容器,很多人第一反应是「用完即弃」。确实,容器天生就是无状态的——你删掉一个容器,里面写的数据也就跟着没了。但现实中的业务哪有这么简单?数据库要存盘、日志要保留、配置文件要挂载、多个容器要共享数据……
所以这一章,我们来聊聊Docker的三种存储方案。我个人习惯把它们分成三类:Volume(卷)、Bind Mount(绑定挂载)、tmpfs(临时内存挂载)。每种方案都有它的适用场景,选错了,后面运维起来会非常痛苦。
3.1 为什么需要数据持久化?
先想一个问题:容器重启后,数据还在吗?
默认情况下,容器内的文件系统是临时的。你写进去的日志、上传的图片、数据库的记录,一旦容器被删除,全部消失。这显然不行。
我在项目中遇到过好几次这样的场景:开发同学在容器里调试,写了一大堆测试数据,结果不小心把容器删了……嗯,那种绝望的眼神我至今记得。
所以,数据持久化的核心目的就两个:
- 容器删除后,数据不丢失
- 多个容器之间,可以共享数据
说白了,就是把容器的「内部存储」和「外部存储」打通。
3.2 三种存储方案对比
先给个总览表格,方便你快速对比:
| 特性 | Volume | Bind Mount | tmpfs |
|---|---|---|---|
| 存储位置 | Docker管理(/var/lib/docker/volumes/) | 宿主机任意路径 | 内存(RAM) |
| 持久性 | 持久化 | 持久化 | 非持久化(容器停止即消失) |
| 共享能力 | 强(多个容器可挂载同一Volume) | 强(多个容器可挂载同一宿主机路径) | 弱(仅限单个容器) |
| 性能 | 中等(磁盘I/O) | 中等(磁盘I/O) | 极快(内存速度) |
| 适用场景 | 生产环境、数据库、日志 | 开发调试、配置文件挂载 | 临时缓存、敏感信息(不落盘) |
3.3 Volume:最推荐的持久化方案
Volume是Docker官方最推荐的方式。它由Docker自己管理,你不需要关心数据到底存在宿主机哪个目录下。
创建和使用Volume:
# 创建一个Volume
docker volume create my_data
# 查看所有Volume
docker volume ls
# 查看Volume详情
docker volume inspect my_data
# 使用Volume启动容器
docker run -d --name mysql \
-v my_data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=root \
mysql:8.0
你看,这里我把一个叫my_data的Volume挂载到了MySQL的数据目录/var/lib/mysql。这样即使容器被删了,数据还在Volume里,下次启动时重新挂载就行。
多个容器共享同一个Volume:
# 容器A写入数据
docker run -d --name writer \
-v shared_data:/data \
alpine sh -c "while true; do echo 'hello' >> /data/log.txt; sleep 5; done"
# 容器B读取数据
docker run -it --name reader \
-v shared_data:/data \
alpine cat /data/log.txt
这个场景我在微服务架构中经常用到。比如日志收集器需要读取多个服务写出的日志文件,用Volume共享就非常方便。
3.4 Bind Mount:开发调试的利器
Bind Mount说白了就是把宿主机上的一个目录或文件,直接映射到容器里。它不像Volume那样由Docker管理,而是你指定什么路径就是什么路径。
典型用法:
# 把当前目录挂载到容器的/app目录
docker run -d --name dev_app \
-v $(pwd):/app \
-p 8080:8080 \
node:18 npm start
这样做的好处是:你在宿主机上修改代码,容器里立刻就能感知到。开发时不用反复构建镜像,效率高很多。
/home/user/app挂载到容器的/app,那容器镜像里/app下的所有文件都会被隐藏。我曾经因为这个踩过坑——挂载后容器启动报错,查了半天才发现是镜像里的初始化脚本被覆盖了。
Bind Mount的权限问题:
嗯,这里要特别说一下。宿主机和容器的用户ID可能不一样。比如宿主机上文件属于uid=1000的用户,但容器里进程以uid=999运行,就会出现权限拒绝。
解决办法有两种:
- 在宿主机上调整文件权限(
chmod 777,但不安全) - 在容器启动时指定用户ID(
--user 1000:1000)
我个人更推荐第二种,安全且可控。
3.5 tmpfs:内存级别的临时存储
tmpfs是Docker提供的一种「不落盘」的存储方式。数据只存在于内存中,容器停止后数据就没了。
使用场景:
- 存放临时缓存文件(比如编译中间产物)
- 存放敏感信息(比如密钥、Token,避免写入磁盘)
- 需要极高读写性能的场景
# 挂载tmpfs到容器的/tmp目录,限制大小为100MB
docker run -d --name cache_app \
--tmpfs /tmp:size=100M \
nginx:latest
3.6 数据共享策略:多容器如何协作?
实际项目中,很少只有一个容器在跑。多个容器之间需要共享数据,常见的有三种模式:
模式一:共享Volume
多个容器挂载同一个Volume。适合日志收集、文件上传等场景。
docker volume create shared_uploads
docker run -d --name app1 -v shared_uploads:/uploads app_image
docker run -d --name app2 -v shared_uploads:/uploads app_image
模式二:数据容器(已过时,但仍有项目在用)
创建一个专门用来挂载Volume的容器,其他容器通过--volumes-from来继承。不过Docker官方现在更推荐直接使用Volume。
# 创建数据容器
docker create -v /data --name data_container busybox
# 其他容器继承
docker run --volumes-from data_container ubuntu ls /data
模式三:NFS或分布式存储
当容器分布在多台宿主机上时,Volume和Bind Mount就不够用了。这时候需要网络存储,比如NFS、Ceph、GlusterFS等。
# 使用NFS Volume(需要先安装nfs-common)
docker volume create --driver local \
--opt type=nfs \
--opt o=addr=192.168.1.100,rw \
--opt device=:/shared/data \
nfs_volume
3.7 实战:一个完整的存储方案设计
假设我们要部署一个WordPress博客,需要MySQL数据库和Web服务。来看看怎么设计存储:
# 创建两个Volume
docker volume create wp_db_data
docker volume create wp_uploads
# 启动MySQL,使用Volume持久化数据库
docker run -d --name wordpress_db \
-v wp_db_data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=wordpress \
mysql:8.0
# 启动WordPress,挂载上传目录到Volume
docker run -d --name wordpress_app \
-v wp_uploads:/var/www/html/wp-content/uploads \
-p 8080:80 \
--link wordpress_db:mysql \
wordpress:latest
这样设计的好处:
- 数据库数据存在
wp_db_data里,删容器也不怕 - 用户上传的图片存在
wp_uploads里,方便备份和迁移 - WordPress的代码和插件还是用镜像自带的,更新版本时直接换镜像就行
3.8 备份与恢复
数据持久化之后,备份就是必须考虑的事了。Volume的备份其实很简单:
# 备份Volume到宿主机
docker run --rm -v wp_db_data:/data -v $(pwd):/backup alpine \
tar czf /backup/wp_db_backup.tar.gz -C /data .
# 恢复Volume
docker run --rm -v wp_db_data:/data -v $(pwd):/backup alpine \
tar xzf /backup/wp_db_backup.tar.gz -C /data
这个思路就是:启动一个临时容器,把Volume挂载进去,然后用tar打包或解包。简单粗暴,但非常有效。
好了,关于Docker存储卷的内容就讲到这里。三种方案各有千秋,选型时记住一句话:生产用Volume,开发用Bind Mount,临时用tmpfs。下一章我们会聊聊Docker网络,看看容器之间怎么通信。