在第12章中,我们了解了Docker的存储机制和三种数据管理方式。本章将深入学习数据卷(Volume)的高级用法,这是Docker推荐的数据持久化方案。
13.1 数据卷基础
13.1.1 什么是数据卷
数据卷是Docker管理的特殊目录,完全独立于容器的生命周期。
text
数据卷特点:
1. 持久化存储
容器删除 → 数据卷保留
2. 容器间共享
容器A ←→ 数据卷 ←→ 容器B
3. 独立管理
使用docker volume命令集管理
4. 高性能
绕过UnionFS,直接访问
5. 可备份迁移
支持多种备份方案
13.1.2 数据卷存储位置
bash
# 查看数据卷默认存储路径
docker info | grep "Docker Root Dir"
# Docker Root Dir: /var/lib/docker
# 数据卷实际存储位置
ls -l /var/lib/docker/volumes/
# 输出示例:
# drwx-----x 3 root root 4096 Feb 10 10:00 mysql-data
# drwx-----x 3 root root 4096 Feb 10 10:05 redis-data
# drwx-----x 3 root root 4096 Feb 10 10:10 app-uploads
# 查看具体数据卷内容
ls -la /var/lib/docker/volumes/mysql-data/_data/
13.2 创建和管理数据卷
13.2.1 创建数据卷
bash
# 基本创建
docker volume create mydata
# 创建时指定驱动
docker volume create --driver local mydata
# 创建时添加标签
docker volume create \
--label env=production \
--label app=mysql \
--label version=8.0 \
mysql-prod-data
# 创建时指定选项(取决于驱动)
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=192.168.1.10,rw \
--opt device=:/path/to/share \
nfs-volume
13.2.2 查看数据卷
bash
# 列出所有数据卷
docker volume ls
# 输出示例:
# DRIVER VOLUME NAME
# local mysql-data
# local redis-data
# local app-uploads
# 过滤数据卷
docker volume ls --filter name=mysql
docker volume ls --filter label=env=production
docker volume ls --filter dangling=true # 未使用的卷
# 格式化输出
docker volume ls --format "table {{.Name}}\t{{.Driver}}\t{{.Mountpoint}}"
# 显示悬空卷(未被任何容器使用)
docker volume ls -f dangling=true
13.2.3 查看数据卷详情
bash
# 查看详细信息
docker volume inspect mydata
# 输出示例:
# [
# {
# "CreatedAt": "2024-02-10T10:00:00+08:00",
# "Driver": "local",
# "Labels": {
# "env": "production",
# "app": "mysql"
# },
# "Mountpoint": "/var/lib/docker/volumes/mydata/_data",
# "Name": "mydata",
# "Options": {},
# "Scope": "local"
# }
# ]
# 提取特定信息
docker volume inspect mydata --format '{{.Mountpoint}}'
docker volume inspect mydata --format '{{.Driver}}'
docker volume inspect mydata --format '{{json .Labels}}' | jq
# 查看数据卷大小
docker system df -v | grep mydata
13.2.4 删除数据卷
bash
# 删除单个数据卷
docker volume rm mydata
# 删除多个数据卷
docker volume rm mydata1 mydata2 mydata3
# 删除未使用的数据卷(推荐)
docker volume prune
# 输出:
# WARNING! This will remove all local volumes not used by at least one container.
# Are you sure you want to continue? [y/N] y
# 强制删除(不提示)
docker volume prune -f
# 删除特定条件的卷
docker volume prune --filter "label!=keep"
# 删除所有数据卷(危险!)
docker volume rm $(docker volume ls -q)
13.2.5 数据卷生命周期
bash
# 创建卷
docker volume create mydata
# 容器使用卷
docker run -d -v mydata:/data --name app1 nginx
# 查看卷的引用
docker ps -a --filter volume=mydata
# 停止并删除容器
docker stop app1
docker rm app1
# 卷仍然存在
docker volume ls | grep mydata
# mydata
# 清理未使用的卷
docker volume prune
13.3 挂载数据卷到容器
13.3.1 使用 -v 参数
bash
# 挂载命名卷
docker run -d -v mydata:/data nginx
# 挂载多个卷
docker run -d \
-v mysql-data:/var/lib/mysql \
-v mysql-logs:/var/log/mysql \
mysql:8.0
# 只读挂载
docker run -d -v mydata:/data:ro nginx
# 创建匿名卷
docker run -d -v /data nginx
# Docker自动创建一个随机名称的卷
13.3.2 使用 --mount 参数(推荐)
bash
# 基本挂载
docker run -d \
--mount source=mydata,target=/data \
nginx
# 等同于
docker run -d \
--mount type=volume,source=mydata,target=/data \
nginx
# 只读挂载
docker run -d \
--mount source=mydata,target=/data,readonly \
nginx
# 如果卷不存在则创建
docker run -d \
--mount source=newdata,target=/data \
nginx
# 指定卷驱动和选项
docker run -d \
--mount type=volume,source=mydata,target=/data,volume-driver=local \
nginx
13.3.3 -v vs --mount 对比
bash
# -v 方式(简洁但不够明确)
docker run -d -v mydata:/data:ro nginx
# --mount 方式(推荐,更清晰)
docker run -d \
--mount type=volume,source=mydata,target=/data,readonly \
nginx
# --mount 的优势:
# 1. 更易读(键值对形式)
# 2. 参数验证更严格
# 3. 支持更多选项
# 4. 卷不存在时的行为可控
13.3.4 实战示例
示例1:MySQL数据持久化
bash
# 创建数据卷
docker volume create mysql-data
docker volume create mysql-logs
# 运行MySQL
docker run -d \
--name mysql \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=myapp \
--mount source=mysql-data,target=/var/lib/mysql \
--mount source=mysql-logs,target=/var/log/mysql \
-p 3306:3306 \
mysql:8.0
# 验证数据持久化
docker exec mysql mysql -uroot -psecret -e "CREATE DATABASE test;"
# 删除容器
docker rm -f mysql
# 重新创建容器(使用相同的卷)
docker run -d \
--name mysql \
-e MYSQL_ROOT_PASSWORD=secret \
--mount source=mysql-data,target=/var/lib/mysql \
-p 3306:3306 \
mysql:8.0
# 数据仍然存在
docker exec mysql mysql -uroot -psecret -e "SHOW DATABASES;"
# 输出包含test数据库
示例2:多容器共享数据
bash
# 创建共享卷
docker volume create shared-files
# 容器1:写入数据
docker run -d \
--name writer \
--mount source=shared-files,target=/data \
alpine sh -c "while true; do date >> /data/log.txt; sleep 5; done"
# 容器2:读取数据
docker run -d \
--name reader \
--mount source=shared-files,target=/data \
alpine tail -f /data/log.txt
# 查看容器2的输出
docker logs -f reader
示例3:Web应用文件上传
bash
# 创建上传文件卷
docker volume create app-uploads
# 运行应用
docker run -d \
--name webapp \
--mount source=app-uploads,target=/app/uploads \
-p 8080:8080 \
mywebapp:latest
# 备份上传的文件
docker run --rm \
--mount source=app-uploads,target=/data \
-v $(pwd):/backup \
alpine tar czf /backup/uploads-$(date +%Y%m%d).tar.gz /data
13.4 命名卷 vs 匿名卷
13.4.1 命名卷(Named Volume)
bash
# 创建命名卷
docker volume create mydata
# 使用命名卷
docker run -d -v mydata:/data nginx
# 优点:
# ✅ 易于识别和管理
# ✅ 可以预先创建和配置
# ✅ 便于备份和迁移
# ✅ 可以添加标签和元数据
# 查看命名卷
docker volume ls
# DRIVER VOLUME NAME
# local mydata
# 推荐使用场景:
# - 生产环境
# - 需要长期保存的数据
# - 需要在多个容器间共享
13.4.2 匿名卷(Anonymous Volume)
bash
# 创建匿名卷
docker run -d -v /data nginx
# Docker自动生成卷名
docker volume ls
# DRIVER VOLUME NAME
# local a1b2c3d4e5f6...
# 缺点:
# ❌ 难以识别
# ❌ 容易积累无用卷
# ❌ 不便于管理
# 自动删除匿名卷
docker run -d --rm -v /data nginx
# 容器删除时,匿名卷也会删除
# 使用场景:
# - 临时测试
# - 不需要持久化的场景
# - 使用--rm自动清理
13.4.3 对比和选择
| 特性 | 命名卷 | 匿名卷 |
|---|---|---|
| 卷名 | 自定义名称 | 随机生成 |
| 易识别 | 是 | 否 |
| 管理 | 简单 | 困难 |
| 清理 | 手动 | 可自动(--rm) |
| 共享 | 容易 | 困难 |
| 备份 | 方便 | 需要查找卷名 |
| 推荐场景 | 生产环境 | 临时测试 |
最佳实践:
bash
# ✅ 推荐:使用命名卷
docker run -d -v mysql-data:/var/lib/mysql mysql:8.0
# ❌ 避免:使用匿名卷(除非临时测试)
docker run -d -v /var/lib/mysql mysql:8.0
# ✅ 临时测试:使用匿名卷+自动删除
docker run --rm -v /data alpine sh -c "echo test > /data/file.txt"
13.5 数据卷的备份与恢复
13.5.1 备份数据卷
方法1:使用临时容器备份
bash
# 备份数据卷到tar文件
docker run --rm \
--mount source=mysql-data,target=/data \
-v $(pwd):/backup \
alpine tar czf /backup/mysql-data-backup.tar.gz /data
# 或使用ubuntu镜像(如果需要更多工具)
docker run --rm \
--mount source=mysql-data,target=/data \
-v $(pwd):/backup \
ubuntu tar czf /backup/mysql-data-backup.tar.gz -C /data .
方法2:直接复制数据卷目录
bash
# 查找数据卷路径
VOLUME_PATH=$(docker volume inspect mysql-data --format '{{.Mountpoint}}')
# 备份
sudo tar czf mysql-data-backup.tar.gz -C $VOLUME_PATH .
# 或复制整个目录
sudo cp -r $VOLUME_PATH ./mysql-data-backup
方法3:使用数据库专用备份工具
bash
# MySQL备份
docker exec mysql mysqldump -uroot -psecret --all-databases > backup.sql
# PostgreSQL备份
docker exec postgres pg_dumpall -U postgres > backup.sql
# Redis备份
docker exec redis redis-cli BGSAVE
docker cp redis:/data/dump.rdb ./backup/
13.5.2 恢复数据卷
方法1:从tar文件恢复
bash
# 创建新的数据卷
docker volume create mysql-data-restored
# 恢复数据
docker run --rm \
--mount source=mysql-data-restored,target=/data \
-v $(pwd):/backup \
alpine sh -c "cd /data && tar xzf /backup/mysql-data-backup.tar.gz --strip 1"
# 使用恢复的卷启动容器
docker run -d \
--name mysql \
-e MYSQL_ROOT_PASSWORD=secret \
--mount source=mysql-data-restored,target=/var/lib/mysql \
mysql:8.0
方法2:从目录恢复
bash
# 创建数据卷
docker volume create mysql-data-new
# 获取卷路径
VOLUME_PATH=$(docker volume inspect mysql-data-new --format '{{.Mountpoint}}')
# 恢复数据
sudo tar xzf backup.tar.gz -C $VOLUME_PATH
# 或直接复制
sudo cp -r ./backup/* $VOLUME_PATH/
方法3:使用数据库恢复命令
bash
# MySQL恢复
docker exec -i mysql mysql -uroot -psecret < backup.sql
# PostgreSQL恢复
docker exec -i postgres psql -U postgres < backup.sql
# Redis恢复
docker cp ./dump.rdb redis:/data/
docker restart redis
13.5.3 自动化备份脚本
bash
#!/bin/bash
# backup-volumes.sh
BACKUP_DIR="./backups"
DATE=$(date +%Y%m%d-%H%M%S)
# 创建备份目录
mkdir -p $BACKUP_DIR
# 备份函数
backup_volume() {
local volume_name=$1
local backup_file="$BACKUP_DIR/${volume_name}-${DATE}.tar.gz"
echo "Backing up volume: $volume_name"
docker run --rm \
--mount source=$volume_name,target=/data \
-v $BACKUP_DIR:/backup \
alpine tar czf /backup/$(basename $backup_file) -C /data .
if [ $? -eq 0 ]; then
echo "✅ Backup successful: $backup_file"
else
echo "❌ Backup failed: $volume_name"
fi
}
# 备份指定的卷
if [ -n "$1" ]; then
backup_volume $1
else
# 备份所有命名卷
for volume in $(docker volume ls --format "{{.Name}}" | grep -v "^[a-f0-9]\{64\}$"); do
backup_volume $volume
done
fi
# 清理旧备份(保留最近7天)
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete
echo "Backup completed: $(date)"
使用备份脚本:
bash
# 备份单个卷
./backup-volumes.sh mysql-data
# 备份所有卷
./backup-volumes.sh
# 设置定时任务
crontab -e
# 每天凌晨2点备份
0 2 * * * /path/to/backup-volumes.sh
13.5.4 恢复脚本
bash
#!/bin/bash
# restore-volume.sh
if [ $# -lt 2 ]; then
echo "Usage: $0 <backup-file> <volume-name>"
exit 1
fi
BACKUP_FILE=$1
VOLUME_NAME=$2
# 检查备份文件是否存在
if [ ! -f "$BACKUP_FILE" ]; then
echo "Error: Backup file not found: $BACKUP_FILE"
exit 1
fi
# 创建数据卷(如果不存在)
if ! docker volume inspect $VOLUME_NAME > /dev/null 2>&1; then
echo "Creating volume: $VOLUME_NAME"
docker volume create $VOLUME_NAME
fi
# 恢复数据
echo "Restoring volume: $VOLUME_NAME from $BACKUP_FILE"
docker run --rm \
--mount source=$VOLUME_NAME,target=/data \
-v $(dirname $BACKUP_FILE):/backup \
alpine sh -c "cd /data && tar xzf /backup/$(basename $BACKUP_FILE)"
if [ $? -eq 0 ]; then
echo "✅ Restore successful"
else
echo "❌ Restore failed"
exit 1
fi
使用恢复脚本:
bash
# 恢复数据卷
./restore-volume.sh ./backups/mysql-data-20240210-120000.tar.gz mysql-data-new
13.6 数据卷的迁移
13.6.1 同主机迁移
bash
# 方法1:重命名卷(实际上是创建新卷并复制数据)
# 备份旧卷
docker run --rm \
--mount source=old-volume,target=/data \
-v $(pwd):/backup \
alpine tar czf /backup/temp.tar.gz /data
# 创建新卷并恢复
docker volume create new-volume
docker run --rm \
--mount source=new-volume,target=/data \
-v $(pwd):/backup \
alpine sh -c "cd /data && tar xzf /backup/temp.tar.gz --strip 1"
# 清理临时文件
rm temp.tar.gz
# 方法2:使用docker cp(适合小数据量)
docker run -d --name temp --mount source=old-volume,target=/data alpine sleep 3600
docker cp temp:/data ./temp-data
docker volume create new-volume
docker run -d --name temp2 --mount source=new-volume,target=/data alpine sleep 3600
docker cp ./temp-data/. temp2:/data
docker rm -f temp temp2
rm -rf ./temp-data
13.6.2 跨主机迁移
bash
# 在源主机上
# 1. 备份数据卷
docker run --rm \
--mount source=mysql-data,target=/data \
-v $(pwd):/backup \
alpine tar czf /backup/mysql-data.tar.gz /data
# 2. 传输到目标主机
scp mysql-data.tar.gz user@target-host:/tmp/
# 在目标主机上
# 3. 创建数据卷
docker volume create mysql-data
# 4. 恢复数据
docker run --rm \
--mount source=mysql-data,target=/data \
-v /tmp:/backup \
alpine sh -c "cd /data && tar xzf /backup/mysql-data.tar.gz --strip 1"
# 5. 启动容器
docker run -d \
--name mysql \
-e MYSQL_ROOT_PASSWORD=secret \
--mount source=mysql-data,target=/var/lib/mysql \
mysql:8.0
13.6.3 使用卷插件迁移
bash
# 使用NFS卷插件实现跨主机共享
# 在所有主机上安装NFS卷插件
docker plugin install --grant-all-permissions vieux/sshfs
# 创建NFS卷
docker volume create \
--driver vieux/sshfs \
-o sshcmd=user@192.168.1.10:/path/to/data \
-o password=secret \
nfs-volume
# 在任何主机上使用
docker run -d --mount source=nfs-volume,target=/data nginx
13.7 数据卷驱动程序
13.7.1 本地驱动(local)
bash
# 默认驱动
docker volume create mydata
# 显式指定local驱动
docker volume create --driver local mydata
# 使用local驱动的选项
# 1. 挂载NFS
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=192.168.1.10,rw \
--opt device=:/path/to/share \
nfs-volume
# 2. 挂载CIFS/SMB
docker volume create \
--driver local \
--opt type=cifs \
--opt o=addr=192.168.1.10,username=user,password=pass \
--opt device=//192.168.1.10/share \
smb-volume
# 3. 使用tmpfs
docker volume create \
--driver local \
--opt type=tmpfs \
--opt device=tmpfs \
--opt o=size=1g \
temp-volume
13.7.2 第三方驱动
bash
# 安装卷插件
docker plugin install --grant-all-permissions vieux/sshfs
# 使用插件创建卷
docker volume create \
--driver vieux/sshfs \
-o sshcmd=user@host:/path \
-o password=secret \
ssh-volume
# 常用第三方驱动:
# - vieux/sshfs: SSH文件系统
# - local-persist: 本地持久化
# - rexray: 支持多种存储后端(AWS EBS, Google Persistent Disk等)
# - convoy: 支持多种存储后端
13.7.3 查看和管理驱动
bash
# 查看已安装的插件
docker plugin ls
# 输出示例:
# ID NAME ENABLED
# 1234567890ab vieux/sshfs:latest true
# 禁用插件
docker plugin disable vieux/sshfs
# 启用插件
docker plugin enable vieux/sshfs
# 删除插件
docker plugin rm vieux/sshfs
13.8 数据卷最佳实践
13.8.1 命名规范
bash
# ✅ 好的命名(描述性强)
docker volume create mysql-prod-8.0-data
docker volume create redis-cache-session-data
docker volume create app-uploads-2024
docker volume create logs-nginx-prod
# ❌ 不好的命名
docker volume create vol1
docker volume create data
docker volume create temp
docker volume create myvolume
13.8.2 标签使用
bash
# 创建时添加标签
docker volume create \
--label env=production \
--label app=mysql \
--label team=backend \
--label backup=daily \
mysql-prod-data
# 根据标签查找
docker volume ls --filter label=env=production
docker volume ls --filter label=backup=daily
# 根据标签删除(谨慎)
docker volume prune --filter label=env!=production
13.8.3 权限管理
bash
# 创建卷后设置权限
docker volume create mydata
# 使用临时容器设置权限
docker run --rm \
--mount source=mydata,target=/data \
alpine sh -c "chown -R 1000:1000 /data && chmod -R 755 /data"
# 验证权限
docker run --rm \
--mount source=mydata,target=/data \
alpine ls -la /data
13.8.4 定期维护
bash
#!/bin/bash
# volume-maintenance.sh
echo "=== Docker Volume Maintenance ==="
# 1. 显示卷使用情况
echo -e "\n1. Volume Usage:"
docker system df -v | grep -A 100 "Local Volumes space usage"
# 2. 查找未使用的卷
echo -e "\n2. Unused Volumes:"
docker volume ls --filter dangling=true
# 3. 查找大体积卷
echo -e "\n3. Large Volumes:"
for vol in $(docker volume ls -q); do
size=$(docker run --rm -v $vol:/data alpine du -sh /data 2>/dev/null | cut -f1)
echo "$vol: $size"
done | sort -h -k2
# 4. 检查卷的容器引用
echo -e "\n4. Volume References:"
for vol in $(docker volume ls -q); do
refs=$(docker ps -a --filter volume=$vol --format "{{.Names}}" | wc -l)
echo "$vol: $refs containers"
done
# 5. 清理建议
echo -e "\n5. Cleanup Recommendations:"
unused=$(docker volume ls --filter dangling=true -q | wc -l)
echo "Unused volumes: $unused"
echo "Run 'docker volume prune' to clean up"
13.8.5 监控和告警
bash
#!/bin/bash
# volume-monitor.sh
# 设置阈值(GB)
THRESHOLD=10
for vol in $(docker volume ls -q); do
# 获取卷大小
size_kb=$(docker run --rm -v $vol:/data alpine du -sk /data 2>/dev/null | cut -f1)
size_gb=$((size_kb / 1024 / 1024))
if [ $size_gb -gt $THRESHOLD ]; then
echo "⚠️ Warning: Volume $vol is ${size_gb}GB (threshold: ${THRESHOLD}GB)"
# 发送告警(示例)
# curl -X POST https://alert.example.com/webhook \
# -d "Volume $vol exceeded threshold: ${size_gb}GB"
fi
done
13.9 实战案例
13.9.1 完整的数据库部署
bash
#!/bin/bash
# deploy-database.sh
# 配置
APP_NAME="myapp"
DB_NAME="mysql"
VOLUME_DATA="${APP_NAME}-${DB_NAME}-data"
VOLUME_LOGS="${APP_NAME}-${DB_NAME}-logs"
NETWORK="${APP_NAME}-net"
# 创建网络
docker network create $NETWORK
# 创建数据卷
docker volume create \
--label app=$APP_NAME \
--label component=$DB_NAME \
--label type=data \
--label backup=daily \
$VOLUME_DATA
docker volume create \
--label app=$APP_NAME \
--label component=$DB_NAME \
--label type=logs \
$VOLUME_LOGS
# 运行数据库
docker run -d \
--name ${APP_NAME}-${DB_NAME} \
--network $NETWORK \
--restart unless-stopped \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=$APP_NAME \
--mount source=$VOLUME_DATA,target=/var/lib/mysql \
--mount source=$VOLUME_LOGS,target=/var/log/mysql \
--health-cmd "mysqladmin ping -h localhost" \
--health-interval 10s \
mysql:8.0
echo "Database deployed successfully"
echo "Data volume: $VOLUME_DATA"
echo "Logs volume: $VOLUME_LOGS"
13.9.2 开发和生产环境隔离
bash
# 开发环境
docker volume create dev-mysql-data
docker run -d \
--name dev-mysql \
--mount source=dev-mysql-data,target=/var/lib/mysql \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=dev \
mysql:8.0
# 生产环境
docker volume create prod-mysql-data
docker run -d \
--name prod-mysql \
--mount source=prod-mysql-data,target=/var/lib/mysql \
-p 3307:3306 \
-e MYSQL_ROOT_PASSWORD=secure_password \
mysql:8.0
13.10 小结
通过本章学习,我们深入掌握了数据卷的使用:
✅ 数据卷基础
- 数据卷的特点和优势
- 存储位置和结构
✅ 创建和管理
- 创建、查看、删除数据卷
- 使用标签和元数据
- 生命周期管理
✅ 挂载到容器
- -v 和 --mount 参数
- 命名卷 vs 匿名卷
- 实战示例
✅ 备份和恢复
- 三种备份方法
- 三种恢复方法
- 自动化脚本
✅ 数据迁移
- 同主机迁移
- 跨主机迁移
- 使用卷插件
✅ 卷驱动
- 本地驱动
- 第三方驱动
- 驱动管理
✅ 最佳实践
- 命名和标签规范
- 权限管理
- 定期维护
- 监控和告警
数据卷使用决策
text
需要数据持久化?
└─ 是 → 生产环境?
├─ 是 → 使用命名卷
│ ├─ 添加标签
│ ├─ 设置备份策略
│ └─ 定期维护
└─ 否 → 开发环境?
├─ 是 → 根据需求选择
│ ├─ 需要主机访问 → Bind Mount
│ └─ 不需要 → 命名卷
└─ 否 → 临时测试
└─ 使用匿名卷 + --rm
下一步
在第14章中,我们将学习绑定挂载(Bind Mount)的详细用法:
- 目录挂载语法
- 权限问题处理
- 开发环境实时同步
- 挂载单个文件
- 与Volume的对比
本章思考题:
- 为什么推荐在生产环境使用命名卷而不是匿名卷?
- 如何设计一个完善的数据卷备份策略?
- 数据卷的备份和容器的导出(docker export)有什么区别?
- 如何在不停止服务的情况下备份数据卷?
- 什么场景下应该使用第三方卷驱动?
相关资源: