Skip to content

Docker 存储管理

Docker 提供了多种数据持久化和共享机制,理解这些存储选项对于构建可靠的容器化应用至关重要。本文将详细介绍 Docker 的各种存储解决方案。

目录

  1. 存储概述
  2. 数据卷(Volumes)
  3. 绑定挂载(Bind Mounts)
  4. tmpfs 挂载
  5. 存储驱动
  6. 数据备份与恢复
  7. 存储最佳实践

存储概述

1.1 容器存储问题

默认情况下,容器写入的数据存储在可写容器层中:

容器层结构:
┌─────────────────────────────────┐
│       可写容器层                 │  ← 数据随容器删除而丢失
├─────────────────────────────────┤
│       镜像只读层                 │
├─────────────────────────────────┤
│       镜像只读层                 │
└─────────────────────────────────┘

问题:

  • 数据不能持久化
  • 难以在容器间共享数据
  • 性能开销较大
  • 与主机紧密耦合

1.2 Docker 存储选项对比

特性数据卷绑定挂载tmpfs
主机位置Docker 管理用户指定内存中
挂载示例/var/lib/docker/volumes/任意路径不适用
支持共享
非 Docker 进程访问
持久化
性能依赖主机最好

数据卷(Volumes)

2.1 创建和管理卷

bash
# 创建命名卷
docker volume create my-data

# 创建带标签的卷
docker volume create \
  --label environment=production \
  --label project=myapp \
  app-data

# 列出所有卷
docker volume ls

# 查看卷详情
docker volume inspect my-data

# 删除卷
docker volume rm my-data

# 清理未使用的卷
docker volume prune

2.2 使用数据卷

bash
# 运行容器并挂载卷
docker run -d \
  -v my-data:/app/data \
  --name myapp \
  myimage:latest

# 使用 --mount 语法(推荐)
docker run -d \
  --mount source=my-data,target=/app/data \
  --name myapp \
  myimage:latest

# 只读挂载
docker run -d \
  --mount source=my-data,target=/app/data,readonly \
  myimage:latest

2.3 卷的存储位置

bash
# 查看卷的实际存储路径
docker volume inspect my-data --format='{{ .Mountpoint }}'

# Linux 默认位置
/var/lib/docker/volumes/my-data/_data

# Docker Desktop (Windows/Mac)
# 存储在虚拟机内部,通过 docker volume 命令管理

2.4 卷驱动

bash
# 使用本地驱动(默认)
docker volume create --driver local my-volume

# 使用 NFS 驱动
docker volume create \
  --driver local \
  --opt type=nfs \
  --opt o=addr=192.168.1.100,rw \
  --opt device=:/path/to/export \
  nfs-volume

# 使用第三方驱动(如 RexRay)
docker volume create \
  --driver rexray/ebs \
  --opt size=20 \
  ebs-volume

2.5 卷容器模式

bash
# 创建数据卷容器
docker create \
  -v /dbdata \
  --name dbstore \
  postgres:15-alpine \
  /bin/true

# 其他容器使用数据卷容器
docker run -d \
  --volumes-from dbstore \
  --name db1 \
  postgres:15-alpine

docker run -d \
  --volumes-from dbstore \
  --name db2 \
  postgres:15-alpine

绑定挂载(Bind Mounts)

3.1 基本使用

bash
# 绑定挂载主机目录到容器
docker run -d \
  -v /host/path:/container/path \
  nginx:alpine

# 使用 --mount 语法
docker run -d \
  --mount type=bind,source=/host/path,target=/container/path \
  nginx:alpine

# 只读绑定挂载
docker run -d \
  --mount type=bind,source=/host/config,target=/etc/nginx,readonly \
  nginx:alpine

3.2 开发环境应用

bash
# 前端开发 - 实时代码同步
docker run -d \
  -p 3000:3000 \
  -v $(pwd):/app \
  -v /app/node_modules \
  --name dev-server \
  node:18-alpine \
  npm run dev

# Python 开发 - 热重载
docker run -d \
  -p 5000:5000 \
  -v $(pwd):/app \
  --name flask-dev \
  python:3.11-slim \
  flask run --host=0.0.0.0 --reload

3.3 配置文件挂载

bash
# 挂载 Nginx 配置
docker run -d \
  -p 80:80 \
  -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro \
  -v $(pwd)/html:/usr/share/nginx/html:ro \
  nginx:alpine

