第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\--opttypenone\--optdevice/data/mysql\--optobind\mysql-data# 2. 查看所有 Volumedockervolumels# DRIVER VOLUME NAME# local mydata# local mysql-data# local abc123def456... ← 匿名卷# 3. 查看 Volume 详情dockervolume inspect mydata# [# {# CreatedAt: 2024-01-15T10:30:0008: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_PASSWORDsecret123\mysql:8.0# 5. 验证数据持久化dockerexecmysql-db mysql-uroot-psecret123-eCREATE DATABASE test;dockerrm-fmysql-db# 重新创建容器数据仍然存在dockerrun-d\--namemysql-db-new\-vmysql-data:/var/lib/mysql\-eMYSQL_ROOT_PASSWORDsecret123\mysql:8.0dockerexecmysql-db-new mysql-uroot-psecret123-eSHOW 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-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# 基本用法dockerrun-d-v/host/path:/container/path nginx# 推荐使用 --mount 语法更清晰dockerrun-d\--mounttypebind,source/host/path,target/container/path\nginx# 只读挂载dockerrun-d\--mounttypebind,source/host/config,target/app/config,readonly\nginx# 挂载单个文件dockerrun-d\--mounttypebind,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,size100m\nginx# 指定 tmpfs 参数dockerrun-d\--tmpfs/tmp:rw,noexec,nosuid,size100m\myapp# tmpfs 参数# rw 读写模式# ro 只读模式# noexec 不可执行# nosuid 不允许 setuid# size 大小限制8.5.2 tmpfs 适用场景场景说明敏感数据密钥、证书、Token不写入磁盘高速缓存临时缓存文件重启即清除日志缓冲高频写入的临时日志临时文件应用运行时产生的临时文件8.6 数据库容器化实战8.6.1 MySQL 持久化部署# 创建 Docker Compose 文件catdocker-compose.ymlEOF 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# 添加自定义配置catmysql/conf.d/custom.cnfEOF [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-psecret1238.6.2 Redis 持久化部署# Redis Docker Compose 配置catdocker-compose-redis.ymlEOF 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-prediscatredis/redis.confEOF 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-d8.6.3 PostgreSQL 持久化部署# PostgreSQL Docker Compose 配置catdocker-compose-pg.ymlEOF 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/initdbcatpostgres/initdb/01-init.sqlEOF -- 创建扩展 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-d8.7 数据备份与恢复8.7.1 MySQL 备份# 备份 MySQL 数据dockerexecmysql-server mysqldump-uroot-psecret123--all-databasesmysql-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-psecret123mysql-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/mysql8.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-server8.7.3 自动化备份脚本catbackup.shEOF #!/bin/bash # Docker 数据自动备份脚本 BACKUP_DIR/backup/docker DATE$(date %Y%m%d_%H%M%S) RETENTION_DAYS7 # 创建备份目录 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/ EOFchmodx backup.sh# 添加到 crontab每天凌晨3点备份# crontab -e# 0 3 * * * /path/to/backup.sh /var/log/docker-backup.log 218.8 动手实验实验 8.1Volume 数据持久化验证# 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.2Bind Mount 开发环境# 1. 创建项目目录mkdir-p~/docker-lab/webappcd~/docker-lab/webapp# 2. 创建 Flask 应用catapp.pyEOF from flask import Flask app Flask(__name__) app.route(/) def hello(): return Hello from Docker! (with bind mount) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue) EOF# 3. 运行开发容器挂载代码目录dockerrun-d--namedev-web\-v$(pwd):/app\-w/app\-p5000:5000\python:3.11-slim\pipinstallflaskpython app.py# 4. 修改 app.py容器内自动更新# 5. 清理dockerrm-fdev-web实验 8.3MySQL 完整部署# 使用上面的 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 课后练习基础题使用 Volume 部署 MySQL验证数据持久化。进阶题使用 Bind Mount 搭建 Python 开发环境实现代码热更新。实践题编写自动化备份脚本备份 MySQL 和 Redis 数据。 下一章Docker Compose 编排 —— 学会多容器应用的编排和管理