Btrfs 文件系统运维完全教程 / 第 15 章:运维最佳实践
第 15 章:运维最佳实践
15.1 运维规范总览
15.1.1 核心原则
| 原则 | 说明 |
|---|---|
| 备份优先 | 任何操作前确认备份可用 |
| 渐进式变更 | 逐步应用变更,避免大规模一次性操作 |
| 监控先行 | 部署前先配置好监控和告警 |
| 文档化 | 记录所有配置和操作 |
| 测试验证 | 变更前在测试环境验证 |
15.1.2 版本选择指南
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| Linux 内核 | 6.1 LTS 或 6.6 LTS | 长期支持版本 |
| btrfs-progs | v6.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-deadline或none - 使用
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-progs | Btrfs 管理工具 | 系统自带 |
| compsize | 压缩统计 | apt install compsize |
| snapper | 快照管理 | apt install snapper |
| btrbk | 快照备份 | apt install btrbk |
| smartmontools | 磁盘健康 | apt install smartmontools |
| fio | 性能测试 | apt install fio |
| iostat | I/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 本章小结
核心最佳实践:
- 版本选择:使用 LTS 内核(6.1/6.6)+ 最新 btrfs-progs
- 子卷规划:安装时就规划好,数据库目录关闭 COW
- 备份策略:遵循 3-2-1 规则,定期验证恢复
- 监控告警:监控空间、设备错误、scrub 结果
- 定期维护:每周 scrub,按需 balance
- 避免 RAID 5/6:使用 RAID 1/10/1c3
- 文档化:记录所有配置和操作流程
- 测试恢复:定期验证备份可恢复