强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Bcachefs 完全指南 / 第 10 章:Docker 与容器化

第 10 章:Docker 与容器化

让 Bcachefs 成为容器的最佳存储后端


10.1 概述

10.1.1 为什么在 Docker 中使用 Bcachefs

Bcachefs 在容器场景的优势:

1. CoW 快速创建容器
   - 容器启动 = CoW 复制元数据
   - 毫秒级创建,几乎零空间占用

2. 快照支持
   - 一键快照容器状态
   - 快速回滚到历史版本

3. 透明压缩
   - 节省镜像和容器存储空间
   - 降低存储成本

4. 数据完整性
   - 校验和保护容器数据
   - 避免静默数据损坏

5. 多设备分层
   - 热数据放 SSD,冷数据放 HDD
   - 最优性能与成本平衡

10.1.2 Docker 存储驱动对比

存储驱动CoW快照压缩性能稳定性
overlay2 (ext4)⭐⭐⭐⭐⭐⭐⭐⭐
overlay2 (xfs)⭐⭐⭐⭐⭐⭐⭐⭐
overlay2 (btrfs)⭐⭐⭐⭐⭐⭐
overlay2 (bcachefs)⭐⭐⭐⭐⭐⭐
devicemapper⭐⭐⭐⭐
zfs⭐⭐⭐⭐⭐⭐

10.2 配置 Docker 使用 Bcachefs

10.2.1 方案一:将 Docker 数据目录放在 Bcachefs 上(推荐)

# 1. 创建并挂载 Bcachefs 文件系统
sudo bcachefs format /dev/sdb --compression=zstd
sudo mkdir -p /var/lib/docker-bcachefs
sudo mount -t bcachefs /dev/sdb /var/lib/docker-bcachefs

# 2. 停止 Docker
sudo systemctl stop docker docker.socket containerd

# 3. 迁移 Docker 数据(可选)
sudo rsync -aP /var/lib/docker/ /var/lib/docker-bcachefs/docker/

# 4. 配置 Docker 使用新数据目录
sudo mkdir -p /etc/docker
cat << 'EOF' | sudo tee /etc/docker/daemon.json
{
    "data-root": "/var/lib/docker-bcachefs/docker"
}
EOF

# 5. 启动 Docker
sudo systemctl start docker

# 6. 验证
docker info | grep "Docker Root Dir"
# 输出: Docker Root Dir: /var/lib/docker-bcachefs/docker

10.2.2 方案二:将整个 /var/lib/docker 放在 Bcachefs 上

# 1. 创建 Bcachefs 文件系统
sudo bcachefs format /dev/sdb --compression=zstd

# 2. 备份现有数据
sudo systemctl stop docker
sudo mv /var/lib/docker /var/lib/docker.bak

# 3. 挂载 Bcachefs 到 /var/lib/docker
sudo mkdir -p /var/lib/docker
sudo mount -t bcachefs /dev/sdb /var/lib/docker

# 4. 添加到 fstab
UUID=$(sudo bcachefs show-super /dev/sdb | grep UUID | awk '{print $2}')
echo "UUID=$UUID /var/lib/docker bcachefs defaults,noatime 0 0" | sudo tee -a /etc/fstab

# 5. 恢复数据(可选)
sudo rsync -aP /var/lib/docker.bak/ /var/lib/docker/

# 6. 启动 Docker
sudo systemctl start docker

10.2.3 方案三:使用 Bcachefs 作为 Docker Volume

# 1. 创建 Bcachefs 挂载点
sudo mkdir -p /mnt/bcachefs-volumes

# 2. 创建 Docker Volume 指向 Bcachefs
docker volume create \
    --driver local \
    --opt type=bcachefs \
    --opt device=/dev/sdb \
    --opt o=noatime,compress=zstd \
    bcachefs-data

# 3. 使用 Volume
docker run -v bcachefs-data:/data alpine echo "Hello Bcachefs"

10.3 Docker Compose 配置

10.3.1 基本配置

# docker-compose.yml
version: '3.8'

services:
  app:
    image: nginx:latest
    volumes:
      - app-data:/var/www/html
      - app-logs:/var/log/nginx
    ports:
      - "80:80"

  db:
    image: postgres:15
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: example

volumes:
  app-data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /mnt/bcachefs-docker/app-data
  
  app-logs:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /mnt/bcachefs-docker/app-logs
  
  db-data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /mnt/bcachefs-docker/db-data

10.3.2 自动创建目录

#!/bin/bash
# setup-docker-volumes.sh

BCACHEFS_MOUNT="/mnt/bcachefs-docker"
VOLUMES=("app-data" "app-logs" "db-data")

# 创建挂载点
sudo mkdir -p "$BCACHEFS_MOUNT"

# 挂载 Bcachefs(如果尚未挂载)
if ! mountpoint -q "$BCACHEFS_MOUNT"; then
    sudo mount -t bcachefs /dev/sdb "$BCACHEFS_MOUNT"
fi

