第08章:Docker 数据持久化
·
第08章:Docker 数据持久化
本章目标:理解容器数据的存储方式,掌握 Volume 和 Bind Mount 的使用,实现数据的持久化和共享。
8.1 为什么需要数据持久化
8.1.1 容器数据的生命周期
容器的可写层(Write Layer):
┌─────────────────────────────────┐
│ Container Layer (可写层) │
│ - /app/data/db.sqlite │
│ - /var/log/app.log │
│ - /tmp/session.cache │
└─────────────────────────────────┘
⚠️ 容器被删除时,可写层中的数据也会丢失!
docker rm <container> → 所有数据消失!
8.1.2 需要持久化的数据类型
| 数据类型 | 示例 | 重要性 |
|---|---|---|
| 数据库文件 | MySQL 数据、Redis RDB | ⭐⭐⭐ 生命线 |
| 用户上传 | 图片、视频、文档 | ⭐⭐⭐ 不可再生 |
| 应用日志 | 访问日志、错误日志 | ⭐⭐ 排查问题 |
| 配置文件 | 证书、密钥、配置 | ⭐⭐ 安全相关 |
| 临时文件 | 缓存、会话 | ⭐ 可重建 |
8.2 数据存储方式概览
8.2.1 三种数据存储方式
┌────────────────────────────────────────────────────┐
│ Docker 数据存储方式 │
│ │
│ ┌──────────────────┐ ┌────────────────────────┐│
│ │ Volume (卷) │ │ Bind Mount (绑定挂载) ││
│ │ │ │ ││
│ │ Docker 管理 │ │ 用户指定目录 ││
│ │ 独立于容器 │ │ 直接映射宿主机目录 ││
│ │ 推荐使用 │ │ 开发环境常用 ││
│ └──────────────────┘ └────────────────────────┘│
│ │
│ ┌──────────────────┐ │
│ │ tmpfs (临时文件) │ │
│ │ │ │
│ │ 内存中存储 │ │
│ │ 容器停止即消失 │ │
│ │ 敏感数据使用 │ │
│ └──────────────────┘ │
└────────────────────────────────────────────────────┘
8.2.2 三种方式对比
| 特性 | Volume | Bind Mount | tmpfs |
|---|---|---|---|
| 存储位置 | Docker 管理的目录 | 宿主机目录 | 内存 |
| 持久性 | ✅ 容器删除后保留 | ✅ 宿主机目录保留 | ❌ 容器停止即丢失 |
| 多容器共享 | ✅ 支持 | ✅ 支持 | ❌ 不支持 |
| 备份迁移 | ✅ 方便 | ✅ 直接复制 | ❌ 不支持 |
| 性能 | 高 | 高 | 最高(内存) |
| 配置复杂度 | 低 | 中 | 低 |
| 推荐场景 | 生产环境 | 开发环境 | 敏感数据/缓存 |
8.3 Volume(数据卷)
8.3.1 Volume 的工作原理
Volume 存储结构:
宿主机文件系统
┌──────────────────────────────────────┐
│ /var/lib/docker/volumes/ │
│ ┌──────────────────────────────┐ │
│ │ mydata/ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ _data/ │ │ │
│ │ │ ├── db/ │ │ │
│ │ │ │ ├── ibdata1 │ │ │
│ │ │ │ ├── ib_logfile0 │ │ │
│ │ │ │ └── ... │ │ │
│ │ │ └── ... │ │ │
│ │ └──────────────────────┘ │ │
│ └──────────────────────────────┘ │
└──────────────────────────────────────┘
│
│ 挂载
▼
容器文件系统
┌──────────────────────────────────────┐
│ /var/lib/mysql/ │
│ ├── ibdata1 │
│ ├── ib_logfile0 │
│ └── ... │
└──────────────────────────────────────┘
8.3.2 创建和使用 Volume
# 1. 创建 Volume
docker volume create mydata
docker volume create --driver local \
--opt type=none \
--opt device=/data/mysql \
--opt o=bind \
mysql-data
# 2. 查看所有 Volume
docker volume ls
# DRIVER VOLUME NAME
# local mydata
# local mysql-data
# local abc123def456... ← 匿名卷
# 3. 查看 Volume 详情
docker volume inspect mydata
# [
# {
# "CreatedAt": "2024-01-15T10:30:00+08:00",
# "Driver": "local",
# "Labels": {},
# "Mountpoint": "/var/lib/docker/volumes/mydata/_data",
# "Name": "mydata",
# "Options": {},
# "Scope": "local"
# }
# ]
# 4. 使用 Volume 运行容器
docker run -d \
--name mysql-db \
-v mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret123 \
mysql:8.0
# 5. 验证数据持久化
docker exec mysql-db mysql -uroot -psecret123 -e "CREATE DATABASE test;"
docker rm -f mysql-db
# 重新创建容器,数据仍然存在!
docker run -d \
--name mysql-db-new \
-v mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret123 \
mysql:8.0
docker exec mysql-db-new mysql -uroot -psecret123 -e "SHOW DATABASES;"
# test 数据库仍然存在!
8.3.3 匿名卷 vs 命名卷
# 匿名卷(Docker 自动生成名称)
docker run -d -v /var/lib/mysql mysql:8.0
# Volume 名称:abc123def456...(随机)
# 命名卷(推荐!)
docker run -d -v mysql-data:/var/lib/mysql mysql:8.0
# Volume 名称:mysql-data(可读可管理)
# 在 Dockerfile 中声明匿名卷
VOLUME /data
VOLUME ["/data", "/logs"]
# ⚠️ 注意:Dockerfile 中的 VOLUME 声明会导致后续对该目录的修改无法保存到镜像层
8.3.4 Volume 的生命周期管理
# 删除未使用的 Volume
docker volume prune
# 删除指定 Volume
docker volume rm mydata
# 查看 Volume 使用情况
docker system df -v
# 备份 Volume
docker run --rm -v mysql-data:/source-data -v $(pwd):/backup \
ubuntu tar czf /backup/mysql-data-backup.tar.gz -C /source-data .
# 恢复 Volume
docker volume create mysql-data-restored
docker run --rm -v mysql-data-restored:/target-data -v $(pwd):/backup \
ubuntu tar xzf /backup/mysql-data-backup.tar.gz -C /target-data
8.4 Bind Mount(绑定挂载)
8.4.1 Bind Mount 的工作原理
Bind Mount 直接映射宿主机目录:
宿主机目录 容器目录
┌──────────────────────┐ ┌──────────────────────┐
│ /home/user/project/ │ ──────► │ /app/ │
│ ├── src/ │ │ ├── src/ │
│ │ └── app.py │ │ │ └── app.py │
│ ├── config/ │ │ ├── config/ │
│ │ └── settings.yml│ │ │ └── settings.yml│
│ └── requirements.txt│ │ └── requirements.txt│
└──────────────────────┘ └──────────────────────┘
容器内的修改会直接影响宿主机!
宿主机的修改也会直接影响容器!
8.4.2 使用 Bind Mount
# 基本用法
docker run -d -v /host/path:/container/path nginx
# 推荐使用 --mount 语法(更清晰)
docker run -d \
--mount type=bind,source=/host/path,target=/container/path \
nginx
# 只读挂载
docker run -d \
--mount type=bind,source=/host/config,target=/app/config,readonly \
nginx
# 挂载单个文件
docker run -d \
--mount type=bind,source=/host/nginx.conf,target=/etc/nginx/nginx.conf,readonly \
nginx
# 开发环境示例:挂载代码目录
docker run -d \
--name dev-app \
-v $(pwd)/src:/app/src \
-v $(pwd)/requirements.txt:/app/requirements.txt \
-p 8080:8080 \
python:3.11-slim
# 开发时修改代码后,容器内立即生效!
8.4.3 --volumes-from(从另一个容器挂载)
# 从已有容器挂载所有卷
docker run -d --name data-container -v mysql-data:/data ubuntu
# 新容器从 data-container 挂载卷
docker run -d --name app --volumes-from data-container myapp
# data-container 和 app 共享同一个 mysql-data 卷
# 适合数据容器模式(已不推荐,优先使用命名卷)
8.5 tmpfs(临时文件系统)
8.5.1 tmpfs 的特点
# tmpfs 挂载:数据存储在内存中
docker run -d \
--tmpfs /tmp:rw,size=100m \
nginx
# 指定 tmpfs 参数
docker run -d \
--tmpfs /tmp:rw,noexec,nosuid,size=100m \
myapp
# tmpfs 参数:
# rw 读写模式
# ro 只读模式
# noexec 不可执行
# nosuid 不允许 setuid
# size 大小限制
8.5.2 tmpfs 适用场景
| 场景 | 说明 |
|---|---|
| 敏感数据 | 密钥、证书、Token(不写入磁盘) |
| 高速缓存 | 临时缓存文件(重启即清除) |
| 日志缓冲 | 高频写入的临时日志 |
| 临时文件 | 应用运行时产生的临时文件 |
8.6 数据库容器化实战
8.6.1 MySQL 持久化部署
# 创建 Docker Compose 文件
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: mysql-server
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-secret123}
MYSQL_DATABASE: ${DB_NAME:-myapp}
MYSQL_USER: ${DB_USER:-appuser}
MYSQL_PASSWORD: ${DB_PASSWORD:-apppass123}
volumes:
- mysql-data:/var/lib/mysql # 数据文件
- mysql-logs:/var/log/mysql # 日志文件
- ./mysql/conf.d:/etc/mysql/conf.d # 配置文件(只读)
- ./mysql/initdb:/docker-entrypoint-initdb.d # 初始化脚本
ports:
- "3306:3306"
networks:
- backend
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
deploy:
resources:
limits:
memory: 1G
cpus: '1.0'
volumes:
mysql-data:
driver: local
mysql-logs:
driver: local
networks:
backend:
driver: bridge
EOF
# 创建必要的目录
mkdir -p mysql/conf.d mysql/initdb
# 添加自定义配置
cat > mysql/conf.d/custom.cnf << 'EOF'
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
max_connections = 200
innodb_buffer_pool_size = 512M
EOF
# 启动 MySQL
docker compose up -d
# 查看日志
docker compose logs -f mysql
# 测试连接
docker exec -it mysql-server mysql -uroot -psecret123
8.6.2 Redis 持久化部署
# Redis Docker Compose 配置
cat > docker-compose-redis.yml << 'EOF'
version: '3.8'
services:
redis:
image: redis:7-alpine
container_name: redis-server
restart: unless-stopped
command: redis-server /etc/redis/redis.conf
volumes:
- redis-data:/data
- ./redis/redis.conf:/etc/redis/redis.conf:ro
ports:
- "6379:6379"
networks:
- backend
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
redis-data:
driver: local
networks:
backend:
driver: bridge
EOF
# 创建 Redis 配置
mkdir -p redis
cat > redis/redis.conf << 'EOF'
bind 0.0.0.0
protected-mode no
requirepass redis123
save 900 1
save 300 10
save 60 10000
maxmemory 256mb
maxmemory-policy allkeys-lru
appendonly yes
EOF
# 启动 Redis
docker compose -f docker-compose-redis.yml up -d
8.6.3 PostgreSQL 持久化部署
# PostgreSQL Docker Compose 配置
cat > docker-compose-pg.yml << 'EOF'
version: '3.8'
services:
postgres:
image: postgres:15-alpine
container_name: postgres-server
restart: unless-stopped
environment:
POSTGRES_DB: ${PG_DB:-myapp}
POSTGRES_USER: ${PG_USER:-appuser}
POSTGRES_PASSWORD: ${PG_PASSWORD:-apppass123}
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- pg-data:/var/lib/postgresql/data
- ./postgres/initdb:/docker-entrypoint-initdb.d
ports:
- "5432:5432"
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${PG_USER:-appuser}"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pg-data:
driver: local
networks:
backend:
driver: bridge
EOF
# 创建初始化脚本
mkdir -p postgres/initdb
cat > postgres/initdb/01-init.sql << 'EOF'
-- 创建扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- 创建示例表
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
EOF
# 启动 PostgreSQL
docker compose -f docker-compose-pg.yml up -d
8.7 数据备份与恢复
8.7.1 MySQL 备份
# 备份 MySQL 数据
docker exec mysql-server mysqldump -uroot -psecret123 --all-databases > mysql-backup.sql
# 使用 Volume 备份
docker run --rm \
-v mysql-data:/var/lib/mysql \
-v $(pwd):/backup \
ubuntu tar czf /backup/mysql-volume-backup.tar.gz -C /var/lib/mysql .
# 恢复 MySQL 数据
docker exec -i mysql-server mysql -uroot -psecret123 < mysql-backup.sql
# 使用 Volume 恢复
docker volume create mysql-data-new
docker run --rm \
-v mysql-data-new:/var/lib/mysql \
-v $(pwd):/backup \
ubuntu tar xzf /backup/mysql-volume-backup.tar.gz -C /var/lib/mysql
8.7.2 Redis 备份
# 触发 Redis 保存
docker exec redis-server redis-cli -a redis123 BGSAVE
# 备份 RDB 文件
docker cp redis-server:/data/dump.rdb ./redis-backup.rdb
# 恢复 Redis 数据
docker cp ./redis-backup.rdb redis-server:/data/dump.rdb
docker exec redis-server redis-cli -a redis123 SHUTDOWN NOSAVE
docker restart redis-server
8.7.3 自动化备份脚本
cat > backup.sh << 'EOF'
#!/bin/bash
# Docker 数据自动备份脚本
BACKUP_DIR="/backup/docker"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=7
# 创建备份目录
mkdir -p $BACKUP_DIR
# 备份 MySQL
echo "正在备份 MySQL..."
docker exec mysql-server mysqldump -uroot -psecret123 --all-databases | \
gzip > $BACKUP_DIR/mysql_$DATE.sql.gz
# 备份 Redis
echo "正在备份 Redis..."
docker exec redis-server redis-cli -a redis123 BGSAVE
sleep 2
docker cp redis-server:/data/dump.rdb $BACKUP_DIR/redis_$DATE.rdb
# 备份 PostgreSQL
echo "正在备份 PostgreSQL..."
docker exec postgres-server pg_dumpall -U appuser | \
gzip > $BACKUP_DIR/postgres_$DATE.sql.gz
# 清理过期备份
echo "清理 $RETENTION_DAYS 天前的备份..."
find $BACKUP_DIR -name "*.gz" -mtime +$RETENTION_DAYS -delete
find $BACKUP_DIR -name "*.rdb" -mtime +$RETENTION_DAYS -delete
echo "备份完成!"
ls -lh $BACKUP_DIR/
EOF
chmod +x backup.sh
# 添加到 crontab(每天凌晨3点备份)
# crontab -e
# 0 3 * * * /path/to/backup.sh >> /var/log/docker-backup.log 2>&1
8.8 动手实验
实验 8.1:Volume 数据持久化验证
# 1. 创建命名卷
docker volume create test-data
# 2. 启动容器写入数据
docker run --rm -v test-data:/data ubuntu bash -c \
"echo 'Hello Volume' > /data/test.txt && cat /data/test.txt"
# Hello Volume
# 3. 删除容器(数据仍在卷中)
# (上面使用了 --rm,容器已自动删除)
# 4. 启动新容器读取数据
docker run --rm -v test-data:/data ubuntu cat /data/test.txt
# Hello Volume ✅ 数据持久化成功!
# 5. 清理
docker volume rm test-data
实验 8.2:Bind Mount 开发环境
# 1. 创建项目目录
mkdir -p ~/docker-lab/webapp
cd ~/docker-lab/webapp
# 2. 创建 Flask 应用
cat > app.py << 'EOF'
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "Hello from Docker! (with bind mount)"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
EOF
# 3. 运行开发容器(挂载代码目录)
docker run -d --name dev-web \
-v $(pwd):/app \
-w /app \
-p 5000:5000 \
python:3.11-slim \
pip install flask && python app.py
# 4. 修改 app.py,容器内自动更新!
# 5. 清理
docker rm -f dev-web
实验 8.3:MySQL 完整部署
# 使用上面的 docker-compose.yml 部署 MySQL
# 1. 启动 MySQL
docker compose up -d
# 2. 等待健康检查通过
docker compose ps
# 3. 连接并创建数据
docker exec -it mysql-server mysql -uroot -psecret123 -e "
USE myapp;
CREATE TABLE test (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50));
INSERT INTO test (name) VALUES ('Docker'), ('MySQL'), ('Volume');
SELECT * FROM test;
"
# 4. 停止并删除容器
docker compose down
# 5. 重新启动(数据仍在)
docker compose up -d
# 6. 验证数据
docker exec -it mysql-server mysql -uroot -psecret123 -e "
USE myapp;
SELECT * FROM test;
"
# 数据完整!✅
8.9 本章小结
| 存储方式 | 特点 | 推荐场景 |
|---|---|---|
| Volume | Docker 管理,独立于容器 | 生产环境数据库、持久化数据 |
| Bind Mount | 直接映射宿主机目录 | 开发环境、配置文件 |
| tmpfs | 内存存储,临时使用 | 敏感数据、高速缓存 |
8.10 课后练习
- 基础题:使用 Volume 部署 MySQL,验证数据持久化。
- 进阶题:使用 Bind Mount 搭建 Python 开发环境,实现代码热更新。
- 实践题:编写自动化备份脚本,备份 MySQL 和 Redis 数据。
📖 下一章:Docker Compose 编排 —— 学会多容器应用的编排和管理
更多推荐

所有评论(0)