# 挂载多个配置文件
docker run -d \
  --name postgres \
  -v $(pwd)/postgres.conf:/etc/postgresql/postgresql.conf:ro \
  -v $(pwd)/pg_hba.conf:/etc/postgresql/pg_hba.conf:ro \
  postgres:15-alpine \
  -c config_file=/etc/postgresql/postgresql.conf

3.4 绑定挂载注意事项

bash
# 文件不存在时的行为差异

# -v 语法:自动创建目录(可能导致问题)
docker run -v /nonexistent/path:/container/path alpine

# --mount 语法:如果源不存在则报错(更安全)
docker run --mount type=bind,source=/nonexistent/path,target=/container/path alpine
# 错误:bind source path does not exist

tmpfs 挂载

4.1 使用 tmpfs

tmpfs 挂载将数据存储在主机内存中,适合敏感数据或临时文件。

bash
# 使用 --tmpfs
docker run -d \
  --tmpfs /app/cache:noexec,nosuid,size=100m \
  myapp:latest

# 使用 --mount
docker run -d \
  --mount type=tmpfs,target=/app/cache,tmpfs-size=100m \
  myapp:latest

4.2 tmpfs 选项

选项说明
size最大存储空间
mode文件权限模式
uid所有者用户 ID
gid所有者组 ID
noexec禁止执行
nosuid忽略 setuid/setgid
nodev不解释字符/块设备

4.3 使用场景

bash
# 敏感数据(访问令牌、密钥)
docker run -d \
  --mount type=tmpfs,target=/run/secrets,tmpfs-size=10m \
  myapp:latest

# 临时缓存
docker run -d \
  --tmpfs /tmp:noexec,nosuid,size=500m \
  myapp:latest

# 会话数据
docker run -d \
  --mount type=tmpfs,target=/var/lib/php/sessions,tmpfs-size=50m \
  php-app:latest

存储驱动

5.1 存储驱动概述

存储驱动管理镜像层和容器层的存储。

存储驱动对比:
┌─────────────────┬─────────────┬─────────────┬──────────────┐
│ 驱动            │ 性能        │ 稳定性      │ 适用场景     │
├─────────────────┼─────────────┼─────────────┼──────────────┤
│ overlay2        │ 优秀        │ 优秀        │ 推荐(默认) │
│ fuse-overlayfs  │ 良好        │ 良好        │ 无 root 权限 │
│ btrfs           │ 优秀        │ 良好        │ 需要 btrfs   │
│ zfs             │ 优秀        │ 优秀        │ 需要 zfs     │
│ devicemapper    │ 一般        │ 一般        │ 旧系统兼容   │
└─────────────────┴─────────────┴─────────────┴──────────────┘

5.2 查看和配置存储驱动

bash
# 查看当前存储驱动
docker info --format '{{ .Driver }}'

# 查看存储驱动详情
docker info | grep -A 10 "Storage Driver"

# 配置存储驱动(daemon.json)
{
  "storage-driver": "overlay2",
  "storage-opts": [
    "overlay2.override_kernel_check=true"
  ]
}

5.3 Overlay2 详解

bash
# 查看 overlay2 存储结构
ls -la /var/lib/docker/overlay2/

# 查看镜像层
docker inspect nginx:alpine --format='{{ .GraphDriver.Data }}'

# 容器层结构
# lowerdir: 底层(镜像层)
# upperdir: 可写层(容器层)
# workdir: 工作目录
# merged: 合并视图

5.4 存储驱动性能优化

bash
# 使用 SSD 存储 Docker 数据
# /etc/docker/daemon.json
{
  "data-root": "/ssd/docker"
}

# 限制容器日志大小
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

# 启用直接 I/O(特定场景)
{
  "storage-opts": [
    "overlay2.mountopt=nodev"
  ]
}

数据备份与恢复

6.1 卷备份

bash
# 方法 1: 使用 tar 备份
docker run --rm \
  -v my-data:/data \
  -v $(pwd):/backup \
  alpine \
  tar czf /backup/my-data-backup.tar.gz -C /data .

# 方法 2: 使用 cp 命令
docker cp my-container:/app/data $(pwd)/backup/

# 方法 3: 使用卷快照(支持快照的存储驱动)
# btrfs 快照
btrfs subvolume snapshot /var/lib/docker/volumes/my-data \
  /var/lib/docker/volumes/my-data-snapshot

6.2 卷恢复

bash
# 从 tar 归档恢复
docker run --rm \
  -v my-data:/data \
  -v $(pwd):/backup \
  alpine \
  sh -c "cd /data && tar xzf /backup/my-data-backup.tar.gz"

# 使用 cp 命令恢复
docker cp $(pwd)/backup/. my-container:/app/data/