# 创建卷目录
for vol in "${VOLUMES[@]}"; do
    sudo mkdir -p "$BCACHEFS_MOUNT/$vol"
    sudo chown 1000:1000 "$BCACHEFS_MOUNT/$vol"
done

echo "Docker volumes 准备完成"

10.4 快照管理与容器

10.4.1 快照 Docker 数据

#!/bin/bash
# docker-snapshot.sh

set -e

DOCKER_DATA="/var/lib/docker-bcachefs"
SNAPSHOT_DIR="/mnt/snapshots/docker"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)

echo "=== Docker 数据快照 ==="

# 1. 暂停所有容器
echo "暂停容器..."
docker pause $(docker ps -q) 2>/dev/null || true

# 2. 同步数据
sync

# 3. 创建快照
echo "创建快照..."
sudo bcachefs subvolume snapshot -r "$DOCKER_DATA" "$SNAPSHOT_DIR/snap-$TIMESTAMP"

# 4. 恢复容器
echo "恢复容器..."
docker unpause $(docker ps -q) 2>/dev/null || true

echo "快照完成: $SNAPSHOT_DIR/snap-$TIMESTAMP"

10.4.2 从快照恢复 Docker 数据

#!/bin/bash
# docker-restore.sh

set -e

if [ -z "$1" ]; then
    echo "用法: $0 <快照路径>"
    echo "可用快照:"
    ls /mnt/snapshots/docker/
    exit 1
fi

SNAPSHOT="$1"
DOCKER_DATA="/var/lib/docker-bcachefs"

echo "=== 从快照恢复 Docker 数据 ==="
echo "快照: $SNAPSHOT"

# 1. 停止 Docker
echo "停止 Docker..."
sudo systemctl stop docker docker.socket containerd

# 2. 备份当前数据
echo "备份当前数据..."
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
sudo mv "$DOCKER_DATA/docker" "$DOCKER_DATA/docker.bak.$TIMESTAMP"

# 3. 恢复快照
echo "恢复快照..."
sudo cp -a "$SNAPSHOT/docker" "$DOCKER_DATA/docker"

# 4. 启动 Docker
echo "启动 Docker..."
sudo systemctl start docker

echo "恢复完成"
docker ps

10.4.3 自动化快照策略

# /etc/systemd/system/docker-snapshot.timer
[Unit]
Description=Docker snapshot timer

[Timer]
OnCalendar=daily
OnCalendar=03:00
Persistent=true

[Install]
WantedBy=timers.target
# /etc/systemd/system/docker-snapshot.service
[Unit]
Description=Docker snapshot service
After=docker.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/docker-snapshot.sh
StandardOutput=journal
StandardError=journal
sudo systemctl enable --now docker-snapshot.timer

10.5 Overlay 文件系统集成

10.5.1 Overlay 与 Bcachefs 的关系

Docker 默认使用 overlay2 存储驱动:

镜像层 (只读)
    ↓
overlay2
    ↓
容器层 (可写)
    ↓
底层文件系统 (ext4/xfs/btrfs/bcachefs)

当底层文件系统是 Bcachefs 时:
  - overlay2 正常工作
  - 容器创建层使用 CoW(如果支持)
  - 透明压缩自动生效

10.5.2 验证 Overlay2 工作正常

# 检查 Docker 存储驱动
docker info | grep "Storage Driver"
# 输出: Storage Driver: overlay2

# 检查底层文件系统
df -hT /var/lib/docker
# 输出: /dev/sdb bcachefs ...

# 测试容器运行
docker run --rm alpine echo "Bcachefs + Docker works!"

# 检查镜像层
docker image inspect alpine | grep -A 5 "GraphDriver"

10.5.3 性能优化

# 在 Bcachefs 上运行 overlay2 时的优化建议:

# 1. 使用 noatime 挂载
sudo mount -o remount,noatime /var/lib/docker-bcachefs

# 2. 使用快速压缩
sudo mount -o remount,compress=lz4 /var/lib/docker-bcachefs

# 3. 确保启用 discard
sudo mount -o remount,discard /var/lib/docker-bcachefs

10.6 Kubernetes 与 Bcachefs

10.6.1 本地 PersistentVolume

# bcachefs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: bcachefs-pv
spec:
  capacity:
    storage: 100Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: bcachefs
  local:
    path: /mnt/bcachefs-k8s/data
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - node1
---
# bcachefs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: bcachefs-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: bcachefs
  resources:
    requests:
      storage: 100Gi

10.6.2 StorageClass (需要 CSI 驱动)

# bcachefs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: bcachefs
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain

10.6.3 StatefulSet 使用

# statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:15
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: bcachefs
        resources:
          requests:
            storage: 50Gi

10.7 容器化部署脚本

10.7.1 完整的 Docker + Bcachefs 设置脚本

#!/bin/bash
# setup-docker-bcachefs.sh

set -e

# 配置
DEVICE="/dev/sdb"
MOUNT_POINT="/var/lib/docker-bcachefs"
COMPRESSION="zstd"

echo "=== Docker + Bcachefs 配置 ==="

