Skip to Content

Create (or Expand) a ZFS Mirror in FreeBSD

Here’s a quick guide to convert a system created by the FreeBSD installer from a single-drive stripe into a 2-drive mirror. The same procedure also works to expand an existing mirror by adding additional drives.

The single-drive system was originally partitioned using Auto (ZFS) in the installer and configuring these options:

Configure Options:
Pool Type/Disks: stripe: 1 disk
Encrypt Disks? YES
Encrypt Swap? YES
FreeBSD Installer > Partitioning
FreeBSD Installer > ZFS Configuration

To add a new mirror drive to the system, first back up the data.

Second, use zpool status to examine the current ZFS pool. In this example, the zroot pool consists of the third partition (p3) of drive ada0 and it has been encrypted with GELI (.eli).

$ su
Password:

# zpool status
  pool: zroot
 state: ONLINE
  scan: none requested
config:

        NAME          STATE     READ WRITE CKSUM
        zroot         ONLINE       0     0     0
          ada0p3.eli  ONLINE       0     0     0

errors: No known data errors

Next, use swapinfo to examine the swap partition and notice that the swap uses the second partition (p2) of the same drive (ada0) and that it is also GELI-encrypted (.eli).

# swapinfo
Device          1K-blocks     Used    Avail Capacity
/dev/ada0p2.eli   2097152        0  2097152     0%

Also, use geom or camcontrol to get the drive’s serial number for reference.

# geom disk list ada0 | grep ident
   ident: WD-WMATV0942547

# camcontrol identify ada0 | grep serial
serial number         WD-WMATV0942547

Finally, run gpart show to display the complete partition layout for drive ada0.

# gpart show ada0
=>        40  1953525088  ada0  GPT  (932G)
          40        1024     1  freebsd-boot  (512K)
        1064         984        - free -  (492K)
        2048     4194304     2  freebsd-swap  (2.0G)
     4196352  1949327360     3  freebsd-zfs  (930G)
  1953523712        1416        - free -  (708K)

At this point, copy the serial number of the new drive to be added to the system from the drive label. Insert the new drive into an empty bay and run dmesg to find the device node assigned to it. In this case, the new drive has been assigned ada1.

# dmesg
[...]
ada1 at ahcich1 bus 0 scbus1 target 0 lun 0
ada1: <Hitachi HDS721010CLA332 JP4OA25C> ATA8-ACS SATA 2.x device
ada1: Serial Number JP2921HQ0A6ZJA
ada1: 300.000MB/s transfers (SATA 2.x, UDMA6, PIO 8192bytes)
ada1: Command Queueing enabled
ada1: 953869MB (1953525168 512 byte sectors)

Delete any existing partitions on the new drive using gpart destroy. The command will give an error if the drive doesn’t contain any partitions.

# gpart destroy -F ada1
ada1 destroyed

# gpart destroy -F ada1
gpart: arg0 'ada1': Invalid argument

The easiest way to copy the partition layout from drive ada0 to drive ada1 is to use the gpart backup and gpart restore commands.

Run gpart backup to examine the partition layout on drive ada0.

# gpart backup ada0
GPT 128
1   freebsd-boot         40       1024 gptboot0
2   freebsd-swap       2048    4194304 swap0
3    freebsd-zfs    4196352 1949327360 zfs0

The last column in the output lists the automatically generated GPT labels for each of the three partitions on drive ada0, which is why the installer appended a 0 to the end of each GPT label.

Since the new drive has been assigned the device node ada1, run the command again and pipe the output into sed to replace the last 0 on each line with a 1 to generate new GPT label names for the new drive.

# gpart backup ada0 | sed -r 's/(^[[:digit:]].*)0/\11/'
GPT 128
1   freebsd-boot         40       1024 gptboot1
2   freebsd-swap       2048    4194304 swap1
3    freebsd-zfs    4196352 1949327360 zfs1

If the output from sed looks correct, run the last command again and pipe those results into gpart restore to copy the partition layout to ada1.

# gpart backup ada0 | sed -r 's/(^[[:digit:]].*)0/\11/' | gpart restore -lF ada1

Use gpart show to see that both drives now have the same layout.

# gpart show -l
=>        40  1953525088  ada0  GPT  (932G)
          40        1024     1  gptboot0  (512K)
        1064         984        - free -  (492K)
        2048     4194304     2  swap0  (2.0G)
     4196352  1949327360     3  zfs0  (930G)
  1953523712        1416        - free -  (708K)