6.3 自动化备份脚本

bash
#!/bin/bash
# backup-volumes.sh

VOLUMES="db-data app-data cache-data"
BACKUP_DIR="/backup/docker-volumes"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p $BACKUP_DIR

for volume in $VOLUMES; do
    echo "Backing up $volume..."
    docker run --rm \
        -v $volume:/data \
        -v $BACKUP_DIR:/backup \
        alpine \
        tar czf /backup/${volume}_${DATE}.tar.gz -C /data .
done

# 清理旧备份(保留最近 7 天)
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete

echo "Backup completed!"

6.4 跨主机迁移

bash
# 源主机:导出卷
docker run --rm \
  -v my-data:/data \
  alpine tar czf - -C /data . > my-data.tar.gz

# 传输到目标主机
scp my-data.tar.gz user@target-host:/tmp/

# 目标主机:导入卷
docker volume create my-data

# 方法 1: 使用临时容器
docker run --rm \
  -v my-data:/data \
  -v /tmp:/backup \
  alpine sh -c "cd /data && tar xzf /backup/my-data.tar.gz"

# 方法 2: 使用 stdin
cat my-data.tar.gz | docker run --rm \
  -i -v my-data:/data \
  alpine tar xzf - -C /data

存储最佳实践

7.1 选择合适的存储类型

yaml
# Docker Compose 示例
version: '3.8'
services:
  web:
    image: nginx:alpine
    volumes:
      # 配置文件使用绑定挂载
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      # 静态资源使用绑定挂载
      - ./html:/usr/share/nginx/html:ro
  
  app:
    image: myapp:latest
    volumes:
      # 应用数据使用命名卷
      - app-data:/app/data
      # 临时文件使用 tmpfs
      - type: tmpfs
        target: /app/tmp
        tmpfs:
          size: 100M
  
  db:
    image: postgres:15-alpine
    volumes:
      # 数据库使用命名卷
      - db-data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db-password
    secrets:
      - db-password

volumes:
  app-data:
    driver: local
  db-data:
    driver: local

secrets:
  db-password:
    file: ./secrets/db-password.txt

7.2 性能优化

bash
# 1. 使用卷而不是绑定挂载(生产环境)
# 卷的 I/O 性能更好

# 2. 避免在容器中存储大量小文件
# 使用卷存储数据,避免复制到镜像层

# 3. 合理使用缓存
# Dockerfile 中先复制依赖文件,再复制代码

# 4. 日志管理
{
  "log-driver": "local",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

# 5. 监控磁盘使用
docker system df -v

7.3 安全最佳实践

bash
# 1. 敏感数据使用 tmpfs 或 secrets
docker run -d \
  --mount type=tmpfs,target=/run/secrets \
  myapp:latest

# 2. 配置文件使用只读挂载
docker run -d \
  --mount type=bind,source=/host/config,target=/etc/app,readonly \
  myapp:latest

# 3. 限制卷大小(使用 btrfs/zfs)
docker volume create --driver local \
  --opt type=btrfs \
  --opt size=10G \
  limited-volume

# 4. 定期清理未使用数据
docker volume prune -f
docker system prune -a --volumes

7.4 监控和维护

bash
# 查看卷使用情况
docker system df -v

# 查看容器磁盘 I/O
docker stats --format "table {{.Name}}\t{{.BlockIO}}"

# 查找大文件
docker run --rm -v my-volume:/data alpine \
  sh -c "find /data -type f -size +100M -exec ls -lh {} \;"

# 清理容器日志
# 方法 1: 清空日志文件
docker exec container sh -c 'truncate -s 0 /var/log/app/*.log'

# 方法 2: 配置日志轮转
docker run -d \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  myapp:latest

故障排查

8.1 常见问题

bash
# 卷挂载失败
# 检查权限
ls -la /var/lib/docker/volumes/my-volume/_data

# 检查 SELinux(如果启用)
docker run -d -v my-volume:/data:z myapp:latest  # 共享标签
docker run -d -v my-volume:/data:Z myapp:latest  # 私有标签

# 磁盘空间不足
# 检查 Docker 磁盘使用
docker system df

# 清理未使用资源
docker system prune -a --volumes

8.2 调试存储问题

bash
# 查看容器挂载点
docker inspect container --format='{{ json .Mounts }}'

# 进入容器查看文件系统
docker exec -it container sh -c 'df -h && mount'

# 检查存储驱动日志
journalctl -u docker.service | grep -i storage

下一步

基于 MIT 许可发布