Docker 数据卷管理
数据持久化是容器化应用的关键需求。容器的可写层随容器删除而消失,因此需要将重要数据存储在容器外部。本章将详细介绍 Docker 的存储机制和数据管理方法。
存储基础概念
容器层的局限性
默认情况下,容器内创建的所有文件都存储在可写容器层上。这个可写层与容器的生命周期绑定——容器被删除时,其中的数据也会随之丢失。
问题:
- 容器删除后数据丢失
- 数据难以在容器间共享
- 写入性能较低(需要经过存储驱动层)
存储挂载类型
Docker 提供三种将数据存储在容器外部的方式:
| 类型 | 说明 | 管理方式 | 适用场景 |
|---|---|---|---|
| Volumes | Docker 管理的持久化存储 | 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 挂载选项说明:
| 选项 | 说明 |
|---|---|
addr | NFS 服务器地址 |
rw | 读写模式 |
ro | 只读模式 |
nfsvers | NFS 版本(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/sshfs | SSH 远程挂载 |
sapk/plugin-rclone | 云存储(S3、GCS、Azure Blob 等) |
rexray/s3fs | AWS 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
存储类型对比
| 特性 | Volumes | Bind Mounts | tmpfs |
|---|---|---|---|
| 主机位置 | 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 匿名卷的详细说明:
这是一个常见的开发环境技巧。问题场景:
- 主机上的
./frontend目录没有node_modules(因为没有运行npm install) - 镜像中可能预装了
node_modules,或者你想在容器内运行npm install - 直接挂载
./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 中声明式管理数据卷
练习
- 创建一个命名卷并在两个容器间共享数据
- 使用绑定挂载配置一个开发环境
- 备份一个数据卷并恢复到新卷
- 配置 tmpfs 挂载用于临时缓存