第08章:Docker 数据持久化

📅 2026/7/2 20:29:21 👁️ 阅读次数 📝 编程学习
第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 三种方式对比

特性VolumeBind Mounttmpfs
存储位置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. 创建 Volumedockervolume create mydatadockervolume create--driverlocal\--opttype=none\--optdevice=/data/mysql\--opto=bind\mysql-data# 2. 查看所有 Volumedockervolumels# DRIVER VOLUME NAME# local mydata# local mysql-data# local abc123def456... ← 匿名卷# 3. 查看 Volume 详情dockervolume 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 运行容器dockerrun-d\--namemysql-db\-vmysql-data:/var/lib/mysql\-eMYSQL_ROOT_PASSWORD=secret123\mysql:8.0# 5. 验证数据持久化dockerexecmysql-db mysql-uroot-psecret123-e"CREATE DATABASE test;"dockerrm-fmysql-db# 重新创建容器,数据仍然存在!dockerrun-d\--namemysql-db-new\-vmysql-data:/var/lib/mysql\-eMYSQL_ROOT_PASSWORD=secret123\mysql:8.0dockerexecmysql-db-new mysql-uroot-psecret123-e"SHOW DATABASES;"# test 数据库仍然存在!

8.3.3 匿名卷 vs 命名卷

# 匿名卷(Docker 自动生成名称)dockerrun-d-v/var/lib/mysql mysql:8.0# Volume 名称:abc123def456...(随机)# 命名卷(推荐!)dockerrun-d-vmysql-data:/var/lib/mysql mysql:8.0# Volume 名称:mysql-data(可读可管理)# 在 Dockerfile 中声明匿名卷VOLUME /data VOLUME["/data","/logs"]# ⚠️ 注意:Dockerfile 中的 VOLUME 声明会导致后续对该目录的修改无法保存到镜像层

8.3.4 Volume 的生命周期管理

# 删除未使用的 Volumedockervolume prune# 删除指定 Volumedockervolumermmydata# 查看 Volume 使用情况dockersystemdf-v# 备份 Volumedockerrun--rm-vmysql-data:/source-data-v$(pwd):/backup\ubuntutarczf /backup/mysql-data-backup.tar.gz-C/source-data.# 恢复 Volumedockervolume create mysql-data-restoreddockerrun--rm-vmysql-data-restored:/target-data-v$(pwd):/backup\ubuntutarxzf /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

# 基本用法dockerrun-d-v/host/path:/container/path nginx# 推荐使用 --mount 语法(更清晰)dockerrun-d\--mounttype=bind,source=/host/path,target=/container/path\nginx# 只读挂载dockerrun-d\--mounttype=bind,source=/host/config,target=/app/config,readonly\nginx# 挂载单个文件dockerrun-d\--mounttype=bind,source=/host/nginx.conf,target=/etc/nginx/nginx.conf,readonly\nginx# 开发环境示例:挂载代码目录dockerrun-d\--namedev-app\-v$(pwd)/src:/app/src\-v$(pwd)/requirements.txt:/app/requirements.txt\-p8080:8080\python:3.11-slim# 开发时修改代码后,容器内立即生效!

8.4.3 --volumes-from(从另一个容器挂载)

# 从已有容器挂载所有卷dockerrun-d--name>-vmysql-data:/data ubuntu# 新容器从>dockerrun-d--nameapp --volumes-from>#># 适合数据容器模式(已不推荐,优先使用命名卷)

8.5 tmpfs(临时文件系统)

8.5.1 tmpfs 的特点

# tmpfs 挂载:数据存储在内存中dockerrun-d\--tmpfs/tmp:rw,size=100m\nginx# 指定 tmpfs 参数dockerrun-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-pmysql/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# 启动 MySQLdockercompose up-d# 查看日志dockercompose logs-fmysql# 测试连接dockerexec-itmysql-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-prediscat>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# 启动 Redisdockercompose-fdocker-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-ppostgres/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# 启动 PostgreSQLdockercompose-fdocker-compose-pg.yml up-d

8.7 数据备份与恢复

8.7.1 MySQL 备份

# 备份 MySQL 数据dockerexecmysql-server mysqldump-uroot-psecret123--all-databases>mysql-backup.sql# 使用 Volume 备份dockerrun--rm\-vmysql-data:/var/lib/mysql\-v$(pwd):/backup\ubuntutarczf /backup/mysql-volume-backup.tar.gz-C/var/lib/mysql.# 恢复 MySQL 数据dockerexec-imysql-server mysql-uroot-psecret123<mysql-backup.sql# 使用 Volume 恢复dockervolume create mysql-data-newdockerrun--rm\-vmysql-data-new:/var/lib/mysql\-v$(pwd):/backup\ubuntutarxzf /backup/mysql-volume-backup.tar.gz-C/var/lib/mysql

8.7.2 Redis 备份

# 触发 Redis 保存dockerexecredis-server redis-cli-aredis123 BGSAVE# 备份 RDB 文件dockercpredis-server:/data/dump.rdb ./redis-backup.rdb# 恢复 Redis 数据dockercp./redis-backup.rdb redis-server:/data/dump.rdbdockerexecredis-server redis-cli-aredis123 SHUTDOWN NOSAVEdockerrestart 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/ EOFchmod+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. 创建命名卷dockervolume create test-data# 2. 启动容器写入数据dockerrun--rm-vtest-data:/data ubuntubash-c\"echo 'Hello Volume' > /data/test.txt && cat /data/test.txt"# Hello Volume# 3. 删除容器(数据仍在卷中)# (上面使用了 --rm,容器已自动删除)# 4. 启动新容器读取数据dockerrun--rm-vtest-data:/data ubuntucat/data/test.txt# Hello Volume ✅ 数据持久化成功!# 5. 清理dockervolumermtest-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. 运行开发容器(挂载代码目录)dockerrun-d--namedev-web\-v$(pwd):/app\-w/app\-p5000:5000\python:3.11-slim\pipinstallflask&&python app.py# 4. 修改 app.py,容器内自动更新!# 5. 清理dockerrm-fdev-web

实验 8.3:MySQL 完整部署

# 使用上面的 docker-compose.yml 部署 MySQL# 1. 启动 MySQLdockercompose up-d# 2. 等待健康检查通过dockercomposeps# 3. 连接并创建数据dockerexec-itmysql-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. 停止并删除容器dockercompose down# 5. 重新启动(数据仍在)dockercompose up-d# 6. 验证数据dockerexec-itmysql-server mysql-uroot-psecret123-e" USE myapp; SELECT * FROM test; "# 数据完整!✅

8.9 本章小结

存储方式特点推荐场景
VolumeDocker 管理,独立于容器生产环境数据库、持久化数据
Bind Mount直接映射宿主机目录开发环境、配置文件
tmpfs内存存储,临时使用敏感数据、高速缓存

8.10 课后练习

  1. 基础题:使用 Volume 部署 MySQL,验证数据持久化。
  2. 进阶题:使用 Bind Mount 搭建 Python 开发环境,实现代码热更新。
  3. 实践题:编写自动化备份脚本,备份 MySQL 和 Redis 数据。

📖 下一章:Docker Compose 编排 —— 学会多容器应用的编排和管理