Backup zfs

J’ai mis en place un système de backup de mon serveur FreeBSD (dédié) vers un autre serveur FreeBSD (@home). Les deux machines sont en zfs, c’était pour moi l’occasion de tester zfs (send|recv) over ssh. L’idée c’est qu’on envoie les donnés de manière incrémentale entre un snapshot du jour et un snapshot de la veille. Ce que l’on gagne par rapport à des solutions comme rsync c’est de la rapidité et moins d’accès disques (zfs sait exactement ce qui a bougé entre deux snapshot), l’autre avantage c’est sur la machine de backup on a un snapshot par jour, on peut donc facilement retrouver des fichiers supprimés.

#!/bin/sh
set -e
# Used in ssh command, example user@host -p 2222
REMOTE_HOST="diophante"
# Source zfs pool
POOL_SRC="tank"
# zfs sets to backup (relative to POOL_SRC)
SETS="usr/local/vmail usr/local/data usr/local/pgsql/backups usr/home usr/local/git var/backups"
# Destination pool
POOL_DST="tank/backup/${REMOTE_HOST}"
# We use ssh connection sharing
SSH_ARGS="-o ControlPath=~/.ssh/%r@%h:%p"
# zfs snapshot prefix => tank/foo@bck-2011-05-25
PREFIX="bck-"
# Remote zfs command
REMOTE_ZFS="ssh ${SSH_ARGS} ${REMOTE_HOST} sudo zfs"
# Local zfs command
LOCAL_ZFS="sudo zfs"
# Launch master ssh for sharing connections
ssh -MNn ${SSH_ARGS} ${REMOTE_HOST} &
ssh_master_pid=$!
for zfs_set in ${SETS}
do
# Test if destination exist
${LOCAL_ZFS} list -H ${POOL_DST}/${zfs_set} >/dev/null
date_suffix="${PREFIX}`date +%F`"
snap="${POOL_SRC}/${zfs_set}@${date_suffix}"
old_snap=`${REMOTE_ZFS} list -Ht snapshot 2>/dev/null | grep "^${POOL_SRC}/${zfs_set}@${PREFIX}" 2>/dev/null| awk -F' ' '{ print $1 }' 2>/dev/null`
if [ "${old_snap}" ]
then
if [ "${old_snap}" != "`echo ${old_snap} | head -n 1`" ]
then
echo "[!] Multiple zfs snapshot found: ${old_snap}"
echo "[!] Consider changing PREFIX or fix the issue yourself"
continue
fi
if [ "${old_snap}" = "${snap}" ]
then
echo "[!] ${snap} exists"
continue
fi
fi
echo ${REMOTE_ZFS} snapshot "${snap}"
${REMOTE_ZFS} snapshot "${snap}"
if [ "${old_snap}" ]
then
extra_args="-i ${old_snap}"
else
extra_args=""
fi
echo ${REMOTE_ZFS} send $extra_args "${snap}" '|' ${LOCAL_ZFS} recv -F "${POOL_DST}/${zfs_set}"
${REMOTE_ZFS} send $extra_args "${snap}" | ${LOCAL_ZFS} recv -F "${POOL_DST}/${zfs_set}"
if [ "${old_snap}" ]
then
echo ${REMOTE_ZFS} destroy "${old_snap}"
${REMOTE_ZFS} destroy "${old_snap}"
fi
done
kill $ssh_master_pid

Le script est aussi disponible sous forme de gist github.

Sur mes deux machines j’ai un user backup qui peut exécuter zfs avec sudo, l’user backup sur la machine de backup peut accéder en ssh à l’user backup (vous suivez ?) sur la machine à sauvegarder au moyen d’une clé ssh dédiée sans mot de passe.

Le script se lance donc sur la machine de backup (en ayant pris soin de créer tous les sets zfs qu’on va sauvegarder). La première fois il va transférer le set en entier, et les jours suivants il va envoyer de l’incrémental, il faut au minimum un jour entre chaque backup mais rien ne vous empêche de mettre l’heure dans le nom du snapshot.

Commentaires, patchs bienvenus.

PS: Ça fait 6 mois que j’ai pas posté sur ce blog, c’est parce que je travaille et même si je trouve encore un peu de temps pour faire mon propre code c’est moins le cas quand il faut le décrire ici. Par contre je maintient une liste de posts à faire sur ce blog, je rattraperais mon retard bientôt.