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:
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.