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

Btrfs 文件系统运维完全教程 / 第 15 章:运维最佳实践

第 15 章:运维最佳实践

15.1 运维规范总览

15.1.1 核心原则

原则说明
备份优先任何操作前确认备份可用
渐进式变更逐步应用变更,避免大规模一次性操作
监控先行部署前先配置好监控和告警
文档化记录所有配置和操作
测试验证变更前在测试环境验证

15.1.2 版本选择指南

组件推荐版本说明
Linux 内核6.1 LTS 或 6.6 LTS长期支持版本
btrfs-progsv6.6+与内核版本匹配
发行版openSUSE Leap / Fedora原生 Btrfs 支持
# 检查当前版本
uname -r
btrfs version

# 查看内核 Btrfs 特性
cat /proc/fs/btrfs/features

15.2 子卷布局规范

15.2.1 服务器推荐布局

/dev/sda2 → Btrfs
├── @ (ID 256)              → /
├── @home (ID 257)          → /home
├── @var (ID 258)           → /var
├── @var-log (ID 259)       → /var/log
├── @var-lib-docker (ID 260)→ /var/lib/docker
├── @tmp (ID 261)           → /tmp
└── @snapshots (ID 262)     → /.snapshots

fstab 配置模板

# /etc/fstab
# 根分区
UUID=xxx /              btrfs subvol=/@,defaults,compress=zstd:1,ssd,discard=async    0 0
# 用户数据
UUID=xxx /home          btrfs subvol=/@home,defaults,compress=zstd:3,ssd              0 0
# 变量数据(关闭压缩,频繁写入)
UUID=xxx /var           btrfs subvol=/@var,defaults,noatime,ssd                        0 0
# 日志
UUID=xxx /var/log       btrfs subvol=/@var-log,defaults,noatime,ssd                    0 0
# Docker(专用子卷)
UUID=xxx /var/lib/docker btrfs subvol=/@var-lib-docker,defaults,compress=zstd:3,ssd    0 0
# 临时文件(关闭 COW)
UUID=xxx /tmp           btrfs subvol=/@tmp,defaults,noatime,nodatacow,ssd              0 0
# 快照(不自动挂载)
UUID=xxx /.snapshots    btrfs subvol=/@snapshots,defaults,noauto                       0 0

15.2.2 数据库目录处理

# 关闭数据库目录的 COW
sudo chattr +C /var/lib/mysql
sudo chattr +C /var/lib/postgresql
sudo chattr +C /var/lib/redis

# 验证
lsattr /var/lib/mysql
# -------------------C-- /var/lib/mysql  ← C 表示 NoCOW

# 注意:必须在目录为空时设置
# 如果目录已有数据,需要:
sudo mv /var/lib/mysql /var/lib/mysql.bak
sudo mkdir /var/lib/mysql
sudo chattr +C /var/lib/mysql
sudo rsync -av /var/lib/mysql.bak/ /var/lib/mysql/

15.3 备份策略

15.3.1 3-2-1 备份规则

                    ┌─────────────────────┐
                    │   生产服务器          │
                    │   (Btrfs 快照)       │
                    └──────────┬──────────┘
                               │
              ┌────────────────┼────────────────┐
              │                │                │
              ▼                ▼                ▼
    ┌─────────────┐  ┌─────────────┐  ┌─────────────┐
    │ 本地备份 NAS │  │ 异地备份服务器│  │ 离线备份磁带 │
    │ (Send/Recv) │  │ (SSH/增量)  │  │ (定期全量)  │
    └─────────────┘  └─────────────┘  └─────────────┘
         3 份副本         2 种介质         1 份异地

15.3.2 备份频率建议

数据类型快照频率备份频率保留策略
系统配置每次变更即时30 个快照
用户数据每小时每 6 小时24 小时 + 7 天 + 4 周
数据库每 15 分钟每小时100 个快照
Docker 数据每天每天7 天
日志不备份每天14 天

15.3.3 自动化备份脚本

#!/bin/bash
# btrfs-production-backup.sh - 生产环境备份脚本
set -euo pipefail

# 配置
SOURCE_MNT="/mnt/data"
BACKUP_SERVER="backup@nas-server"
BACKUP_PATH="/backup/production"
SNAPSHOT_DIR="/mnt/data/@snapshots"
SUBVOLUMES=("@home" "@var" "@config" "@docker-data")
KEEP_LOCAL_SNAPS=10
KEEP_REMOTE_SNAPS=30
LOG_FILE="/var/log/btrfs-backup.log"
ALERT_EMAIL="admin@example.com"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}

send_alert() {
    local subject="$1"
    local body="$2"
    echo "$body" | mail -s "Btrfs Backup: $subject" "$ALERT_EMAIL"
}

