2.3 持久化存储策略


2.3 持久化存储策略

本节摘要:容器是"用完即焚"的,容器内部写入的数据随容器删除而消失,持久化就是把数据放到容器生命周期之外的机制。Docker 提供三种挂载方式:命名卷由 Docker 托管、绑定挂载直连宿主机目录、tmpfs 只存内存。本节对比三者的生命周期与适用场景,解释数据库数据为什么必须进命名卷,并专门讨论 UID 与 GID 引发的权限问题,最后给出备份与恢复的命令配方。

上手前先明确

  1. 能说清容器文件系统与卷的生命周期差异,解释"删容器不删数据"的原理。
  2. 能对比命名卷、绑定挂载、tmpfs 三种方式的存储位置、持久性与适用场景。
  3. 能为数据库、缓存、临时目录分别选出正确的挂载方式。
  4. 能定位并解决绑定挂载场景下容器写文件无权限的 UID 与 GID 问题。
  5. 能用 docker run 加 alpine 容器完成命名卷的备份与恢复。

一、先理解"容器为什么会丢数据"

容器镜像由只读层叠加而成,容器运行时写入的文件落在一个可写层里。这个可写层跟容器绑定:容器删除,可写层一并删除。docker-compose down 不会删卷,但 docker-compose down -v 或者手工 docker rm 之后再重建,容器内数据就是新的了。

所以规则只有一条:凡是删了容器就不能丢的数据,必须放到容器之外。卷(volume)就是 Docker 提供的"容器之外":它绕过容器的联合文件系统,直接落在宿主机文件系统或内存里。卷有三个天然属性:容器删除后数据仍在、多个容器可以共享同一个卷、卷可以独立备份和恢复。

二、三种挂载方式总览

对比项 命名卷 绑定挂载 tmpfs
数据落点 Docker 管理目录 宿主机任意路径 宿主机内存
谁创建 首次挂载自动创建 手动准备目录 容器启动即创建
容器删除后 数据保留 宿主机文件保留 数据立即消失
是否跨容器共享 可以 可以 不可以
主要用途 数据库、上传目录 配置文件、源码热更新 会话、缓存、密钥临时存放
备份方式 docker run 打包 宿主机工具直接拷贝 无需备份

选择逻辑一句话:数据库和长期数据用命名卷,需要直接读写宿主机文件的场景用绑定挂载,只活一瞬的临时数据用 tmpfs。三种挂载可以混用在同一份 compose 文件里:一个服务挂命名卷存数据、挂绑定挂载读配置、挂 tmpfs 放临时文件,互不冲突。

三种卷类型生命周期对比图

下面这张图把三种方式从创建到销毁的完整生命周期并排画出来,差异一目了然。

三种卷类型生命周期对比图

三、命名卷:数据库数据的默认选择

命名卷的用法在 2.2 节已经出现过:db_data:/var/lib/postgresql/data,文件底部用 volumes 段声明 db_data:。挂载时卷不存在会自动创建,Docker 把数据存在自己的管理目录下,典型位置是 /var/lib/docker/volumes 下的同名目录,目录内部以 _data 结尾,比如 <卷名>/_data。日常操作不需要碰这个路径,用 docker volume ls 查看卷列表、docker volume inspect 查看详情即可。

为什么数据库数据必须放命名卷?三个理由:生命周期独立——容器删了重建,挂回同一个卷名,数据原样回来;管理统一——备份、迁移、清理都有专门命令,不会像散落的目录那样失控;避免权限裸奔——绑定挂载会把宿主机目录的权限直接暴露给容器进程,而命名卷首次挂载时会按镜像声明的用户初始化所有权,省掉大量权限折腾。

Redis 的配方更短:

version: "3.9" services: redis: image: redis:7.2 volumes: - redis_data:/data volumes: redis_data:

redis_data:/data 把命名卷挂到 Redis 的持久化目录,AOF 与 RDB 快照都写进卷里。想要删除一个不再使用的卷,先确保没有容器在挂载它,然后 docker volume rm redis_data

命名卷的数据量会一直增长,数据库卷尤其明显。建议给卷所在磁盘留足余量,定期用 df 检查;备份策略按数据重要程度分级:核心业务每日备份加异地存放,工具类数据每周一次即可。卷的备份文件本身也要加密,数据库明文备份文件泄露和数据库泄露是一个性质。

四、绑定挂载:灵活但要自己管

绑定挂载把宿主机路径直接挂进容器,语法是宿主机路径加容器路径:./html:/usr/share/nginx/html。它的价值在于"宿主机直接可见":开发时改源码即时生效,部署时把宿主机上现成的配置文件挂进容器,都不用进容器。

典型用法是把配置外置:

version: "3.9" services: web: image: nginx:1.21 ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro

尾部 :ro 是只读,容器内改不了配置文件,宿主机侧仍然随意编辑。配置文件、证书、初始化脚本都适合这种挂法。

代价是责任转移:目录要自己建、权限要自己管、Docker 不追踪它的生命周期,docker compose down -v 也删不掉它。所以我们的原则是:能上命名卷就不上绑定挂载,绑定挂载留给"必须与宿主机直接交互"的场景。

