字数 1605,阅读大约需 9 分钟
ZFS 快照是 ZFS 最"魔幻"的特性之一——几乎不占空间、瞬间创建,比 LVM 快照好用十倍。本篇讲清原理、用法、自动化和最佳实践。
一、快照是什么?
ZFS 快照 = dataset 在某时刻的只读副本。
tank/data
├── 原始 block: 100, 200, 300
│
├── 快照@before:
│ └── 共享原始 block(指针共享)
│
└── 修改后(覆盖 block 200):
├── 新 block 200' 在位置 500
└── 旧 block 200 仍在位置 200(被 @before 引用)
关键特性:
二、创建快照
2.1 基本用法
# 创建快照,命名为 snap1
$ zfs snapshot tank/data@snap1
# 用自定义时间
$ zfs snapshot tank/data@2024-01-15
$ zfs snapshot tank/data@2024-01-15-0300
# 一次拍多个 dataset
$ zfs snapshot tank/home/alice@daily
$ zfs snapshot tank/home/bob@daily
2.2 看快照
# 列出所有快照
$ zfs list -t snapshot
NAME USED AVAIL REFER MOUNTPOINT
tank/data@snap1 8K - 100M -
tank/home/alice@daily 16K - 1.2G -
tank/home/bob@daily 8K - 800M -
# 看某个 dataset 的所有快照
$ zfs list -t snapshot -r tank/data
# 详细信息
$ zfs get all tank/data@snap1
USED 字段是快照本身占的空间(COW 复制的数据)。
2.3 访问快照数据
ZFS 默认隐藏快照(通过 .zfs 隐藏目录),但可以访问:
# 列出隐藏目录
$ ls -la /tank/data/.zfs
dr-xr-xr-x ... snapshot/
# 访问快照数据
$ ls /tank/data/.zfs/snapshot/snap1/
file1.txt file2.txt ...
或者用 ZFS 命令访问:
# 克隆(见下文)
$ zfs clone tank/data@snap1 tank/data-snap1
# 发送
$ zfs send tank/data@snap1
2.4 启用可见的 .zfs 目录
$ zfs set snapdir=visible tank/data
# 之后 /tank/data/.zfs/snapshot/ 可以直接 ls/cd
三、克隆(Clone)
克隆 = 从快照创建的可写 dataset。
# 1. 创建快照
$ zfs snapshot tank/data@snap1
# 2. 从快照克隆
$ zfs clone tank/data@snap1 tank/data-clone
# 3. 克隆可以独立修改
$ echo "new" > /tank/data-clone/file.txt
# 4. 源 dataset 不受影响
$ cat /tank/data/file.txt
(原始内容)
3.1 克隆的注意事项
- • 克隆本质上是写时复制:未修改的 block 仍引用原快照
- • 删除原快照前必须先 promote 克隆(见下文)
- • 克隆可以挂在原 dataset 路径下(
canmount=noauto)
3.2 promote 克隆
把克隆"提升"为独立 dataset:
# 把 tank/data-clone 提升为顶层
$ zfs promote tank/data-clone
# 之后可以删除原 tank/data@snap1
$ zfs destroy tank/data@snap1
重要:删除有克隆的快照,不会删除数据,但会保留这个依赖关系。
promote 把依赖翻转:原 dataset 反过来成为克隆。
四、销毁快照
# 销毁单个快照
$ zfs destroy tank/data@snap1
# 销毁所有快照
$ zfs destroy tank/data@%
# 递归销毁(含子 dataset 的所有快照)
$ zfs destroy -r tank/data@%
警告:删除快照后,当前 dataset 中"被快照引用"的旧版本数据立即被回收。
五、快照与性能
5.1 性能影响
- • 修改 dataset 时,被快照覆盖的 block会先复制到快照空间(COW)
普通 dataset 写:
block 100 → 新位置 500(一次性写)
有快照的 dataset 写:
block 100 → 复制 100 到快照空间
写新数据到 500
两次写操作
所以:大量快照 + 大量写入会拖慢性能。
5.2 监控
# 看快照占用
$ zfs list -t snapshot -o name,used,referenced
# 看 dataset + 快照总占用
$ zfs list -o name,used,usedbychildren,usedbydatasets,usedbysnapshots
六、自动快照
6.1 手工 cron 方案
# 每天凌晨 3 点拍快照,保留 30 天
$ cat /etc/cron.d/zfs-snapshot
0 3 * * * root /usr/sbin/zfs snapshot -r tank@auto-$(date +\%Y\%m\%d)
30 3 * * * root /usr/sbin/zfs destroy -r tank@auto-$(date -d '30 days ago' +\%Y\%m\%d)
6.2 高级工具:zfs-auto-snapshot
# 安装(Debian/Ubuntu)
$ apt install zfs-auto-snapshot
# 启动
$ systemctl enable --now zfs-auto-snapshot.timer
# 配置(/etc/zfs-auto-snapshot.conf)
# 默认:每 15 分钟拍快照,保留 N 天
# 手动拍
$ zfs-auto-snapshot --verbose --keep=10 tank/data
6.3 高级工具:sanoid + syncoid(强烈推荐)
sanoid 是 ZFS 自动快照的"瑞士军刀":
# 安装
$ apt install sanoid
# 配置文件 /etc/sanoid/sanoid.conf
[tank/data]
use_template = production
recursive = yes
[tank/home]
use_template = production
[template_production]
frequent = 4
hourly = 24
daily = 30
monthly = 6
yearly = 1
autosnap = yes
autoprune = yes
sanoid 拍快照,syncoid 同步到另一台机器(zfs send/recv 包装)。
# 同步 tank/data 到 backup 机器
$ syncoid tank/data backup:backup/data
# 增量同步
$ syncoid tank/data backup:backup/data
七、实战:快照备份策略
7.1 "3-2-1" 备份原则
ZFS 天然支持:
# 1 份:主 pool
# 1 份:本地备份 pool(不同盘)
# 1 份:远程 backup 机器(zfs send/recv)
7.2 自动备份脚本
#!/bin/bash
# /usr/local/bin/zfs-backup.sh
SOURCE=tank/data
TARGET=backup-machine:backup/data
SNAP=auto-$(date +%Y%m%d-%H%M%S)
# 1. 拍快照
zfs snapshot $SOURCE@$SNAP
# 2. 同步到远程(增量)
syncoid --no-sync-snap $SOURCE $TARGET
# 3. 清理 7 天前快照
zfs destroy $SOURCE@$(date -d '7 days ago' +%Y%m%d-%H%M%S)
八、快照常见问题
1. "dataset is busy"
# 通常是 dataset 还在被访问
$ lsof | grep tank/data
# 或
$ fuser /tank/data
2. "cannot destroy snapshot: has dependent clones"
# 必须先 promote 克隆或删除克隆
$ zfs list -t snapshot
$ zfs list -o clones tank/data@snap1
# NAME ...
# tank/data-clone@origin
# 选项 1:删除克隆
$ zfs destroy tank/data-clone
# 选项 2:promote 克隆(推荐)
$ zfs promote tank/data-clone
$ zfs destroy tank/data@snap1
3. 快照占满空间
# 看占用
$ zfs list -t snapshot -o name,used
# 删旧的
$ zfs destroy tank/data@old-snap
# 调整快照保留策略
$ vim /etc/sanoid/sanoid.conf
4. .zfs/snapshot 不可见
$ zfs set snapdir=visible tank/data
但修改 .zfs 目录的内容是不允许的(只读)。
九、快照的高级玩法
9.1 一次性可写副本
# 想做"基于生产的临时测试环境":
$ zfs snapshot tank/db@now
$ zfs clone tank/db@now tank/db-test
$ zfs set mountpoint=/var/lib/pgsql-test tank/db-test
# 之后随便改,不影响生产
# 改完废弃
$ zfs destroy tank/db-test
$ zfs destroy tank/db@now
9.2 容器镜像
# 基础镜像
$ zfs create tank/images/ubuntu-base
# 安装软件...
# 给每个容器做 snapshot + clone
$ zfs snapshot tank/images/ubuntu-base@v1
$ zfs clone tank/images/ubuntu-base@v1 tank/images/ubuntu-container-001
$ zfs clone tank/images/ubuntu-base@v1 tank/images/ubuntu-container-002
# 每个 container 独立可写,但共享 base block(极省空间)
9.3 数据库升级前快照
$ zfs snapshot tank/db@before-pg-upgrade
# 升级 PG
$ pg_upgradecluster ...
# 失败?回滚:
$ zfs rollback tank/db@before-pg-upgrade
rollback 是另一个"魔法"操作,后续详细介绍。
十、本篇小结
| |
|---|
| zfs snapshot tank/data@snap1 |
| zfs list -t snapshot |
| /tank/data/.zfs/snapshot/snap1/ |
| zfs clone tank/data@snap1 tank/data-clone |
| zfs promote tank/data-clone |
| zfs destroy tank/data@snap1 |
| zfs destroy tank/data@% |
核心要点: