Replicating an entire FreeBSD system using ZFS

I had a FreeBSD setup I wanted to replicate to another, identical computer. The source system runs ZFS and so should the receiving system. A recursive snapshot in combination with the zfs send and zfs receive commands proved most fruitful.

I began by booting the latest snapshot for stable/10 amd64 on the receiving system. There, I chose Shell from the menu.

First, I wiped the drive clean:

dd if=/dev/zero of=/dev/ada0 bs=128M

I now needed to replicate the GPT partitioning. On the source system, the command

gpart backup ada0

showed what needed to be typed to a file on the receiving system. I stored the following in the file /tmp/ada0.gpart.txt on the receiving system:

GPT 128
1 freebsd-boot 40 1024 gptboot0
2 freebsd-swap 1064 8388608 swap0
3 freebsd-zfs 8389672 303905488 zroot0

Yes, this system is 4Kn ready, even if the current harddrives are 512n.

Next, I issued the command:

gpart restore -l < /tmp/ada0.gpart.txt

This created the same partition layout as on the source system, and set the GPT labels to the names indicated.

The receiving harddrive is slightly larger than then source harddrive, leaving about 140 MiB of gap between the end of the ZFS partition and the end of the drive. This gap is 100 MiB exactly on the source system.

Now, it’s time to put the bootblocks in place:

gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 ada0

The receiving ZFS pool needed to be erected:

hostname hostB.FQDN
kldload zfs
sysctl vfs.zfs.auto_min_ashift=12
zpool create -o autoreplace=on -o failmode=continue -o cachefile=/tmp/zpool.cache -O mountpoint=legacy zroot gpt/zroot0

Yes, this is a single-disk ZFS pool.

The receiving system was running an incomplete system, and the network interface needed some handholding:

ifconfig em0 inet6 accept_rtadv -ifdisabled
dhclient em0
rtsol em0
ifconfig em0

The last command will reveal the IP addresses assigned to the network interface. Choose any of these. I chose the public IPv6 address.

On the receiving system, type and hit ENTER:

nc -6 -l 88 | zfs receive -u -v -F zroot

If you don’t use IPv6, then omit the -6 option. Feel free to use any port number, but remember to type the right one on the source system. I do recommend using a privileged port number, i.e. less than 1024.

On the source system, type these commands and hit ENTER after typing the final command:

zfs snapshot -r zroot@transfer
zfs send -R -v zroot@transfer | nc -N -v IP-address-of-the-receiver 88

If all goes well, you should have replicated the source system onto the receiving system. In my case, the entire transfer took roughly 20 minutes at near gigabit ethernet wire speed.

The receiving system needs some tuning before it’s ready for use.

First, you need to set the bootfs property to the preferred BE, if you use BEs at all. In my case:

zpool set bootfs=zroot/ROOT/20150227-r279351 zroot

Next, you should mount the preferred BE or the initial dataset on /mnt, in my case:

mount -t zfs zroot/ROOT/20150227-r279351 /mnt

Edit /mnt/etc/rc.conf as needed. Do the same to other files within /mnt/etc and possibly /mnt/boot.

Copy /tmp/zpool.cache to /mnt/boot/zfs/zpool.cache, overwriting the existing cache file. Unmount /mnt.

Treat other datasets as you see fit.

On both the source and receiving systems, type:

zfs destroy -r zroot@transfer

I rebooted the receiving system, and the new system works pretty well.