zfs 的 snapshot 備份

最近重新整理了 mail server,改用 zfs 做存 mail 的 file system,也重新調整了備份機制。

現在是透過 zfs snapshot 做資料備份,一小時打一次 snapshot 後丟到其他地方存著。

下面是參考這裡調整後的 script。

#!/bin/sh

# take the appropriately named snapshot
create_snapshot()
{
    # enumerate datasets under this pool or dataset, skip excluded datasets if requested
    datasets=$(zfs list -r -H -o name $pool)

    # loop through datasets, do snapshots of each
    for dataset in $datasets; do
        snapshot="$dataset@$now"
        # look for an existing snapshot with this name
        if zfs list $snapshot > /dev/null 2>&1; then
            echo "     snapshot $snapshot already exists, skipping..."
        else
#            echo "     taking snapshot: $snapshot"
            zfs snapshot $snapshot
        fi
    done
}

# delete the named snapshot
delete_snapshot()
{
    snapshot=$1
#    echo "     destroying old snapshot, $snapshot"
    zfs destroy -r $snapshot
}

# backup_snapshot
backup_snapshot()
{
    # enumerate datasets under this pool or dataset, skip excluded datasets if requested
    datasets=$(zfs list -r -H -o name $pool)

    # loop through datasets, do snapshots of each
    for dataset in $datasets; do
        snapshot="$dataset@$now"
        # look for an existing snapshot with this name
        zfs send $snapshot | gzip > $dest/$now.gz
    done
}

# take a type snapshot of pool, keeping keep old ones
do_pool()
{
    pool=$1
    keep=$2
    type=$3
    dest=$4

    # create the regex matching the type of snapshots we're currently working
    # on
    case "$type" in
        hourly)
        # hourly-2009-01-01-00
        regex="^$pool@$type-[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]$"
        ;;
        daily)
        # daily-2009-01-01
        regex="^$pool@$type-[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$"
        ;;
        weekly)
        # weekly-2009-01
        regex="^$pool@$type-[0-9][0-9][0-9][0-9]-[0-9][0-9]"
        ;;
        monthly)
        # monthly-2009-01
        regex="^$pool@$type-[0-9][0-9][0-9][0-9]-[0-9][0-9]"
        ;;
        *)
        echo "unknown snapshot type: $type"
        exit 1
    esac

    case "$type" in
        hourly)
        now=`date +"$type-%Y-%m-%d-%H"`
        ;;
        daily)
        now=`date +"$type-%Y-%m-%d"`
        ;;
        weekly)
        now=`date +"$type-%Y-%U"`
        ;;
        monthly)
        now=`date +"$type-%Y-%m"`
        ;;
        *)
        echo "unknown snapshot type: $type"
        exit 1
    esac

    create_snapshot $pool $type $now
    backup_snapshot $pool $type $now $dest

    # get a list of all of the snapshots of this type sorted alpha, which
    # effectively is increasing date/time
    # (using sort as zfs's sort seems to have bugs)
    snapshots=`zfs list -H -o name -t snapshot | sort | grep $regex`
    # count them
    count=`echo $snapshots | wc -w`
    if [ $count -ge 0 ]; then
        # how many items should we delete
        delete=`expr $count - $keep`
        count=0
        # walk through the snapshots, deleting them until we've trimmed deleted
        for snapshot in $snapshots; do
            if [ $count -ge $delete ]; then
                break
            fi
            delete_snapshot $snapshot
            count=`expr $count + 1`
        done
    fi
}

# take snapshots of type, for pools, keeping keep old ones,
do_snapshots()
{
    pools=$1
    keep=$2
    type=$3
    dest=$4

#    echo ""
#    echo "Doing zfs $type snapshots:"
    for pool in $pools; do
        do_pool $pool $keep $type $dest
    done
}

# Function to display usage:
usage() {
    scriptname=`/usr/bin/basename $0`
    echo "$scriptname: Take and rotate snapshots on a ZFS file system"
    echo
    echo "  Usage:"
    echo "  $scriptname target perodic count dest"
    echo
    echo "  target:    ZFS file system to act on"
    echo "  type:      hourly/daily/weekly/monthly"
    echo "  count:     Number of snapshots with perodic keep at one time"
    echo "  dest:      Destination of snapshot backup"
    echo
    exit
}

TARGET=$1
TYPE=$2
COUNT=$3
DEST=$4

# Basic argument checks:
if [ -z $TYPE ] ; then
    usage
fi

if [ ! -z $5 ] ; then
    usage
fi

do_snapshots "$TARGET" $COUNT $TYPE $DEST

假設把 script 丟 /usr/local/sbin/zfs-snapshot,然後用 crontab 來做自動備份

想要 hourly 備份一次,把 snapshot gzip 後丟到 /backup 下,且在 zfs 上最多留 24 份 hourly snapshot

1 * * * *  root /usr/local/sbin/zfs-snapshot ZPOOL hourly 24 /backup > /dev/null 2>&1

想要 daily 備份一次,把 snapshot gzip 後丟到 /backup 下,且在 zfs 上最多留 7 份 daily snapshot

1 * * * *  root /usr/local/sbin/zfs-snapshot ZPOOL daily 7 /backup > /dev/null 2>&1

想要 weekly 備份一次,把 snapshot gzip 後丟到 /backup 下,且在 zfs 上最多留 4 份 weekly snapshot

1 * * * *  root /usr/local/sbin/zfs-snapshot ZPOOL weekly 4 /backup > /dev/null 2>&1

想要 monthly 備份一次,把 snapshot gzip 後丟到 /backup 下,且在 zfs 上最多留 6 份 monthly snapshot

1 * * * *  root /usr/local/sbin/zfs-snapshot ZPOOL monthly 6 /backup > /dev/null 2>&1

當然,再稍加修改一下還可以有更彈性的做法,例如加密後丟上 S3 之類的…

不過因為我們家 mail server 的資料量太大,所以沒有這樣搞…

本篇發表於 FreeBSD 並標籤為 , , , 。將永久鏈結加入書籤。

發表迴響