跳到主要内容

Docker 数据卷管理

数据持久化是容器化应用的关键需求。容器的可写层随容器删除而消失,因此需要将重要数据存储在容器外部。本章将详细介绍 Docker 的存储机制和数据管理方法。

存储基础概念

容器层的局限性

默认情况下,容器内创建的所有文件都存储在可写容器层上。这个可写层与容器的生命周期绑定——容器被删除时,其中的数据也会随之丢失。

问题:

  • 容器删除后数据丢失
  • 数据难以在容器间共享
  • 写入性能较低(需要经过存储驱动层)

存储挂载类型

Docker 提供三种将数据存储在容器外部的方式:

类型说明管理方式适用场景
VolumesDocker 管理的持久化存储Docker 管理生产环境、数据持久化
Bind Mounts主机目录直接挂载到容器用户管理开发环境、配置文件
tmpfs内存中存储,不落盘临时存储敏感数据、缓存

数据卷(Volumes)

什么是数据卷

数据卷是由 Docker 管理的持久化存储,存储在主机的特定目录(/var/lib/docker/volumes/)下。数据卷完全由 Docker 管理,与容器生命周期独立,可在多个容器间共享。

创建和管理数据卷

# 创建数据卷
docker volume create my-volume

# 列出所有数据卷
docker volume ls

# 查看数据卷详情
docker volume inspect my-volume

# 输出示例
[
{
"CreatedAt": "2024-01-15T10:00:00Z",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/my-volume/_data",
"Name": "my-volume",
"Scope": "local"
}
]

# 删除数据卷
docker volume rm my-volume

# 删除所有未使用的数据卷
docker volume prune

# 查看数据卷占用空间
docker system df -v

使用数据卷

基本用法:

# 使用 --mount(推荐,更明确的语法)
docker run -d \
--name web \
--mount source=my-volume,target=/app \
nginx:latest

# 使用 -v(简写语法)
docker run -d \
--name web \
-v my-volume:/app \
nginx:latest

两种语法的对比:

# --mount 语法(推荐)
--mount type=volume,source=<卷名>,destination=<容器路径>[,options]

# -v 语法(简写)
-v <卷名>:<容器路径>[:<选项>]

挂载选项:

# 只读挂载(容器不能修改卷中的数据)
docker run -d \
--name web \
--mount source=config,target=/etc/nginx,readonly \
nginx:latest

# 使用 -v 简写
docker run -d --name web -v config:/etc/nginx:ro nginx

命名卷与匿名卷

# 命名卷:有明确名称,方便管理
docker run -v mydata:/data myapp

# 匿名卷:随机生成名称
docker run -v /data myapp

# Dockerfile 中定义的匿名卷
# VOLUME /data

# 查看匿名卷
docker volume ls

# 匿名卷在容器使用 --rm 选项时随容器自动删除
docker run --rm -v /data myapp

命名卷的优势在于可以方便地引用和管理,推荐在生产环境中始终使用命名卷。

数据卷预填充

当挂载一个空数据卷到容器中有数据的目录时,容器中的原始内容会自动复制到数据卷中:

# nginx 镜像中 /usr/share/nginx/html 有默认内容
# 挂载空卷时,默认内容会自动复制到卷中
docker run -d \
--name nginx-test \
-v nginx-html:/usr/share/nginx/html \
nginx:latest

# 验证数据已复制到卷中
docker volume inspect nginx-html

这个特性对于初始化卷中的数据非常有用,比如数据库首次启动时的初始化数据。

使用卷驱动

卷驱动允许将数据存储在远程存储后端,如 NFS、S3、Azure Blob 等。这使得数据可以在多个主机之间共享,适合集群环境。

本地 NFS 挂载

# 创建使用 NFS 的卷
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=10.0.0.1,rw,nfsvers=4 \
--opt device=:/export/data \
nfs-volume

# 使用 NFS 卷
docker run -v nfs-volume:/data myapp

NFS 挂载选项说明

