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 中定义的匿名卷
# 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
# 数据已复制到卷中
挂载子目录
# 只挂载卷的子目录
docker run -d \
--name app \
--mount source=logs,target=/var/log/app,volume-subpath=app1 \
myapp:latest
使用卷驱动
# 创建使用特定驱动的卷
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=10.0.0.1,rw \
--opt device=:/export/data \
nfs-volume
# 创建 NFS 卷
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=192.168.1.100,nfsvers=4 \
--opt device=:/export/shared \
nfs-shared
绑定挂载(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
适用场景
- 临时缓存目录
- 敏感信息存储(如密码、密钥)
- 高速临时数据处理
# 示例: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"
# 使用外部卷
external-data:
external: true
开发环境配置
# compose.dev.yaml
services:
app:
volumes:
- ./src:/app/src # 代码目录
- ./config:/app/config # 配置目录
- /app/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 # 源代码
- /app/node_modules # 保持容器内的 node_modules
ports:
- "3000:3000"
command: npm run dev
backend:
image: python:3.11
working_dir: /app
volumes:
- ./backend:/app
- backend-cache:/root/.cache
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:
db-data:
日志收集
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. 使用 .dockerignore 避免挂载冲突
# .dockerignore
node_modules
.git
.env
小结
本章我们学习了:
- 存储类型:Volumes、Bind Mounts、tmpfs
- 数据卷管理:创建、查看、删除
- 绑定挂载:开发环境应用
- tmpfs:临时内存存储
- 数据备份恢复:迁移和容灾
- Docker Compose 配置:声明式管理
练习
- 创建一个命名卷并在两个容器间共享数据
- 使用绑定挂载配置一个开发环境
- 备份一个数据卷并恢复到新卷
- 配置 tmpfs 挂载用于临时缓存