# 1. 检查 Bcachefs 支持
if ! modinfo bcachefs &>/dev/null; then
    echo "错误: 内核不支持 Bcachefs"
    exit 1
fi

# 2. 创建文件系统(如果需要)
if ! sudo bcachefs show-super "$DEVICE" &>/dev/null; then
    echo "创建 Bcachefs 文件系统..."
    read -p "这会清除 $DEVICE 上的所有数据!确认?(y/N) " confirm
    if [ "$confirm" != "y" ]; then
        echo "取消"
        exit 0
    fi
    sudo bcachefs format "$DEVICE" --compression="$COMPRESSION"
fi

# 3. 挂载
sudo mkdir -p "$MOUNT_POINT"
if ! mountpoint -q "$MOUNT_POINT"; then
    UUID=$(sudo bcachefs show-super "$DEVICE" | grep UUID | awk '{print $2}')
    echo "UUID=$UUID $MOUNT_POINT bcachefs defaults,noatime 0 0" | sudo tee -a /etc/fstab
    sudo mount "$MOUNT_POINT"
fi

# 4. 停止 Docker
sudo systemctl stop docker docker.socket containerd 2>/dev/null || true

# 5. 配置 Docker
sudo mkdir -p /etc/docker
cat << EOF | sudo tee /etc/docker/daemon.json
{
    "data-root": "$MOUNT_POINT/docker",
    "storage-driver": "overlay2",
    "log-driver": "json-file",
    "log-opts": {
        "max-size": "10m",
        "max-file": "3"
    }
}
EOF

# 6. 启动 Docker
sudo systemctl start docker

# 7. 验证
echo ""
echo "=== 配置完成 ==="
docker info | grep -E "Docker Root Dir|Storage Driver"
df -hT "$MOUNT_POINT"

10.8 备份与恢复

10.8.1 Docker 镜像备份

# 导出所有镜像
docker save $(docker images -q) -o /mnt/bcachefs-backup/docker-images.tar

# 导出特定镜像
docker save nginx:latest postgres:15 -o /mnt/bcachefs-backup/selected-images.tar

# 导入镜像
docker load -i /mnt/bcachefs-backup/docker-images.tar

10.8.2 Docker Volume 备份

# 备份 Volume
docker run --rm \
    -v my-volume:/source:ro \
    -v /mnt/bcachefs-backup:/backup \
    alpine tar czf /backup/my-volume.tar.gz -C /source .

# 恢复 Volume
docker run --rm \
    -v my-volume:/target \
    -v /mnt/bcachefs-backup:/backup \
    alpine sh -c "cd /target && tar xzf /backup/my-volume.tar.gz"

10.8.3 完整 Docker 环境备份

#!/bin/bash
# backup-docker-env.sh

BACKUP_DIR="/mnt/bcachefs-backup/docker-$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"

echo "备份 Docker 环境到: $BACKUP_DIR"

# 1. 备份 Docker 配置
cp -a /etc/docker "$BACKUP_DIR/docker-config"

# 2. 备份 Docker Compose 文件
find /opt -name "docker-compose*.yml" -exec cp {} "$BACKUP_DIR/" \; 2>/dev/null

# 3. 导出镜像列表
docker images --format "{{.Repository}}:{{.Tag}}" > "$BACKUP_DIR/images.txt"

# 4. 导出容器列表
docker ps -a --format "{{.Names}}:{{.Image}}" > "$BACKUP_DIR/containers.txt"

# 5. 快照 Docker 数据
sudo bcachefs subvolume snapshot -r /var/lib/docker-bcachefs "/mnt/snapshots/docker-$(date +%Y%m%d)"

echo "备份完成"

10.9 故障排除

10.9.1 Docker 启动失败

# 检查 Docker 日志
sudo journalctl -u docker -n 50

# 常见原因 1: 数据目录不存在或未挂载
mount | grep docker-bcachefs
df -h /var/lib/docker-bcachefs

# 常见原因 2: 权限问题
ls -la /var/lib/docker-bcachefs/

# 常见原因 3: daemon.json 配置错误
sudo dockerd --debug

10.9.2 容器创建失败

# 检查存储空间
df -h /var/lib/docker-bcachefs

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

# 检查 Bcachefs 文件系统
sudo bcachefs fsck /dev/sdb

10.9.3 性能问题

# 检查 I/O 瓶颈
iostat -x 1

# 检查压缩是否影响性能
mount | grep bcachefs
# 如果使用高压缩级别,考虑降低

# 检查 GC 是否在运行
dmesg | grep -i gc

10.10 本章小结

方案复杂度推荐场景
修改 data-root⭐ 最简单大多数场景
挂载到 /var/lib/docker⭐⭐新服务器
Docker Volume⭐⭐需要精细控制

核心配置:

{
    "data-root": "/var/lib/docker-bcachefs/docker"
}

最佳实践:

  1. 使用 zstd 或 lz4 压缩
  2. 启用 noatime 和 discard
  3. 定期快照 Docker 数据
  4. 监控存储空间

扩展阅读


上一章: ← 第 9 章:性能调优 | 下一章: 第 11 章:数据恢复与故障处理 →