第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 mydatadocker volume create--driver local \ --opt type = none\ --opt device = /data/mysql\ --opt o = bind\ mysql-data# 2. 查看所有 Volume docker volumels # 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.0docker 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 volumerm mydata# 查看 Volume 使用情况 docker systemdf -v # 备份 Volume docker run--rm -v mysql-data:/source-data-v $( pwd ) :/backup\ ubuntutar czf /backup/mysql-data-backup.tar.gz-C /source-data. # 恢复 Volume docker volume create mysql-data-restoreddocker run--rm -v mysql-data-restored:/target-data-v $( pwd ) :/backup\ ubuntutar xzf /backup/mysql-data-backup.tar.gz-C /target-data8.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 >-vmysql-data:/data ubuntu# 新容器从>docker run-d --name app --volumes-from>#># 适合数据容器模式(已不推荐,优先使用命名卷)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 rediscat > 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/initdbcat > 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\ ubuntutar 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-newdocker run--rm \ -v mysql-data-new:/var/lib/mysql\ -v $( pwd ) :/backup\ ubuntutar xzf /backup/mysql-volume-backup.tar.gz-C /var/lib/mysql8.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.rdbdocker exec redis-server redis-cli-a redis123 SHUTDOWN NOSAVEdocker restart redis-server8.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 ubuntubash -c \ "echo 'Hello Volume' > /data/test.txt && cat /data/test.txt" # Hello Volume # 3. 删除容器(数据仍在卷中) # (上面使用了 --rm,容器已自动删除) # 4. 启动新容器读取数据 docker run--rm -v test-data:/data ubuntucat /data/test.txt# Hello Volume ✅ 数据持久化成功! # 5. 清理 docker volumerm test-data实验 8.2:Bind Mount 开发环境 # 1. 创建项目目录 mkdir -p ~/docker-lab/webappcd ~/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\ pipinstall 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 composeps # 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 编排 —— 学会多容器应用的编排和管理