For a few years I have used the Christmas holidays to create a full
backup of my /home
on an external hard disk. For that I used a
Bash script around rsync that uses hard links to keep the used disk
space under control. Each backup was saved in a directory named with
the date of the backup. POSIX ACLs were also backed up.
Since last year’s backup I have moved to ZFS (using ZFS on Linux
with Ubuntu 14.04) as filesystem for /home
(and others). Since ZFS
makes checksums of data and metadata it has the possibility to
detect corrupted files (and if the data is redundant it can also fix
them). This is a feature I’d like to have for my backups as
well: I’d rather know it when corruption occurs than live in
ignorance.
So the plan is to move the old backups from the external disk to the
ZFS pool in my server. and instead of using hard links I’ll transfer
the backups in order from old to new to the ZFS pool making a
snapshot for each. Additionally I will also turn on compression
(using the lz4 algorithm). Once that is done I will reformat the
external drive and create a ZFS pool called “JaarlijkseBackupPool” on
it (jaarlijks means annual in Dutch).
The old situation
In the current/old situation, this is how much disk space is used
on the external disk (with and without taking the hard links into
account):
$ sudo du -csh /mnt/JaarlijkseBackups/*
102G /mnt/JaarlijkseBackups/2010-11-28
121G /mnt/JaarlijkseBackups/2013-02-04
101G /mnt/JaarlijkseBackups/2013-12-23
324G total
$ sudo du -clsh /mnt/JaarlijkseBackups/*
102G /mnt/JaarlijkseBackups/2010-11-28
193G /mnt/JaarlijkseBackups/2013-02-04
255G /mnt/JaarlijkseBackups/2013-12-23
549G total
Copying the data from the Ext4 disk to a temporary ZFS filesystem on my server
The ZFS pool in my server is called storage
. In order to save the
POSIX ACLs of the Ext4 system, they need to be enabled when
creating the ZFS filesystem as well. Setting xattr=sa
means the
ACLS are stored more efficiently (although this option is not
compatible with other ZFS implementations at this time, so if I
would try to import the ZFS pool in FreeBSD for example, that
information would be inaccessible).
$ zfs create storage/JaarlijkseBackupsOrganized \
-o compression=lz4 \
-o acltype=posixacl \
-o xattr=sa
$ sudo rsync -ahPAXHS --numeric-ids \
/storage/JaarlijkseBackups/2010-11-28/ \
/storage/JaarlijkseBackupsOrganized
$ zfs snapshot storage/JaarlijkseBackupsOrganized@2010-11-28
Followed by the same for the same rsync
and zfs snapshot
commands for the other two dates.
Once that is finished, this is the status of that ZFS FS:
$ zfs list -r -t all storage/JaarlijkseBackupsOrganized
NAME USED AVAIL REFER MOUNTPOINT
storage/JaarlijkseBackupsOrganized 275G 438G 272G /storage/JaarlijkseBackupsOrganized
storage/JaarlijkseBackupsOrganized@2010-11-28 1,03G - 88,9G -
storage/JaarlijkseBackupsOrganized@2013-02-04 2,33G - 196G -
storage/JaarlijkseBackupsOrganized@2013-12-23 0 - 272G -
$ zfs get -r -t all compressratio storage/JaarlijkseBackupsOrganized
NAME PROPERTY VALUE SOURCE
storage/JaarlijkseBackupsOrganized compressratio 1.13x -
storage/JaarlijkseBackupsOrganized@2010-11-28 compressratio 1.19x -
storage/JaarlijkseBackupsOrganized@2013-02-04 compressratio 1.14x -
storage/JaarlijkseBackupsOrganized@2013-12-23 compressratio 1.12x -
Partitioning the external disk
The external disk is as 1TB Samsung SATA 3Gbps SpinPoint F2 EcoGreen disk
(type HD103SI, serial number: S1VSJD6ZB02657). The disk uses 512B
sectors:
sudo hdparm -I /dev/sdf |grep Sector
Logical/Physical Sector size: 512 bytes
Before using it with ZFS, it needs to be partitioned. I used
parted:
$ parted /dev/sdf
GNU Parted 2.3
Using /dev/sdf
Welcome to GNU Parted! Type 'help' to view a list of commands.
(parted) p
Model: ATA SAMSUNG HD103SI (scsi)
Disk /dev/sdf: 1000GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Number Start End Size Type File system Flags
1 1049kB 1000GB 1000GB primary ext4
(parted) mklabel
New disk label type? gpt
(parted) u
Unit? [compact]? MB
(parted) p
Model: ATA SAMSUNG HD103SI (scsi)
Disk /dev/sdf: 1000205MB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Number Start End Size File system Name Flags
(parted) mkpart
Partition name? []? JaarlijkseBackups-HD103SI-S1VSJD6ZB02657
File system type? [ext2]? zfs
Start? 1M
End? 1000204M
(parted) p
Model: ATA SAMSUNG HD103SI (scsi)
Disk /dev/sdf: 1000205MB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Number Start End Size File system Name Flags
1 1,05MB 1000204MB 1000203MB ext4 JaarlijkseBackups-HD103SI-S1VSJD6ZB0
(parted) q
This removes the old partition table and creates a new GPT
partition table (which allows naming partitions). Next I set the
units to MB so I can leave 1MB at the beginning and end of the
partition (can be helpful when importing this pool in
e.g. FreeBSD). The disk also shows up in /dev/disk/by=partlabel
now.
Creating the new ZFS pool
$ zpool create -o ashift=9 JaarlijkseBackupPool \
/dev/disk/by-partlabel/JaarlijkseBackups-HD103SI-S1VSJD6ZB0
$ zpool status JaarlijkseBackupPool
pool: JaarlijkseBackupPool
state: ONLINE
scan: none requested
config:
NAME STATE READ WRITE CKSUM
JaarlijkseBackupPool ONLINE 0 0 0
JaarlijkseBackups-HD103SI-S1VSJD6ZB0 ONLINE 0 0 0
errors: No known data errors
Migrating the data
Now that the new ZFS pool and filesystem are all in place, it is
time to move the backups to the new place, starting with the oldest
backup. The -R
option also make sure the attributes like
compression
and xattr
are transferred to the new FS. The
following commands send each snapshot to the new pool (the -n
option of zfs receive
is for doing a dry run, just to show how it
works). After the first snapshot is sent, the other two are sent
using the -i
option to zfs send
so that only the incremental
differences between the snapshots are sent.
$ zfs send -vR storage/JaarlijkseBackupsOrganized@2010-11-28 | \
zfs receive -Fvu JaarlijkseBackupPool/oldRsyncBackups
$ zfs send -vR -i storage/JaarlijkseBackupsOrganized@2010-11-28 \
storage/JaarlijkseBackupsOrganized@2013-02-04 | \
zfs receive -Fvu JaarlijkseBackupPool/oldRsyncBackups
$ zfs send -vR -i storage/JaarlijkseBackupsOrganized@2013-02-04 \
storage/JaarlijkseBackupsOrganized@2013-12-23 | \
zfs receive -Fvu JaarlijkseBackupPool/oldRsyncBackups -n
send from @2013-02-04 to storage/JaarlijkseBackupsOrganized@2013-12-23 estimated size is 84,3G
total estimated size is 84,3G
TIME SENT SNAPSHOT
would receive incremental stream of storage/JaarlijkseBackupsOrganized@2013-12-23 into JaarlijkseBackupPool@2013-12-23
14:09:16 4,22M storage/JaarlijkseBackupsOrganized@2013-12-23
14:09:17 8,46M storage/JaarlijkseBackupsOrganized@2013-12-23
14:09:18 18,4M storage/JaarlijkseBackupsOrganized@2013-12-23
14:09:19 24,8M storage/JaarlijkseBackupsOrganized@2013-12-23
^C
$ zfs send -vR -i storage/JaarlijkseBackupsOrganized@2013-02-04 \
storage/JaarlijkseBackupsOrganized@2013-12-23 | \
zfs receive -Fvu JaarlijkseBackupPool/oldRsyncBackups
Add this year’s backup
At first I tried to add the new backups also to the
oldRsyncBackups
FS, but that didn’t work (at least not with an
incremental backup), so I ended up making a new backup. The extra
cost in disk space is not a real problem. Disk space is rather
cheap and the current configuration will last me at least one more
year. So after creating a snapshot called 2014-12-26
of my
/home
I ran:
$ zfs send -v storage/home@2014-12-26 | \
zfs receive -Fu JaarlijkseBackupPool/home
$ zfs list -r -t all JaarlijkseBackupPool
NAME USED AVAIL REFER MOUNTPOINT
JaarlijkseBackupPool 581G 332G 30K /JaarlijkseBackupPool
JaarlijkseBackupPool/home 311G 332G 311G /JaarlijkseBackupPool/home
JaarlijkseBackupPool/home@2014-12-26 51,2M - 311G -
JaarlijkseBackupPool/oldRsyncBackups 271G 332G 267G /JaarlijkseBackupPool/oldRsyncBackups
JaarlijkseBackupPool/oldRsyncBackups@2010-11-28 974M - 87,1G -
JaarlijkseBackupPool/oldRsyncBackups@2013-02-04 2,23G - 193G -
JaarlijkseBackupPool/oldRsyncBackups@2013-12-23 0 - 267G -
$ zfs get -r compressratio JaarlijkseBackupPool
NAME PROPERTY VALUE SOURCE
JaarlijkseBackupPool compressratio 1.15x -
JaarlijkseBackupPool/home compressratio 1.17x -
JaarlijkseBackupPool/home@2014-12-26 compressratio 1.17x -
JaarlijkseBackupPool/oldRsyncBackups compressratio 1.13x -
JaarlijkseBackupPool/oldRsyncBackups@2010-11-28 compressratio 1.19x -
JaarlijkseBackupPool/oldRsyncBackups@2013-02-04 compressratio 1.14x -
JaarlijkseBackupPool/oldRsyncBackups@2013-12-23 compressratio 1.12x -
Finishing up
In order to be able to disconnect the external drive without
damaging the filesystems use
zpool export JaarlijkseBackupPool
Later, the drive/pool can be imported using the zpool import
command.
Now that the migration is done, the intermediate filesystem
(including the snapshots) can also be removed:
zfs destroy -r storage/JaarlijkseBackupsOrganized
For reference: the old rsync script
#!/bin/sh
#
# Time-stamp: <2013-02-04 16:48:31 (root)>
# This scripts helps me create my annual backups to an external hard
# disk. The script uses rsync's hard link option to make hard links to
# the previous backups for files that haven't changed. It makes the
# backup based on an LVM snapshot it creates of the LV that contains
# the /home partition.
# This script needs to be run as root.
today=`date +%F`
olddate="2013-02-04"
srcdir="/mnt/backupsrc/"
destdir="/mnt/backupdest/JaarlijkseBackups/$today"
prevdir="/mnt/backupdest/JaarlijkseBackups/$olddate"
# LVM options
VG=raid5vg
LV=home
# rstnc options
options="-ahPAXHS --numeric-ids"
exclusions="--exclude 'lost+found/'"
# --exclude '*/.thumbnails'"
# exclusions="$exclusions --exclude '*/.gvfs/'"
# exclusions="$exclusions --exclude '*/.cache/' --exclude '**/Cache'"
# exclusions="$exclusions --exclude '*/.recycle/'"
# Check to see if the previous backup directory exists
if [ ! -d $prevdir ]; then
echo "Error: The directory with the previous back up ($prevdir) doesn't exist" 1>&2
exit 1
fi
# Make a snapshot of the home LV that we can backup
lvcreate -L15G -s -n snap$LV /dev/$VG/$LV
mount /dev/$VG/snap$LV $srcdir
# Start the backup, first a dry-run, then the full one
rsynccommand="rsync $options $exclusions --link-dest=$prevdir $srcdir $destdir"
$rsynccommand -n
# Wait for user input
echo "This was a dry run. Press a key to continue with the real stuff or"
echo "hit Ctrl-c to abort."
read dummy
$rsynccommand |
#!/bin/sh
#
# Time-stamp: <2013-02-04 16:48:31 (root)>
# This scripts helps me create my annual backups to an external hard
# disk. The script uses rsync's hard link option to make hard links to
# the previous backups for files that haven't changed. It makes the
# backup based on an LVM snapshot it creates of the LV that contains
# the /home partition.
# This script needs to be run as root.
today=`date +%F`
olddate="2013-02-04"
srcdir="/mnt/backupsrc/"
destdir="/mnt/backupdest/JaarlijkseBackups/$today"
prevdir="/mnt/backupdest/JaarlijkseBackups/$olddate"
# LVM options
VG=raid5vg
LV=home
# rstnc options
options="-ahPAXHS --numeric-ids"
exclusions="--exclude 'lost+found/'"
# --exclude '*/.thumbnails'"
# exclusions="$exclusions --exclude '*/.gvfs/'"
# exclusions="$exclusions --exclude '*/.cache/' --exclude '**/Cache'"
# exclusions="$exclusions --exclude '*/.recycle/'"
# Check to see if the previous backup directory exists
if [ ! -d $prevdir ]; then
echo "Error: The directory with the previous back up ($prevdir) doesn't exist" 1>&2
exit 1
fi
# Make a snapshot of the home LV that we can backup
lvcreate -L15G -s -n snap$LV /dev/$VG/$LV
mount /dev/$VG/snap$LV $srcdir
# Start the backup, first a dry-run, then the full one
rsynccommand="rsync $options $exclusions --link-dest=$prevdir $srcdir $destdir"
$rsynccommand -n
# Wait for user input
echo "This was a dry run. Press a key to continue with the real stuff or"
echo "hit Ctrl-c to abort."
read dummy
$rsynccommand
Related Images: