容器存储基础:Docker存储驱动、数据卷(Volume)原理、容器文件系统分层

聊到容器存储,很多人第一反应就是「数据放哪儿」。我刚开始接触 Docker 时也犯过迷糊——容器一重启,数据就丢了?这背后其实藏着三个核心概念:存储驱动、数据卷、还有文件系统分层。咱们一个一个拆开讲。

一、容器文件系统分层:镜像的「千层蛋糕」

Docker 镜像为什么能那么小?说白了,就是靠分层。

每个 Dockerfile 里的指令,比如 RUNCOPY,都会生成一个新层。这些层是只读的,叠加在一起就构成了镜像。你启动容器时,Docker 会在最上面加一个可写层——这就是容器层。

核心要点:

  • 镜像层:只读,多个容器可以共享
  • 容器层:可写,容器删除后数据消失
  • 写时复制(Copy-on-Write):修改文件时,先把文件从只读层复制到可写层再改

我在项目中遇到过一个问题:多个容器基于同一个镜像启动,每个容器修改了同一个配置文件。结果发现,每个容器其实都有自己的副本,互不影响。这就是分层的好处——节省磁盘,隔离性好。

我的习惯:docker history 查看镜像分层,能清楚看到每一层的大小和命令。排查镜像臃肿时特别好用。

二、Docker存储驱动:谁来管这些层?

分层机制听着不错,但谁来管理这些层的读写?这就是存储驱动干的事。

Docker 支持多种存储驱动,我挑几个常用的说说:

存储驱动 适用场景 我的评价
overlay2 Linux 默认推荐 稳定、性能好,现在基本都用它
aufs 老版本 Docker 已经被 overlay2 取代了
devicemapper CentOS/RHEL 早期 性能一般,配置复杂,不推荐
vfs 测试环境 没有写时复制,性能最差

我个人习惯用 overlay2。为什么?因为它利用 Linux 内核的 OverlayFS,性能损耗小,而且支持页缓存共享——多个容器读同一个文件时,内存里只有一份缓存。

避坑指南:我曾经在生产环境遇到过 devicemapper 的 loop-lvm 模式,磁盘性能惨不忍睹。后来换成 direct-lvm 才好转。如果你还在用 devicemapper,赶紧迁移到 overlay2 吧。

查看当前存储驱动很简单:

docker info | grep "Storage Driver"

输出类似:

Storage Driver: overlay2

三、数据卷(Volume):持久化的正确姿势

容器层的数据会随容器删除而消失。那怎么办?用数据卷。

数据卷的本质,就是把宿主机上的一个目录挂载到容器里。容器读写这个目录,数据直接落在宿主机上。容器删了,数据还在。

Docker 提供了三种挂载方式:

  • Volume(数据卷):由 Docker 管理,存放在 /var/lib/docker/volumes/
  • Bind mount(绑定挂载):挂载宿主机任意路径
  • tmpfs mount(临时挂载):数据存在内存中,容器停止后消失

你想想看,哪种最推荐?我个人强烈建议用 Volume。原因有三:

  1. Docker 帮你管理路径,不用操心权限问题
  2. 跨主机迁移方便,Volume 可以配合存储插件
  3. 备份和恢复更简单

创建和使用 Volume 的示例:

# 创建数据卷
docker volume create mydata

# 启动容器并挂载
docker run -d --name web -v mydata:/app/data nginx

# 查看数据卷信息
docker volume inspect mydata

关键区别:

  • -v mydata:/app/data:使用 Volume,Docker 管理
  • -v /host/path:/app/data:使用 Bind mount,宿主机路径

嗯,这里要注意:Bind mount 虽然灵活,但容易出权限问题。我曾经帮一个同事排查过——他把宿主机上的 /root 目录挂进容器,结果容器里的进程没权限读写,折腾了半天。

四、写时复制与性能:别被「分层」骗了

分层机制虽然好,但写时复制有个性能陷阱。

当容器要修改一个只读层的文件时,Docker 会把这个文件复制到可写层。如果文件很大(比如一个 10GB 的日志文件),复制操作会非常慢,而且占用大量磁盘空间。

我遇到过的情况:一个容器频繁修改数据库文件,结果可写层越来越大,最后磁盘满了。解决方案很简单——把数据库目录挂载成 Volume,避免写时复制。

我的建议:

  • 频繁写入的数据,用 Volume 或 Bind mount
  • 只读的配置文件,留在镜像层里
  • 临时文件,用 tmpfs mount 放内存里

五、实战:一个完整的存储方案

说了这么多,咱们来个实际的例子。假设你要部署一个 Nginx + PHP 的应用:

# 创建数据卷
docker volume create nginx_html
docker volume create nginx_logs

# 启动 Nginx 容器
docker run -d --name nginx \
  -v nginx_html:/usr/share/nginx/html \
  -v nginx_logs:/var/log/nginx \
  -p 80:80 \
  nginx:alpine

# 查看数据卷挂载点
docker volume inspect nginx_html
# 输出中 "Mountpoint" 就是宿主机上的实际路径

这样设计的好处:

  • HTML 文件持久化,更新内容只需替换 Volume 里的文件
  • 日志持久化,方便后续采集和分析
  • 容器升级或重建,数据不丢失

说白了,容器存储的核心就一句话:把需要持久化的数据,从容器层里「踢」出来,放到 Volume 里

好了,这一章的内容就到这儿。下一章咱们聊聊 Kubernetes 的存储抽象——PV 和 PVC,那才是云原生存储的重头戏。