|
|
Day 17 Compose 数据卷与持久化
你有没有经历过这种心碎时刻:辛辛苦苦往 MySQL 里灌了几万条数据,结果一个之后,数据全没了?那种感觉,就像你花了一下午整理好的房间,被熊孩子五分钟给拆了。
容器天生就是"用完即弃"的,这是它的优点,也是它让人抓狂的地方。今天我们就来解决这个问题——让数据在容器的生死轮回中"永生"。
本文你将学到
Named Volumes 和 Bind Mounts 的区别与使用场景
如何在 Compose 中配置数据卷实现持久化
MySQL 容器数据持久化的完整方案
数据卷的备份与恢复实操
生产环境数据卷的最佳实践
| 阅读时间 | 实操时间 | 难度等级 |
|---------|---------|---------|
| 10 分钟 | 30 分钟 | 中级 |
━━━━━━━━━━━━━━━━━━━━
一、容器数据的"前世今生"
先搞清楚一个基本事实:容器的文件系统是临时的。每次容器重建,里面的数据就会从头开始。这就好比你住酒店——退房之后,房间会被打扫得干干净净,你放在床头柜上的东西不会给你留着。
那问题来了,数据库、上传的文件、配置信息这些东西怎么办?总不能每次重启都从零开始吧。
Docker 给了我们两种"保险箱":
- ┌──────────────────────────────────────────────────┐
- │ 宿主机文件系统 │
- │ │
- │ ┌─────────────┐ ┌─────────────────┐ │
- │ │ Named Volume │ │ Bind Mount │ │
- │ │ │ │ │ │
- │ │ Docker 管理 │ │ 你指定目录 │ │
- │ │ /var/lib/ │ │ /home/data/ │ │
- │ │ docker/ │ │ /app/config/ │ │
- │ │ volumes/ │ │ │ │
- │ └──────┬──────┘ └────────┬────────┘ │
- │ │ │ │
- │ ▼ ▼ │
- │ ┌─────────────────────────────────────────┐ │
- │ │ 容器内部文件系统 │ │
- │ │ │ │
- │ │ /var/lib/mysql /app/config │ │
- │ └─────────────────────────────────────────┘ │
- └──────────────────────────────────────────────────┘
复制代码
二、Named Volumes vs Bind Mounts
这两兄弟经常被搞混,我们用一个生活类比来理解。
Named Volumes(命名卷) 就像银行的保险柜。你把东西交给银行保管,银行给你一个编号,你不需要知道具体放在哪个抽屉里。Docker 帮你管理存储位置、权限、生命周期,你只管用就行。
Bind Mounts(绑定挂载) 就像你在家里买了个保险箱,放在你指定的位置。你清楚地知道东西在哪,可以直接打开看,但维护工作得你自己来。
| 对比维度 | Named Volumes | Bind Mounts |
|---------|--------------|-------------|
| 存储位置 | Docker 管理(/var/lib/docker/volumes/) | 你指定的宿主机路径 |
| 可移植性 | 高,跨机器方便迁移 | 低,依赖宿主机目录结构 |
| 性能 | Linux 原生性能 | 取决于文件系统 |
| 权限管理 | Docker 自动处理 | 需要手动管理 |
| 适用场景 | 数据库、应用数据 | 开发时代码热更新、配置文件 |
| Compose 语法 |- volume_name:/container/path
复制代码 |- ./host/path:/container/path
复制代码 |
选择原则:生产环境优先用 Named Volumes,开发环境按需用 Bind Mounts。
三、Compose 中配置数据卷
3.1 Named Volumes 基础用法
- # docker-compose.yml
- services:
- db:
- image: mysql:8.0
- environment:
- MYSQL_ROOT_PASSWORD: my-secret-pw
- MYSQL_DATABASE: myapp
- volumes:
- - mysql_data:/var/lib/mysql
- ports:
- - "3306:3306"
- volumes:
- mysql_data:
复制代码
注意最后那个顶层的声明,这是在告诉 Docker:"帮我创建一个叫的命名卷。" 如果你忘了写这个声明,Docker Compose 会报错。
3.2 Bind Mounts 用法
- services:
- web:
- image: nginx:alpine
- volumes:
- - ./nginx.conf:/etc/nginx/nginx.conf:ro
- - ./html:/usr/share/nginx/html
复制代码
这里是宿主机上的相对路径,表示只读挂载——容器只能读这个文件,不能改。
3.3 混合使用
实际项目中,两种方式经常一起用:
- services:
- app:
- image: node:20-alpine
- volumes:
- - ./src:/app/src # Bind Mount: 开发时代码热更新
- - node_modules:/app/node_modules # Named Volume: 依赖包持久化
- - app_logs:/app/logs # Named Volume: 日志持久化
- volumes:
- node_modules:
- app_logs:
复制代码
这个组合很经典:源码用 Bind Mount 方便开发调试,而用 Named Volume 避免宿主机和容器的依赖冲突。
四、MySQL 数据持久化实操
说了这么多理论,我们来动手。这是大家最常遇到的场景——MySQL 数据持久化。
4.1 创建项目
- mkdir -p ~/docker-mysql-demo && cd ~/docker-mysql-demo
复制代码
创建:
- services:
- mysql:
- image: mysql:8.0
- container_name: demo-mysql
- environment:
- MYSQL_ROOT_PASSWORD: rootpass123
- MYSQL_DATABASE: demo_db
- MYSQL_USER: demo_user
- MYSQL_PASSWORD: demopass123
- volumes:
- - mysql_data:/var/lib/mysql
- - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
- ports:
- - "3307:3306"
- restart: unless-stopped
- volumes:
- mysql_data:
- name: demo-mysql-data
复制代码
创建初始化脚本:
- USE demo_db;
- CREATE TABLE IF NOT EXISTS users (
- id INT AUTO_INCREMENT PRIMARY KEY,
- name VARCHAR(100) NOT NULL,
- email VARCHAR(100) NOT NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- );
- INSERT INTO users (name, email) VALUES
- ('张三', 'zhangsan@example.com'),
- ('李四', 'lisi@example.com'),
- ('王五', 'wangwu@example.com');
复制代码
4.2 启动并验证
- # 启动服务
- docker compose up -d
- # 预期输出:
- # [+] Running 2/2
- # ✔ Volume "demo-mysql-data" Created
- # ✔ Container demo-mysql Started
复制代码
等待 MySQL 完全启动(大约 10-15 秒),然后验证数据:
- # 连接数据库查询数据
- docker exec demo-mysql mysql -udemo_user -pdemopass123 demo_db \
- -e "SELECT * FROM users;"
- # 预期输出:
- # +----+--------+----------------------+---------------------+
- # | id | name | email | created_at |
- # +----+--------+----------------------+---------------------+
- # | 1 | 张三 | zhangsan@example.com | 2026-03-06 10:00:00 |
- # | 2 | 李四 | lisi@example.com | 2026-03-06 10:00:00 |
- # | 3 | 王五 | wangwu@example.com | 2026-03-06 10:00:00 |
- # +----+--------+----------------------+---------------------+
复制代码
4.3 验证持久化
关键时刻到了——删掉容器,数据还在不在?
- # 插入一条新数据
- docker exec demo-mysql mysql -udemo_user -pdemopass123 demo_db \
- -e "INSERT INTO users (name, email) VALUES ('赵六', 'zhaoliu@example.com');"
- # 停止并删除容器(注意:不要加 -v 参数!)
- docker compose down
- # 重新启动
- docker compose up -d
- # 等待 MySQL 启动后查询
- docker exec demo-mysql mysql -udemo_user -pdemopass123 demo_db \
- -e "SELECT * FROM users;"
- # 预期输出: 4条数据都在,包括后来插入的"赵六"
复制代码
数据完好无损。这就是 Named Volume 的威力。
注意一个大坑:会把 volumes 一起删掉。生产环境千万别手滑加,这比还让人心痛。
五、数据卷的备份与恢复
数据持久化只是第一步。如果磁盘坏了呢?如果要迁移到另一台服务器呢?备份才是真正的保险。
5.1 备份
- # 创建备份目录
- mkdir -p ~/backups
- # 方法一:使用 mysqldump(推荐,跨版本兼容性好)
- docker exec demo-mysql mysqldump -udemo_user -pdemopass123 demo_db \
- > ~/backups/demo_db_backup.sql
- # 方法二:直接备份数据卷(适合整体迁移)
- docker run --rm \
- -v demo-mysql-data:/source:ro \
- -v ~/backups:/backup \
- alpine tar czf /backup/mysql_volume_backup.tar.gz -C /source .
- # 预期输出(方法二):
- # (无输出表示成功,可以 ls 验证)
- ls -lh ~/backups/
- # -rw-r--r-- 1 root root 1.2K ... demo_db_backup.sql
- # -rw-r--r-- 1 root root 5.8M ... mysql_volume_backup.tar.gz
复制代码
方法二的原理:启动一个临时的 Alpine 容器,把数据卷挂载为只读,然后打包压缩到备份目录。用完容器自动销毁(),干净利落。
5.2 恢复
- # 方法一:从 SQL 文件恢复
- docker exec -i demo-mysql mysql -udemo_user -pdemopass123 demo_db \
- < ~/backups/demo_db_backup.sql
- # 方法二:从卷备份恢复
- docker compose down
- docker volume rm demo-mysql-data
- docker volume create demo-mysql-data
- docker run --rm \
- -v demo-mysql-data:/target \
- -v ~/backups:/backup:ro \
- alpine tar xzf /backup/mysql_volume_backup.tar.gz -C /target
- docker compose up -d
复制代码
5.3 定时备份脚本
生产环境建议用 crontab 定时备份:
- #!/bin/bash
- # backup-mysql.sh
- BACKUP_DIR=~/backups/mysql
- DATE=$(date +%Y%m%d_%H%M%S)
- KEEP_DAYS=7
- mkdir -p "$BACKUP_DIR"
- docker exec demo-mysql mysqldump -udemo_user -pdemopass123 \
- --all-databases --single-transaction \
- > "$BACKUP_DIR/all_db_$DATE.sql"
- # 清理过期备份
- find "$BACKUP_DIR" -name "*.sql" -mtime +$KEEP_DAYS -delete
- echo "Backup completed: all_db_$DATE.sql"
复制代码- # 添加定时任务:每天凌晨 3 点执行
- crontab -e
- # 添加这一行:
- # 0 3 * * * /path/to/backup-mysql.sh >> /var/log/mysql-backup.log 2>&1
复制代码
六、数据卷管理常用命令
日常运维中,这几个命令用得最多:
- # 查看所有数据卷
- docker volume ls
- # 查看数据卷详情(存储路径、创建时间等)
- docker volume inspect demo-mysql-data
- # 删除未使用的数据卷(危险操作,先确认!)
- docker volume prune
- # 删除指定数据卷
- docker volume rm demo-mysql-data
复制代码 会删掉所有没被容器引用的卷。听起来很智能,但如果你刚好了一个项目,它的卷就变成"未使用"了。所以执行前一定要三思。
七、常见问题 Q&A
Q1: Named Volume 的数据到底存在宿主机哪里?
Linux 上默认在- /var/lib/docker/volumes/<volume_name>/_data/
复制代码 。你可以用- docker volume inspect <name>
复制代码 查看字段。但不建议直接操作这个目录,通过 Docker 命令管理更安全。
Q2: docker compose down 和 docker compose down -v 有什么区别?
只删除容器和网络,数据卷会保留。会把声明的 Named Volumes 也一起删掉。生产环境请务必确认你的肌肉记忆里没有。我见过不止一个团队因为这个参数翻车。
Q3: Bind Mount 的权限问题怎么解决?
这是 Linux 上最常见的坑。容器内进程的 UID/GID 和宿主机不一致,会导致"Permission denied"。解决方案有三个:
在 Dockerfile 中用设置正确权限
在 Compose 中用指定运行用户
对于 MySQL 等官方镜像,它们通常会自动处理权限,不需要额外配置
八、小结
今天我们学了 Docker 数据持久化的核心知识:
Named Volumes 适合生产环境,Docker 帮你管理,省心省力
Bind Mounts 适合开发环境,代码热更新、配置文件挂载很方便
MySQL 数据持久化的关键是把挂载到 Named Volume
持久化不等于安全,定期备份才是数据的最后防线
是个危险命令,生产环境慎用
数据卷解决了"数据往哪放"的问题。但真实项目不只有数据库,还有前端、后端、缓存、消息队列……它们之间怎么协作?
明天 Day 18 多容器全栈应用实战,我们会用 Compose 编排一个完整的前后端 + 数据库 + Redis 的全栈应用,把前几天学的网络、数据卷、环境变量全部串起来。这才是 Compose 真正发力的地方,敬请期待。 |
|