五、tmpfs:快但转瞬即逝

tmpfs 把数据放在宿主机内存里,不落盘。Compose 里用长语法声明:

version: "3.9" services: web: image: nginx:1.21 ports: - "80:80" volumes: - type: tmpfs target: /tmp

target 指定容器内挂载点,这里把 /tmp 变成内存盘。适合放会话票据、一次性缓存、中间计算产物这类"丢了也无所谓"的数据。两条铁律:一是别放重要数据,容器一停全没;二是大小受宿主机内存约束,别把大文件往里塞,拖垮整台机器。

六、权限问题:UID 与 GID 的坑

绑定挂载的经典故障长这样:容器日志报 Permission denied,明明宿主机目录权限看起来没问题。原因在于容器内进程的用户和宿主机文件属主不是同一个。PostgreSQL 官方镜像默认用 uid 999 运行,宿主机目录属主如果是 root,容器内就写不进去;反过来,容器以 root 写入的文件,宿主机普通用户也删不动。

排查顺序:先用 docker exec 进容器看进程身份,再用 ls -n 看宿主机的 uid 与 gid,最后决定方案——改宿主机目录属主、给容器指定 user,或在镜像构建阶段把目录 chown 给对应 uid。命名卷能绕开大半这类问题,因为 Docker 在首次挂载时会按镜像声明的所有权初始化卷内容,这也是我们反复推荐命名卷的实操原因。

容器写卷的权限还取决于首次挂载的初始化时机:命名卷为空时,Docker 会把镜像里挂载点目录的所有者与权限复制到卷上,镜像声明了非 root 用户(比如 postgres 用户)就能直接写;绑定挂载则完全继承宿主机现状,容器用户与宿主机属主对不上就报 Permission denied。这个差异就是"数据库配命名卷少踩坑"的底层原因。

七、备份与恢复

命名卷备份的标准姿势是用一个临时容器挂载卷并打包:

docker run --rm -v <volume_name>:/data -v $(pwd):/backup alpine tar cvf /backup/backup.tar /data

容器挂两个卷:目标数据卷挂到 /data,当前目录挂到 /backup,alpine 里一条 tar 把数据打成包落到宿主机当前目录。恢复是反向操作:

docker run --rm -v <volume_name>:/data -v $(pwd):/backup alpine tar xvf /backup/backup.tar -C /data

绑定挂载不用这么绕,宿主机备份工具直接拷贝目录即可。备份策略给三条底线:定期执行(按数据量定周期)、备份放不同存储位置防单点、定期演练恢复流程——备份恢复不了等于没备份。

⚠️ docker-compose down -v 会连同命名卷一起删除,数据无法找回。这条命令只在自己清楚要清场时用,日常 down 不要带 -v。

💡 卷名保持"项目加用途"的命名习惯,比如 blog_db_data。同名卷跨项目共享是合法的,但隐式耦合很难查,我们更倾向一个项目一套卷名。

八、卷的日常运维

卷的日常管理集中在几条命令:docker volume ls 列出全部卷,docker volume inspect <卷名> 查看挂载点与驱动信息,docker volume prune 清理未被容器引用的卷。prune 有杀伤力,执行前先 ls 确认,生产环境建议按项目标签过滤,只清自己打标的卷。

compose 里除了短语法 db_data:/var/lib/postgresql/data,还能用长语法显式控制挂载属性:

services: db: image: postgres:14 volumes: - type: volume source: db_data target: /var/lib/postgresql/data read_only: false

长语法在需要声明只读、指定卷驱动时更清晰,日常短语法足够。磁盘告警时想知道卷占了多少,进宿主机看 /var/lib/docker/volumes 下对应目录,du 一把就有数;先确认容器已停再统计,运行中的数据库目录会被占用导致数据不准。

九、选型检查清单

给每份数据过四问:跨容器存活是不是硬需求——是则命名卷;宿主机需要直接编辑吗——是则绑定挂载;允许随时丢失吗——允许则 tmpfs;数据量会不会超过内存——超过就告别 tmpfs。四问走完,选型基本不会偏。再补一条纪律:同一个服务可以同时挂命名卷与 tmpfs,比如数据库数据进卷、临时排序文件进内存盘,别被"一个服务一种挂载"的想法困住。最后一条:卷的命名和目录规划一样值得投入,命名清晰的项目,三个月后回来做迁移,卷的归属一目了然。

温故知新

  • 容器可写层随容器销毁:要留存的数据必须放到卷里,这是持久化的起点。
  • 命名卷是默认选择:生命周期独立、管理命令齐全、所有权自动初始化,数据库首选。
  • 绑定挂载直连宿主机:适合配置外置与源码热更新,但目录与权限要自己负责。
  • tmpfs 只存内存:会话缓存类临时数据合适,重要数据绝不能放。
  • 权限问题看 UID:容器用户与宿主机属主不一致是 Permission denied 的头号来源。
  • 备份用临时容器:tar 打包命令可复用,备份与恢复都要定期演练。
  • down 不带 -v:-v 会删卷,日常清理别误伤数据。

数据有了归宿,下一个问题就是服务之间怎么找到对方、怎么隔离——2.4 节进入网络配置与服务间通信。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U