Automatic ZFS Snapshot Rotation on FreeBSD
OpenSolaris has ZFS Automatic Snapshots; FreeBSD, while it has ZFS, doesn’t have a comparable feature that I’m aware of. So I wrote my own, zfs-snapshot.sh
:
#!/usr/local/bin/bash # Path to ZFS executable: ZFS=/sbin/zfs # Parse arguments: TARGET=$1 SNAP=$2 COUNT=$3 # 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 snap_name count" echo echo " target: ZFS file system to act on" echo " snap_name: Base name for snapshots, to be followed by a '.' and" echo " an integer indicating relative age of the snapshot" echo " count: Number of snapshots in the snap_name.number format to" echo " keep at one time. Newest snapshot ends in '.0'." echo exit } # Basic argument checks: if [ -z $COUNT ] ; then usage fi if [ ! -z $4 ] ; then usage fi # Snapshots are number starting at 0; $max_snap is the highest numbered # snapshot that will be kept. max_snap=$(($COUNT -1)) # Clean up oldest snapshot: if [ -d /${TARGET}/.zfs/snapshot/${SNAP}.${max_snap} ] ; then $ZFS destroy -r ${TARGET}@${SNAP}.${max_snap} fi # Rename existing snapshots: dest=$max_snap while [ $dest -gt 0 ] ; do src=$(($dest - 1)) if [ -d /${TARGET}/.zfs/snapshot/${SNAP}.${src} ] ; then $ZFS rename -r ${TARGET}@${SNAP}.${src} ${TARGET}@${SNAP}.${dest} fi dest=$(($dest - 1)) done # Create new snapshot: $ZFS snapshot -r ${TARGET}@${SNAP}.0
From the command line, call the script something like the following:
./zfs-snapshot.sh tank weekly 5
This would take a recursive snapshot of the “tank” zpool with the basename weekly, rotating through five snapshots with names “weekly.0” through “weekly.4”. This allows you to implement a snapshot scheme approximately similar to NetApp’s hourly-daily-weekly scheme, if you like. Because my FreeBSD workstation isn’t on 24×7, I run hourly snapshots out of /etc/crontab
:
# Automated ZFS backups (hourly): 0 * * * * root /root/bin/zfs-snapshot.sh tank hourly 25
And daily/weekly/monthly snapshots out of /usr/local/etc/anacrontab
(from the sysutils/anacron port):
PATH=/bin:/sbin:/usr/bin:/usr/sbin # days make sure the command is executed at least every 'days' days # delay delay in minutes, before a command starts # id unique id of a command # days delay id command 1 5 daily periodic daily 1 10 daily_snap /root/bin/zfs-snapshot.sh tank daily 8 7 15 weekly periodic weekly 7 30 weekly_snap /root/bin/zfs-snapshot.sh tank weekly 5 30 60 monthly periodic monthly 30 90 monthly_snap /root/bin/zfs-snapshot.sh tank monthly 13
(Of course, this isn’t as cool as the Gnome-integrated Time Slider in OpenSolaris, but it scratches my itch sufficiently.)
Thanks for this script!
But, can You make change this script to:
script creates a snapshot of the real date/time?
Like tank@12:00:00-01-31-11
Best Regards.
P.s.
Sorry for my English 🙂
@Desper – The challenge there would be associating a particular time stamp with the type of snapshot. Would you require that your dailies/weeklies/monthlies always run at the same time of day and determine your snapshot type from that? Or would you add a prefix or change the naming convention for the non-hourlies?
(One nice thing about the time stamp is that you could eliminate the zfs rename portion of the script.)
Hi there,
Am running FreeBSD 8.2 with the v28 pool patch, i think something must have changed in the command syntax – i vaguely remember this when i was running OpenSolaris that zfs list changed…
cannot create snapshot ‘tank/vol0/users@hourly.0’: dataset already exists
no snapshots were created
Looks like the rename is not working, will investigate further…
Ah ha, its this bit:
# Clean up oldest snapshot:
if [ -d /${TARGET}/.zfs/snapshot/${SNAP}.${max_snap} ] ; then
$ZFS destroy -r ${TARGET}@${SNAP}.${max_snap}
fi
its because i have the zfs mounted differently from the zpool name:
ie tank/vol0/users@hourly.0
is mounted as /vol0/users
Thanks for your script! I did a small change to use the real mount point with:
TARGET_MOUNT=$($ZFS get -H -o value mountpoint $TARGET)
I put a copy on my own blog http://hype-o-thetic.com/2011/03/22/andyleonard-com-zfs-snapshot-sh/
Thanks for the nice script!
I made it compatible with zfs-fuse which doesn’t have .zfs directories:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
zfs-snapshot.sh
hosted with ❤ by GitHub
@Ertug – Thanks – and interesting timing. I had just written almost the same thing to work around FreeBSD’s kern/156781 bug (the .zfs/snapshot directory stops working).
@Ertug – Thanks! the current version of zfsonlinux also lacks the .zfs directory. You just saved me a lot of time.
I’m setting this up now on my home server, it looks like exactly what I was after. Many thanks for your efforts!
Great script and documentation — thanks for all of the time you saved me. Nice to see someone giving back with some KISS code/documentation.
For those interested in having the time stamp as the snapshot name, I have modified this script a bit and this is the result. Be aware that it hasn’t been fully tested, so use it at your own risk:
#! /bin/sh
# Path to ZFS executable:
ZFS=`which zfs`
# Parse arguments:
TARGET=$1
S_TYPE=$2
COUNT=$3
# 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 snap_type count”
echo
echo ” target: ZFS file system to act on”
echo ” snap_type: Internal identifier for snapshots of this kind”
echo ” (e.g. hourly, daily, monthly, …)”
echo ” count: Number of snapshots of the type to keep at one time”
echo
exit
}
# Basic argument checks:
if [ -z $COUNT ]; then
usage
fi
if [ ! -z $4 ]; then
usage
fi
# Create new snapshot using current time stamp:
DATE=`date ‘+%Y-%m-%d %H.%M.%S’`
$ZFS snapshot -r -o snapshot:type=$S_TYPE “$TARGET@$DATE”
# Get list of snapshots ordered by creation time and delete the ones which
# are no longer needed
snap_count=0
$ZFS list -d 1 -H -o snapshot:type,name -S creation -t snapshot $TARGET |
while read snap_type snap_name
do
if [ “$snap_type” == “$S_TYPE” ]; then
snap_count=`expr $snap_count + 1`
if [ $snap_count -gt $COUNT ]; then
$ZFS destroy -r “$snap_name”
fi
fi
done
2bbt: nice script improvements, thanks a lot
please be careful with copy-paste, some symbols become broken
Thanks for the script, great work !
Is it possible to have a synchronized copy of all pools/snapshots on another backup server? Probably by using zfs send/receive?
Just wanted to pop on to say thanks for this script 🙂
I’ve been using this script without issue for quite some time (using gimpe’s TARGET_MOUNT modifications). I’m running into some issues, which appear to be documented here. Figured it worth a mention. I’ll update if I uncover anything worth documenting.