第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) 极快(内存速度)
适用场景 生产环境、数据库、日志 开发调试、配置文件挂载 临时缓存、敏感信息(不落盘)
我的建议:生产环境优先使用Volume。Bind Mount虽然方便,但容易造成宿主机和容器之间的权限混乱。tmpfs只适合放那些「丢了也不心疼」的数据。

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名称,Docker会自动生成一个随机名字。我个人习惯手动命名,方便后续管理。

多个容器共享同一个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

这样做的好处是:你在宿主机上修改代码,容器里立刻就能感知到。开发时不用反复构建镜像,效率高很多。

注意:Bind Mount会覆盖容器内的原有目录。比如你把宿主机/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
避坑指南:我曾经在生产环境遇到过一个问题——某个服务把日志写到了tmpfs挂载的目录下,结果容器重启后日志全没了,排查问题根本找不到线索。所以,日志、数据库、用户上传文件等关键数据,千万别用tmpfs

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
我的经验:在Kubernetes环境中,我们通常使用PersistentVolume(PV)和PersistentVolumeClaim(PVC)来管理存储。Docker Compose或Swarm模式下,Volume就够用了。选型时先问自己:我的容器跑在几台机器上?如果只有一台,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的代码和插件还是用镜像自带的,更新版本时直接换镜像就行
重要提醒:千万别把整个WordPress目录都挂载成Volume!那样的话,你更新镜像时,新镜像里的代码会被旧数据覆盖掉。只挂载需要持久化的部分(比如uploads目录),其他保持镜像内的状态。

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网络,看看容器之间怎么通信。