=>        40  1953525088  ada1  GPT  (932G)
          40        1024     1  gptboot1  (512K)
        1064         984        - free -  (492K)
        2048     4194304     2  swap1  (2.0G)
     4196352  1949327360     3  zfs1  (930G)
  1953523712        1416        - free -  (708K)

Since the root partition was GELI-encrypted during the installation, check the installer log to find out what options were used for the geli init and geli attach commands.

# grep geli /var/log/bsdinstall_log
[...]
DEBUG: zfs_create_boot: geli init -bg -e AES-XTS -J - -l 256 -s 4096 "ada0p3"
[...]
DEBUG: zfs_create_boot: geli attach -j - "ada0p3"
[...]

Run the geli init command for the new drive using slightly different options. Remove the -J - option to make the command interactive and change ada0p3 to ada1p3 to match the new drive.

When requested, enter the same GELI password currently used to boot the system.

# geli init -bg -e AES-XTS -l 256 -s 4096 "ada1p3"

Enter new passphrase:
Reenter new passphrase:

Metadata backup for provider ada1p3 can be found in /var/backups/ada1p3.eli
and can be restored with the following command:

        # geli restore /var/backups/ada1p3.eli ada1p3

Mount the new GELI partition using the geli attach command, enter the password when requested and view the active containers with geli status.

# geli attach "ada1p3"
Enter passphrase:

# geli status
      Name  Status  Components
ada0p3.eli  ACTIVE  ada0p3
ada0p2.eli  ACTIVE  ada0p2
ada1p3.eli  ACTIVE  ada1p3

Use zpool attach to attach the new root partition to the existing stripe and convert it into a mirror. Write the boot code to the boot partition on the new drive as recommended by the output text.

# zpool status
  pool: zroot
 state: ONLINE
  scan: none requested
config:

        NAME          STATE     READ WRITE CKSUM
        zroot         ONLINE       0     0     0
          ada0p3.eli  ONLINE       0     0     0

errors: No known data errors

# zpool attach zroot ada0p3.eli ada1p3.eli
Make sure to wait until resilver is done before rebooting.

If you boot from pool 'zroot', you may need to update
boot code on newly attached disk 'ada1p3.eli'.

Assuming you use GPT partitioning and 'da0' is your new boot disk
you may use the following command:

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

# gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 ada1
partcode written to ada1p1
bootcode written to ada1

At this point, /etc/fstab contains an entry for the swap partition on drive ada0, which was added by the installer, but no entry for the swap partition on the newly added drive. Edit the file or use sed to add an entry for the new drive.

# cat /etc/fstab
# Device                Mountpoint      FStype  Options         Dump    Pass#
/dev/ada0p2.eli         none    swap    sw              0       0

# sed -n 's/ada0p2/ada1p2/p' /etc/fstab
/dev/ada1p2.eli         none    swap    sw              0       0

# sed -n 's/ada0p2/ada1p2/p' /etc/fstab >> /etc/fstab

# cat /etc/fstab
# Device                Mountpoint      FStype  Options         Dump    Pass#
/dev/ada0p2.eli         none    swap    sw              0       0
/dev/ada1p2.eli         none    swap    sw              0       0

Wait for the drive to finish resilvering and check the status of the mirror.

# zpool status
  pool: zroot
 state: ONLINE
  scan: resilvered 823M in 0 days 00:00:19 with 0 errors on Mon Mar 23 21:56:30 2020
config:

        NAME            STATE     READ WRITE CKSUM
        zroot           ONLINE       0     0     0
          mirror-0      ONLINE       0     0     0
            ada0p3.eli  ONLINE       0     0     0
            ada1p3.eli  ONLINE       0     0     0

errors: No known data errors

That’s it. The same procedure can be repeated to add more drives as needed, but drives are reliable enough that it’s hard to justify mirroring more than three.

# dmesg
[...]

# zpool attach zroot ada0p3.eli ada2p3.eli
[...]

# zpool status
  pool: zroot
 state: ONLINE
  scan: resilvered 823M in 0 days 00:00:16 with 0 errors on Mon Mar 23 22:27:10 2020
config:

        NAME            STATE     READ WRITE CKSUM
        zroot           ONLINE       0     0     0
          mirror-0      ONLINE       0     0     0
            ada0p3.eli  ONLINE       0     0     0
            ada1p3.eli  ONLINE       0     0     0
            ada2p3.eli  ONLINE       0     0     0

errors: No known data errors