容器删除,数据还在吗?

问题:容器默认的文件系统是临时的,容器删除后,所有数据都会丢失。

场景

  • 运行一个MySQL数据库,插入了大量数据
  • 删除容器重新创建
  • 数据全部丢失!

解决:使用数据卷(Volume)实现数据持久化。

数据卷(Volume)

什么是数据卷?

数据卷是Docker管理的数据存储机制,独立于容器生命周期。

特点:

  • 独立于容器,容器删除后数据还在
  • 可以在容器之间共享
  • 性能好(直接读写宿主机文件系统)
  • 备份和迁移方便

基本操作

1. 创建数据卷

# 创建数据卷
docker volume create my-data

# 查看数据卷
docker volume ls

# 查看数据卷详细信息
docker volume inspect my-data

输出示例:

[
    {
        "CreatedAt": "2023-01-01T00:00:00Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my-data/_data",
        "Name": "my-data",
        "Options": {},
        "Scope": "local"
    }
]
  • Mountpoint:数据卷在宿主机的实际路径

2. 使用数据卷运行容器

# 挂载数据卷
docker run -d -v my-data:/data nginx

# 多个数据卷
docker run -d \
  -v mysql-data:/var/lib/mysql \
  -v mysql-logs:/var/log/mysql \
  mysql:8.0

3. 查看数据卷

# 列出所有数据卷
docker volume ls

# 查看数据卷详细信息
docker volume inspect my-data

# 查看数据卷内容
sudo ls /var/lib/docker/volumes/my-data/_data

4. 删除数据卷

# 删除未使用的数据卷
docker volume prune

# 删除指定数据卷
docker volume rm my-data

# 删除容器时同时删除数据卷(慎用)
docker rm -v my-container

注意:删除数据卷会永久删除数据,无法恢复!

绑定挂载(Bind Mount)

什么是绑定挂载?

将宿主机的目录或文件挂载到容器中。

特点:

  • 直接映射宿主机文件系统
  • 适合开发环境(实时同步代码)
  • 需要处理权限问题

基本用法

# 挂载宿主机目录到容器
docker run -d -v /path/on/host:/path/in/container nginx

# 相对路径(相对于当前目录)
docker run -d -v $(pwd)/html:/usr/share/nginx/html nginx

# 挂载单个文件
docker run -d -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf nginx

读写权限

# 读写(默认)
docker run -d -v /data:/app nginx

# 只读
docker run -d -v /data:/app:ro nginx

# 读写+指定用户
docker run -d -v /data:/app:ro -u 1000:1000 nginx

实战:同步开发代码

# 项目目录
cd my-flask-app

# 挂载当前目录到容器
docker run -d -p 8000:8000 -v $(pwd):/app python:3.11 \
  python -u /app/app.py

修改代码后,容器内立即生效!

数据卷 vs 绑定挂载

特性 数据卷(Volume) 绑定挂载(Bind Mount)
创建方式 Docker管理 用户指定
存储位置 /var/lib/docker/volumes 任意位置
性能 依赖宿主机文件系统
迁移 方便 需要手动处理
适合场景 生产环境 开发环境

建议:

  • 生产环境:使用数据卷
  • 开发环境:使用绑定挂载

实战:MySQL数据持久化

1. 创建数据卷

docker volume create mysql-data

2. 运行MySQL容器

docker run -d \
  --name my-mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -e MYSQL_DATABASE=mydb \
  -v mysql-data:/var/lib/mysql \
  -p 3306:3306 \
  mysql:8.0

3. 插入测试数据

# 连接MySQL
docker exec -it my-mysql mysql -uroot -p123456

# 在MySQL中执行
USE mydb;
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100)
);

INSERT INTO users (name, email) VALUES
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Charlie', 'charlie@example.com');

SELECT * FROM users;
EXIT;

4. 删除容器

docker rm -f my-mysql

5. 重新创建容器(数据还在)

docker run -d \
  --name my-mysql-new \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v mysql-data:/var/lib/mysql \
  -p 3306:3306 \
  mysql:8.0

6. 验证数据还在

docker exec -it my-mysql-new mysql -uroot -p123456 -e \
  "USE mydb; SELECT * FROM users;"

输出:

+----+---------+---------------------+
| id | name    | email               |
+----+---------+---------------------+
|  1 | Alice   | alice@example.com   |
|  2 | Bob     | bob@example.com     |
|  3 | Charlie | charlie@example.com |
+----+---------+---------------------+