create_snapshot() {
    local subvol="$1"
    local timestamp=$(date '+%Y%m%d-%H%M%S')
    local snap_name="${subvol}-${timestamp}"
    local snap_path="${SNAPSHOT_DIR}/${snap_name}"

    log "Creating snapshot: $snap_name"
    if ! sudo btrfs subvolume snapshot -r "${SOURCE_MNT}/${subvol}" "$snap_path" 2>>"$LOG_FILE"; then
        send_alert "Snapshot Failed" "Failed to create snapshot: $snap_name"
        return 1
    fi
    echo "$snap_name"
}

send_incremental() {
    local subvol="$1"
    local new_snap="$2"
    local parent_snap="$3"

    if [[ -n "$parent_snap" ]]; then
        log "Sending incremental: $parent_snap$new_snap"
        sudo btrfs send -p "${SNAPSHOT_DIR}/${parent_snap}" "${SNAPSHOT_DIR}/${new_snap}" | \
            ssh "$BACKUP_SERVER" "sudo btrfs receive ${BACKUP_PATH}/" 2>>"$LOG_FILE"
    else
        log "Sending full: $new_snap"
        sudo btrfs send "${SNAPSHOT_DIR}/${new_snap}" | \
            ssh "$BACKUP_SERVER" "sudo btrfs receive ${BACKUP_PATH}/" 2>>"$LOG_FILE"
    fi
}