选项说明
addrNFS 服务器地址
rw读写模式
ro只读模式
nfsversNFS 版本(3 或 4)
hard硬挂载(默认),服务器不可达时进程阻塞
soft软挂载,超时后返回错误

SSHFS 远程挂载

# 创建使用 SSHFS 的卷
docker volume create \
--driver vieux/sshfs \
--opt sshcmd=user@remote:/path/to/data \
--opt password=<password> \
sshfs-volume

# 或使用 SSH 密钥
docker volume create \
--driver vieux/sshfs \
--opt sshcmd=user@remote:/path/to/data \
--opt IdentityFile=/home/user/.ssh/id_rsa \
sshfs-volume

CIFS/Samba 挂载

# 创建使用 CIFS 的卷
docker volume create \
--driver local \
--opt type=cifs \
--opt o=username=user,password=pass,uid=1000,gid=1000 \
--opt device=//server/share \
cifs-volume

云存储驱动

AWS S3(使用 Rclone)

# 安装 Rclone 卷驱动插件
docker plugin install sapk/plugin-rclone

# 配置 Rclone
docker plugin set sapk/plugin-rclone \
RCLONE_CONFIG_REMOTENAME_TYPE=s3 \
RCLONE_CONFIG_REMOTENAME_REGION=us-east-1 \
RCLONE_CONFIG_REMOTENAME_ACCESS_KEY_ID=xxx \
RCLONE_CONFIG_REMOTENAME_SECRET_ACCESS_KEY=xxx

# 创建 S3 卷
docker volume create \
--driver sapk/plugin-rclone \
--opt remotename=mys3 \
--opt remotepath=/bucket/path \
s3-volume

Volume Driver 插件列表

驱动说明
local本地存储(默认)
vieux/sshfsSSH 远程挂载
sapk/plugin-rclone云存储(S3、GCS、Azure Blob 等)
rexray/s3fsAWS S3
convoy多后端支持

生产环境建议

  • 使用 NFS 或云存储实现数据共享
  • 配置适当的备份策略
  • 注意网络延迟对性能的影响
  • 考虑数据一致性和并发访问问题

绑定挂载(Bind Mounts)

什么是绑定挂载

绑定挂载将主机上的文件或目录直接映射到容器中。与数据卷不同,绑定挂载直接引用主机上的路径。

# 主机目录结构
/home/user/app/
├── src/
├── config/
└── data/

# 绑定挂载到容器
docker run -v /home/user/app:/app myapp

使用绑定挂载

# 使用 --mount(推荐)
docker run -d \
--name dev-app \
--mount type=bind,source=/home/user/app,target=/app \
myapp:latest

# 使用 -v
docker run -d \
--name dev-app \
-v /home/user/app:/app \
myapp:latest

# 只读绑定挂载
docker run -d \
--name web \
--mount type=bind,source=/home/user/config,target=/etc/nginx,readonly \
nginx:latest

绑定挂载的特点

优点:

  • 主机和容器可以同时访问文件
  • 适合开发环境(代码修改即时生效)
  • 配置文件管理方便

缺点:

  • 依赖主机目录结构
  • 可移植性差(换台机器路径可能不同)
  • 安全风险(容器可访问主机任意目录)

常用场景

开发环境:

# 挂载源代码目录,支持热更新
docker run -d \
--name dev-server \
--mount type=bind,source=/home/user/project,target=/app \
-p 3000:3000 \
node:18 \
npm run dev

配置文件挂载:

# 挂载 Nginx 配置文件
docker run -d \
--name nginx \
--mount type=bind,source=/home/user/nginx.conf,target=/etc/nginx/nginx.conf,readonly \
-p 80:80 \
nginx:latest

# 挂载多个配置文件
docker run -d \
--name app \
--mount type=bind,source=./config/app.yaml,target=/app/config.yaml \
--mount type=bind,source=./config/database.yaml,target=/app/database.yaml \
myapp:latest

tmpfs 挂载

什么是 tmpfs

tmpfs 将数据存储在内存中,不会写入磁盘。数据在容器停止后消失。

  • 临时存储,容器停止后数据消失
  • 高性能(内存速度)
  • 安全(敏感数据不会持久化到磁盘)

使用 tmpfs

# 创建 tmpfs 挂载
docker run -d \
--name app \
--mount type=tmpfs,destination=/tmp \
myapp:latest

# 指定大小限制
docker run -d \
--name app \
--mount type=tmpfs,destination=/tmp,tmpfs-size=100m \
myapp:latest

# 设置权限
docker run -d \
--name app \
--mount type=tmpfs,destination=/tmp,tmpfs-mode=1770 \
myapp:latest

适用场景

  • 临时缓存目录
  • 敏感信息存储(如密码、密钥、token)
  • 高速临时数据处理
# 示例:Redis 使用 tmpfs 作为临时存储
docker run -d \
--name redis \
--mount type=tmpfs,destination=/data,tmpfs-size=512m \
redis:alpine

存储类型对比

特性VolumesBind Mountstmpfs
主机位置Docker 管理用户指定内存
持久性
性能最好极高
可移植性N/A
多容器共享支持支持不支持
远程存储支持不支持不支持
适用场景生产环境开发环境临时数据

选择建议:

生产数据持久化  ->  Volumes
开发环境代码挂载 -> Bind Mounts
敏感临时数据 -> tmpfs

数据卷备份与恢复

备份数据卷

# 方法 1:使用临时容器备份
docker run --rm \
-v my-volume:/source:ro \
-v $(pwd):/backup \
alpine \
tar czf /backup/my-volume-backup.tar.gz -C /source .

# 方法 2:备份运行中容器的卷
docker run --rm \
--volumes-from my-container \
-v $(pwd):/backup \
alpine \
tar czf /backup/backup.tar.gz /data

恢复数据卷

# 创建新卷
docker volume create restored-volume

# 恢复数据
docker run --rm \
-v restored-volume:/target \
-v $(pwd):/backup \
alpine \
tar xzf /backup/my-volume-backup.tar.gz -C /target

数据迁移

# 从容器 A 迁移到容器 B
# 1. 备份数据
docker run --rm \
--volumes-from source-container \
-v $(pwd):/backup \
alpine \
tar czf /backup/data.tar.gz /data

# 2. 恢复到目标容器
docker run --rm \
--volumes-from target-container \
-v $(pwd):/backup \
alpine \
tar xzf /backup/data.tar.gz -C /

Docker Compose 中的数据卷

基本配置

# compose.yaml
services:
db:
image: mysql:8.0
volumes:
- db-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret

app:
image: myapp
volumes:
- ./src:/app/src # 绑定挂载
- app-data:/app/data # 命名卷

volumes:
db-data: # 声明命名卷
app-data:

高级配置

services:
app:
image: myapp
volumes:
# 命名卷
- type: volume
source: app-data
target: /app/data
volume:
nocopy: true

# 绑定挂载
- type: bind
source: ./config
target: /app/config
read_only: true

# tmpfs
- type: tmpfs
target: /tmp
tmpfs:
size: 100M

volumes:
app-data:
driver: local
driver_opts:
type: nfs
o: addr=192.168.1.100,nfsvers=4
device: ":/export/data"

开发环境配置

# compose.dev.yaml
services:
app:
volumes:
- ./src:/app/src # 代码目录(热更新)
- ./config:/app/config # 配置目录
- /app/node_modules # 匿名卷(避免覆盖容器内的 node_modules)

共享数据

容器间共享卷

# 多个容器共享同一个卷
docker run -d --name writer -v shared-data:/data myapp
docker run -d --name reader -v shared-data:/data:ro myapp

使用 --volumes-from

# 从另一个容器挂载所有卷
docker run -d --name data-container -v /data busybox

# 其他容器使用数据容器的卷
docker run -d --name app1 --volumes-from data-container myapp
docker run -d --name app2 --volumes-from data-container myapp

注意:--volumes-from 已不推荐使用,建议直接使用命名卷。

实战示例

数据库持久化

# MySQL 数据持久化
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: myapp
volumes:
- mysql-data:/var/lib/mysql
- ./init-scripts:/docker-entrypoint-initdb.d:ro
ports:
- "3306:3306"

volumes:
mysql-data:

Web 应用开发环境

services:
frontend:
image: node:18
working_dir: /app
volumes:
# 绑定挂载:源代码目录,修改即时生效
- ./frontend:/app
# 匿名卷:防止主机空目录覆盖容器内的 node_modules
# 原理:挂载 ./frontend:/app 会覆盖 /app 下所有内容
# 使用匿名卷可以保留容器内 npm install 安装的依赖
- /app/node_modules
ports:
- "3000:3000"
command: npm run dev

backend:
image: python:3.11
working_dir: /app
volumes:
- ./backend:/app # 源代码目录
- backend-cache:/root/.cache # 缓存 pip 下载的包
ports:
- "8000:8000"
command: python manage.py runserver 0.0.0.0:8000

db:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
volumes:
- db-data:/var/lib/postgresql/data # 数据持久化
ports:
- "5432:5432"

volumes:
backend-cache: # Python 包缓存
db-data: # 数据库数据

关于 node_modules 匿名卷的详细说明

这是一个常见的开发环境技巧。问题场景:

  1. 主机上的 ./frontend 目录没有 node_modules(因为没有运行 npm install
  2. 镜像中可能预装了 node_modules,或者你想在容器内运行 npm install
  3. 直接挂载 ./frontend:/app 会用主机空目录覆盖容器内的 node_modules

解决方案:添加 - /app/node_modules 匿名卷,告诉 Docker "这个路径使用容器内的内容,不要被绑定挂载覆盖"。

日志收集

services:
app:
image: myapp
volumes:
- app-logs:/var/log/app

log-collector:
image: logstash
volumes:
- app-logs:/var/log/app:ro
depends_on:
- app

volumes:
app-logs:

清理未使用的卷

# 查看所有卷
docker volume ls

# 查看卷使用情况
docker system df -v

# 删除未使用的卷
docker volume prune

# 强制删除(不提示确认)
docker volume prune -f

# 删除所有卷(谨慎使用)
docker volume rm $(docker volume ls -q)

最佳实践

1. 生产环境使用命名卷

# 推荐:使用命名卷
docker run -v myapp-data:/data myapp

# 不推荐:使用绑定挂载(依赖主机目录结构)
docker run -v /home/user/data:/data myapp

2. 开发环境使用绑定挂载

# 适合开发环境:代码修改即时生效
docker run -v ./src:/app/src myapp

3. 敏感数据使用 tmpfs 或 secrets

# 使用 tmpfs 存储临时敏感数据
docker run --mount type=tmpfs,target=/secrets myapp

# Docker Swarm secrets
docker service create --secret db_password myapp

4. 定期备份重要数据

#!/bin/bash
# 定时备份脚本
docker run --rm \
-v production-db:/source:ro \
-v /backup:/backup \
alpine \
tar czf /backup/db-$(date +%Y%m%d).tar.gz -C /source .

5. 合理选择存储类型

对于不同场景选择合适的存储类型:

  • 数据库数据用命名卷
  • 开发代码用绑定挂载
  • 临时缓存用 tmpfs

小结

Docker 存储机制的核心要点:

  • 存储类型:Volumes(Docker 管理)、Bind Mounts(主机目录)、tmpfs(内存存储)
  • 数据卷管理:创建、查看、删除数据卷的命令
  • 绑定挂载:适合开发环境,代码修改即时生效
  • tmpfs:临时内存存储,适合敏感数据和高速缓存
  • 数据备份恢复:使用临时容器进行数据迁移
  • Docker Compose 配置:在 YAML 中声明式管理数据卷

练习

  1. 创建一个命名卷并在两个容器间共享数据
  2. 使用绑定挂载配置一个开发环境
  3. 备份一个数据卷并恢复到新卷
  4. 配置 tmpfs 挂载用于临时缓存

参考资源