数据卷备份和恢复

备份数据卷

# 启动一个临时容器挂载数据卷
docker run --rm -v mysql-data:/data -v $(pwd):/backup \
  alpine tar czf /backup/mysql-backup.tar.gz -C /data .

# 备份完成,当前目录生成 mysql-backup.tar.gz

恢复数据卷

# 创建新的数据卷
docker volume create mysql-data-new

# 恢复备份
docker run --rm -v mysql-data-new:/data -v $(pwd):/backup \
  alpine sh -c "cd /data && tar xzf /backup/mysql-backup.tar.gz"

# 使用恢复的数据卷运行容器
docker run -d \
  --name my-mysql-restored \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v mysql-data-new:/var/lib/mysql \
  -p 3306:3306 \
  mysql:8.0

使用docker volume命令备份

# 导出数据卷(需要安装docker-volume-backup插件)
docker run --rm -v mysql-data:/data -v $(pwd):/backup \
  alpine tar czf /backup/backup.tar.gz /data

数据卷在容器间共享

场景:多个容器共享数据

# 创建数据卷
docker volume create shared-data

# 容器1写入数据
docker run --name writer -v shared-data:/data alpine sh -c \
  "echo 'Hello from writer' > /data/message.txt"

# 容器2读取数据
docker run --name reader -v shared-data:/data alpine cat /data/message.txt
# 输出:Hello from writer

实战:Web应用和日志收集

# 1. 创建日志数据卷
docker volume create app-logs

# 2. Web应用容器写入日志
docker run -d \
  --name myapp \
  -v app-logs:/app/logs \
  nginx

# 3. 日志分析容器读取日志
docker run --rm \
  -v app-logs:/logs \
  alpine sh -c "tail -f /logs/access.log"

数据卷驱动(Volume Driver)

本地驱动(默认)

# 默认使用local驱动
docker volume create my-data

第三方驱动

# 使用NFS驱动(需要先安装)
docker volume create -d local-persist \
  -o mountpoint=/mnt/nfs/share \
  --name nfs-data

清理未使用的资源

# 清理未使用的容器
docker container prune

# 清理未使用的镜像
docker image prune

# 清理未使用的数据卷
docker volume prune

# 清理所有未使用的资源
docker system prune

# 清理所有未使用的资源(包括未使用的镜像)
docker system prune -a

注意:清理操作不可逆,会永久删除数据!

最佳实践

1. 生产环境使用数据卷

# 生产环境推荐
docker run -d \
  -v mysql-data:/var/lib/mysql \
  mysql:8.0

2. 开发环境使用绑定挂载

# 开发环境推荐
docker run -d -v $(pwd):/app myapp

3. 使用.dockerignore避免敏感文件

.env
*.key
*.pem

4. 定期备份重要数据

# 定时备份数据卷
0 2 * * * docker run --rm -v mysql-data:/data -v /backup:/backup \
  alpine tar czf /backup/mysql-$(date +\%Y\%m\%d).tar.gz -C /data .

5. 使用只读挂载保护重要数据

docker run -d -v /config:/config:ro nginx

6. 明确指定数据卷名称

# 好
docker run -d -v mysql-prod-data:/var/lib/mysql mysql:8.0

# 不好(自动命名,难以管理)
docker run -d -v /var/lib/mysql mysql:8.0

实战:完整的数据持久化方案

项目结构

myapp/
├── app.py
├── requirements.txt
├── Dockerfile
└── docker-compose.yml

docker-compose.yml

version: '3.8'

services:
  app:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - app-data:/app/data
      - ./logs:/app/logs

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: mydb
    volumes:
      - mysql-data:/var/lib/mysql
    ports:
      - "3306:3306"

volumes:
  app-data:
  mysql-data:

使用

# 启动所有服务
docker-compose up -d

# 查看数据卷
docker volume ls

# 备份数据卷
docker run --rm -v myapp_mysql-data:/data -v $(pwd):/backup \
  alpine tar czf /backup/mysql-backup.tar.gz -C /data .

本章小结

  • 数据卷:独立于容器的数据存储,适合生产环境
  • 绑定挂载:映射宿主机目录,适合开发环境
  • 数据持久化:容器删除后数据不丢失
  • 数据备份:定期备份数据卷
  • 数据共享:多个容器可以共享同一数据卷

现在已经掌握了数据持久化,下一章我们来学习网络管理!

继续学下去,马上就能做复杂项目了!