cleanup_old_snapshots() {
    local subvol="$1"
    local max_count="$2"

    local snapshots=($(sudo btrfs subvolume list -s "$SOURCE_MNT" | \
        grep "path ${SNAPSHOT_DIR##*/}/${subvol}-" | \
        sort -t'-' -k2 | awk '{print $NF}'))

    local count=${#snapshots[@]}
    if (( count > max_count )); then
        local to_delete=$(( count - max_count ))
        log "Cleaning up $to_delete old snapshots for $subvol"
        for (( i=0; i<to_delete; i++ )); do
            local snap="${snapshots[$i]}"
            log "Deleting: $snap"
            sudo btrfs subvolume delete "${SOURCE_MNT}/${snap}" 2>>"$LOG_FILE" || true
        done
    fi
}

verify_backup() {
    local subvol="$1"
    local snap="$2"

    log "Verifying backup for $snap"
    local source_count=$(sudo find "${SOURCE_MNT}/${subvol}" -type f | wc -l)
    local backup_count=$(ssh "$BACKUP_SERVER" "sudo find ${BACKUP_PATH}/${snap} -type f 2>/dev/null | wc -l" || echo 0)

    if [[ "$source_count" -ne "$backup_count" ]]; then
        send_alert "Backup Verification Failed" \
            "File count mismatch for $snap: source=$source_count, backup=$backup_count"
        return 1
    fi
    log "Verification passed: $source_count files"
}

main() {
    log "=== Starting production backup ==="

    for subvol in "${SUBVOLUMES[@]}"; do
        log "--- Processing $subvol ---"

        # 获取最新快照
        local latest=$(sudo btrfs subvolume list -s "$SOURCE_MNT" | \
            grep "path ${SNAPSHOT_DIR##*/}/${subvol}-" | \
            sort -t'-' -k2 | tail -1 | awk '{print $NF}' || true)

        # 创建新快照
        local new_snap=$(create_snapshot "$subvol")

        # 发送增量备份
        send_incremental "$subvol" "$new_snap" "$latest"

        # 验证备份
        verify_backup "$subvol" "$new_snap"

        # 清理旧快照
        cleanup_old_snapshots "$subvol" "$KEEP_LOCAL_SNAPS"
    done

    log "=== Backup completed ==="
}

main "$@"

15.4 监控告警

15.4.1 监控指标

指标检查命令告警阈值
磁盘使用率btrfs filesystem usage> 85%
设备错误btrfs device stats> 0
Scrub 错误scrub status任何错误
只读状态mount 检查只读
空间碎片filesystem df分配/使用比 > 2
子卷数量subvolume list> 500

15.4.2 综合监控脚本

#!/bin/bash
# btrfs-monitor.sh - Btrfs 综合监控脚本
set -euo pipefail

MOUNT_POINT="${1:?Usage: $0 /mount/point}"
ALERT_EMAIL="${2:-admin@example.com}"

# 告警阈值
DISK_WARN=80
DISK_CRIT=90

send_alert() {
    local level="$1"
    local subject="$2"
    local body="$3"
    echo "$body" | mail -s "[$level] Btrfs Alert: $subject" "$ALERT_EMAIL"
}

check_disk_usage() {
    local usage
    usage=$(df -h "$MOUNT_POINT" | tail -1 | awk '{print $5}' | tr -d '%')
    
    if (( usage >= DISK_CRIT )); then
        send_alert "CRITICAL" "Disk usage at ${usage}%" \
            "Mount point: $MOUNT_POINT\nUsage: ${usage}%\nPlease free space immediately."
    elif (( usage >= DISK_WARN )); then
        send_alert "WARNING" "Disk usage at ${usage}%" \
            "Mount point: $MOUNT_POINT\nUsage: ${usage}%"
    fi
}

check_device_errors() {
    local errors
    errors=$(sudo btrfs device stats "$MOUNT_POINT" 2>/dev/null | grep -v " 0$" | grep -v "^[/")
    
    if [[ -n "$errors" ]]; then
        send_alert "WARNING" "Device errors detected" \
            "Mount point: $MOUNT_POINT\n\nErrors:\n$errors"
    fi
}

check_readonly() {
    if mount | grep "$MOUNT_POINT" | grep -q "(ro,"; then
        send_alert "CRITICAL" "Filesystem is read-only" \
            "Mount point: $MOUNT_POINT is mounted read-only!"
    fi
}

check_scrub_errors() {
    local status
    status=$(sudo btrfs scrub status "$MOUNT_POINT" 2>/dev/null)
    
    if echo "$status" | grep -q "uncorrectable"; then
        send_alert "CRITICAL" "Scrub found uncorrectable errors" \
            "Mount point: $MOUNT_POINT\n\nScrub status:\n$status"
    fi
}

check_space_fragmentation() {
    local allocated used ratio
    allocated=$(sudo btrfs filesystem usage "$MOUNT_POINT" | grep "Device allocated" | awk '{print $3}' | numfmt --from=iec)
    used=$(sudo btrfs filesystem usage "$MOUNT_POINT" | grep "^Used:" | awk '{print $2}' | numfmt --from=iec)
    
    if (( used > 0 )); then
        ratio=$(( allocated * 100 / used ))
        if (( ratio > 200 )); then
            send_alert "WARNING" "High space fragmentation" \
                "Mount point: $MOUNT_POINT\nAllocated/Used ratio: ${ratio}%\nConsider running balance."
        fi
    fi
}

main() {
    echo "=== Btrfs Monitor Report ==="
    echo "Mount point: $MOUNT_POINT"
    echo "Time: $(date '+%Y-%m-%d %H:%M:%S')"
    echo ""

    check_disk_usage
    check_device_errors
    check_readonly
    check_scrub_errors
    check_space_fragmentation

    echo "Checks completed."
}

main

15.4.3 Prometheus + Grafana 监控

# prometheus-btrfs-exporter.py 示例配置
# 使用 node_exporter 的 textfile collector

#!/usr/bin/env python3
import subprocess
import json
import os

MOUNT_POINTS = ["/data", "/backup"]
OUTPUT_DIR = "/var/lib/node_exporter/textfile_collector"

def get_btrfs_metrics(mount_point):
    metrics = []
    
    # 空间使用
    result = subprocess.run(
        ["btrfs", "filesystem", "usage", mount_point],
        capture_output=True, text=True
    )
    for line in result.stdout.splitlines():
        if "Device size" in line:
            size = line.split(":")[1].strip().split()[0]
            metrics.append(f'btrfs_device_size{{mount="{mount_point}"}} {size}')
        elif "Used:" in line:
            used = line.split(":")[1].strip().split()[0]
            metrics.append(f'btrfs_used{{mount="{mount_point}"}} {used}')
    
    # 设备错误
    result = subprocess.run(
        ["btrfs", "device", "stats", mount_point],
        capture_output=True, text=True
    )
    for line in result.stdout.splitlines():
        if "errs" in line:
            parts = line.split()
            device = parts[0].strip("[]")
            metric = parts[1]
            value = parts[2]
            metrics.append(f'btrfs_device_errors{{mount="{mount_point}",device="{device}",type="{metric}"}} {value}')
    
    return metrics

def main():
    all_metrics = []
    for mp in MOUNT_POINTS:
        all_metrics.extend(get_btrfs_metrics(mp))
    
    output_file = os.path.join(OUTPUT_DIR, "btrfs.prom")
    with open(output_file, "w") as f:
        f.write("\n".join(all_metrics) + "\n")

if __name__ == "__main__":
    main()

15.5 生产 Checklist

15.5.1 部署前 Checklist

  • 内核版本 >= 6.1 LTS
  • btrfs-progs >= 6.0
  • 规划好子卷布局
  • 选择合适的 RAID 级别(不用 RAID 5/6)
  • 确定压缩策略(zstd:1-3)
  • SSD 配置 ssd,discard=async
  • 数据库目录关闭 COW
  • 配置监控告警
  • 配置自动化备份
  • 测试备份恢复流程
  • 文档化所有配置

15.5.2 日常运维 Checklist

每日

  • 检查备份是否成功
  • 检查设备错误统计
  • 检查磁盘空间使用

每周

  • 执行 scrub 检查
  • 检查 scrub 结果
  • 验证备份完整性
  • 检查快照清理是否正常

每月

  • 执行 balance(如果需要)
  • 检查空间碎片化
  • 审查快照保留策略
  • 更新 btrfs-progs(如有安全更新)

每季度

  • 完整备份恢复测试
  • 性能基准测试
  • 容量规划评估
  • 内核版本评估

15.5.3 变更管理 Checklist

  • 在测试环境验证变更
  • 创建变更前快照
  • 计划维护窗口
  • 准备回滚方案
  • 通知相关人员
  • 执行变更
  • 验证变更结果
  • 保留变更快照 7 天

15.6 性能调优 Checklist

15.6.1 SSD 优化

  • 启用 ssd 挂载选项
  • 启用 discard=async 或定期 fstrim
  • 使用 noatime,nodiratime
  • I/O 调度器设为 mq-deadlinenone
  • 使用 zstd:1 压缩(低 CPU 开销)
  • 数据库目录关闭 COW

15.6.2 HDD 优化

  • 使用 autodefrag(桌面/小文件场景)
  • 使用 zstd:3-5 压缩(减少磁盘 I/O)
  • I/O 调度器设为 bfq
  • 定期碎片整理
  • RAID 使用 RAID 1/10

15.7 故障恢复预案

15.7.1 预案模板

## Btrfs 文件系统故障恢复预案

### 场景 1:空间不足 (enospc)
1. 检查空间使用:`btrfs filesystem usage /mnt`
2. 清理快照:`btrfs subvolume delete /mnt/@snapshots/old-*`
3. 执行 balance:`btrfs balance start -dusage=20 /mnt`
4. 如仍不足,添加设备或清理数据

### 场景 2:只读文件系统
1. 检查设备错误:`btrfs device stats /mnt`
2. 检查内核日志:`dmesg | grep btrfs`
3. 尝试 remount:`mount -o remount,rw /mnt`
4. 如失败,使用 rescue 模式:`mount -o rescue=usebackuproot /dev/sdb1 /mnt`
5. 如仍失败,从备份恢复

### 场景 3:设备故障 (RAID 1)
1. 确认降级模式:`btrfs filesystem show /mnt`
2. 替换设备:`btrfs replace start /dev/old /dev/new /mnt`
3. 等待替换完成:`btrfs replace status /mnt`
4. 执行 scrub 验证:`btrfs scrub start /mnt`

### 场景 4:数据损坏
1. 从快照回滚
2. 如无快照,尝试 `btrfs restore`
3. 从备份恢复

15.8 运维工具集

15.8.1 推荐工具

工具用途安装
btrfs-progsBtrfs 管理工具系统自带
compsize压缩统计apt install compsize
snapper快照管理apt install snapper
btrbk快照备份apt install btrbk
smartmontools磁盘健康apt install smartmontools
fio性能测试apt install fio
iostatI/O 监控apt install sysstat

15.8.2 btrbk 配置示例

# /etc/btrbk/btrbk.conf
# btrbk - Btrfs 快照和备份管理工具

volume /mnt/data
    subvolume @home
        snapshot_dir  @snapshots
        snapshot_create onchange
        snapshot_preserve_min 3d
        snapshot_preserve 7d 4w 6m
        
    subvolume @var
        snapshot_dir  @snapshots
        snapshot_create daily
        snapshot_preserve 14d

volume ssh://backup-server/backup
    target /data-backup
    subvolume @home
        receive_preserve 30d
# 运行 btrbk
sudo btrbk run

# 查看状态
sudo btrbk list

# 检查配置
sudo btrbk diff

15.9 本章小结

核心最佳实践

  1. 版本选择:使用 LTS 内核(6.1/6.6)+ 最新 btrfs-progs
  2. 子卷规划:安装时就规划好,数据库目录关闭 COW
  3. 备份策略:遵循 3-2-1 规则,定期验证恢复
  4. 监控告警:监控空间、设备错误、scrub 结果
  5. 定期维护:每周 scrub,按需 balance
  6. 避免 RAID 5/6:使用 RAID 1/10/1c3
  7. 文档化:记录所有配置和操作流程
  8. 测试恢复:定期验证备份可恢复

扩展阅读