diff options
| author | Paul Buetow <paul@buetow.org> | 2025-07-13 16:46:39 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-07-13 16:46:39 +0300 |
| commit | e617e1e84f5eda5e011f9c08967c282d68661f96 (patch) | |
| tree | 8ccc9b6050df599a0da01d438837be8677aee96e /gemfeed | |
| parent | 0756fde9db00db4bed79cd3ee87f9a05bd650301 (diff) | |
add part 6
Diffstat (limited to 'gemfeed')
13 files changed, 3452 insertions, 187 deletions
diff --git a/gemfeed/2016-04-09-jails-and-zfs-on-freebsd-with-puppet.gmi b/gemfeed/2016-04-09-jails-and-zfs-on-freebsd-with-puppet.gmi index 67e7d8df..0d67f9ba 100644 --- a/gemfeed/2016-04-09-jails-and-zfs-on-freebsd-with-puppet.gmi +++ b/gemfeed/2016-04-09-jails-and-zfs-on-freebsd-with-puppet.gmi @@ -397,6 +397,7 @@ E-Mail your comments to `paul@nospam.buetow.org` :-) Other *BSD related posts are: +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi 2025-07-14 f3s: Kubernetes with FreeBSD - Part 6: Storage => ./2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi 2025-05-11 f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network => ./2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi 2025-04-05 f3s: Kubernetes with FreeBSD - Part 4: Rocky Linux Bhyve VMs => ./2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi 2025-02-01 f3s: Kubernetes with FreeBSD - Part 3: Protecting from power cuts diff --git a/gemfeed/2022-07-30-lets-encrypt-with-openbsd-and-rex.gmi b/gemfeed/2022-07-30-lets-encrypt-with-openbsd-and-rex.gmi index 20a64ee1..b03610d3 100644 --- a/gemfeed/2022-07-30-lets-encrypt-with-openbsd-and-rex.gmi +++ b/gemfeed/2022-07-30-lets-encrypt-with-openbsd-and-rex.gmi @@ -676,6 +676,7 @@ E-Mail your comments to `paul@nospam.buetow.org` :-) Other *BSD related posts are: +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi 2025-07-14 f3s: Kubernetes with FreeBSD - Part 6: Storage => ./2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi 2025-05-11 f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network => ./2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi 2025-04-05 f3s: Kubernetes with FreeBSD - Part 4: Rocky Linux Bhyve VMs => ./2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi 2025-02-01 f3s: Kubernetes with FreeBSD - Part 3: Protecting from power cuts diff --git a/gemfeed/2024-01-13-one-reason-why-i-love-openbsd.gmi b/gemfeed/2024-01-13-one-reason-why-i-love-openbsd.gmi index 41c0d806..01cfe726 100644 --- a/gemfeed/2024-01-13-one-reason-why-i-love-openbsd.gmi +++ b/gemfeed/2024-01-13-one-reason-why-i-love-openbsd.gmi @@ -51,6 +51,7 @@ E-Mail your comments to `paul@nospam.buetow.org` :-) Other *BSD related posts are: +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi 2025-07-14 f3s: Kubernetes with FreeBSD - Part 6: Storage => ./2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi 2025-05-11 f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network => ./2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi 2025-04-05 f3s: Kubernetes with FreeBSD - Part 4: Rocky Linux Bhyve VMs => ./2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi 2025-02-01 f3s: Kubernetes with FreeBSD - Part 3: Protecting from power cuts diff --git a/gemfeed/2024-04-01-KISS-high-availability-with-OpenBSD.gmi b/gemfeed/2024-04-01-KISS-high-availability-with-OpenBSD.gmi index f04602be..3d02b234 100644 --- a/gemfeed/2024-04-01-KISS-high-availability-with-OpenBSD.gmi +++ b/gemfeed/2024-04-01-KISS-high-availability-with-OpenBSD.gmi @@ -300,6 +300,7 @@ E-Mail your comments to `paul@nospam.buetow.org` :-) Other *BSD and KISS related posts are: +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi 2025-07-14 f3s: Kubernetes with FreeBSD - Part 6: Storage => ./2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi 2025-05-11 f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network => ./2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi 2025-04-05 f3s: Kubernetes with FreeBSD - Part 4: Rocky Linux Bhyve VMs => ./2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi 2025-02-01 f3s: Kubernetes with FreeBSD - Part 3: Protecting from power cuts diff --git a/gemfeed/2024-11-17-f3s-kubernetes-with-freebsd-part-1.gmi b/gemfeed/2024-11-17-f3s-kubernetes-with-freebsd-part-1.gmi index 1716dba0..0d01ac9d 100644 --- a/gemfeed/2024-11-17-f3s-kubernetes-with-freebsd-part-1.gmi +++ b/gemfeed/2024-11-17-f3s-kubernetes-with-freebsd-part-1.gmi @@ -13,6 +13,7 @@ These are all the posts so far: => ./2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi 2025-02-01 f3s: Kubernetes with FreeBSD - Part 3: Protecting from power cuts => ./2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi 2025-04-05 f3s: Kubernetes with FreeBSD - Part 4: Rocky Linux Bhyve VMs => ./2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi 2025-05-11 f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi 2025-07-14 f3s: Kubernetes with FreeBSD - Part 6: Storage => ./f3s-kubernetes-with-freebsd-part-1/f3slogo.png f3s logo @@ -161,6 +162,7 @@ Read the next post of this series: Other *BSD-related posts: +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi 2025-07-14 f3s: Kubernetes with FreeBSD - Part 6: Storage => ./2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi 2025-05-11 f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network => ./2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi 2025-04-05 f3s: Kubernetes with FreeBSD - Part 4: Rocky Linux Bhyve VMs => ./2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi 2025-02-01 f3s: Kubernetes with FreeBSD - Part 3: Protecting from power cuts diff --git a/gemfeed/2024-12-03-f3s-kubernetes-with-freebsd-part-2.gmi b/gemfeed/2024-12-03-f3s-kubernetes-with-freebsd-part-2.gmi index 8f362270..a2ceafb0 100644 --- a/gemfeed/2024-12-03-f3s-kubernetes-with-freebsd-part-2.gmi +++ b/gemfeed/2024-12-03-f3s-kubernetes-with-freebsd-part-2.gmi @@ -13,6 +13,7 @@ These are all the posts so far: => ./2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi 2025-02-01 f3s: Kubernetes with FreeBSD - Part 3: Protecting from power cuts => ./2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi 2025-04-05 f3s: Kubernetes with FreeBSD - Part 4: Rocky Linux Bhyve VMs => ./2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi 2025-05-11 f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi 2025-07-14 f3s: Kubernetes with FreeBSD - Part 6: Storage => ./f3s-kubernetes-with-freebsd-part-1/f3slogo.png f3s logo @@ -300,6 +301,7 @@ Read the next post of this series: Other *BSD-related posts: +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi 2025-07-14 f3s: Kubernetes with FreeBSD - Part 6: Storage => ./2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi 2025-05-11 f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network => ./2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi 2025-04-05 f3s: Kubernetes with FreeBSD - Part 4: Rocky Linux Bhyve VMs => ./2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi 2025-02-01 f3s: Kubernetes with FreeBSD - Part 3: Protecting from power cuts diff --git a/gemfeed/2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi b/gemfeed/2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi index de156ab2..65c1637b 100644 --- a/gemfeed/2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi +++ b/gemfeed/2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi @@ -9,6 +9,7 @@ This is the third blog post about my f3s series for my self-hosting demands in m => ./2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi 2025-02-01 f3s: Kubernetes with FreeBSD - Part 3: Protecting from power cuts (You are currently reading this) => ./2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi 2025-04-05 f3s: Kubernetes with FreeBSD - Part 4: Rocky Linux Bhyve VMs => ./2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi 2025-05-11 f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi 2025-07-14 f3s: Kubernetes with FreeBSD - Part 6: Storage => ./f3s-kubernetes-with-freebsd-part-1/f3slogo.png f3s logo @@ -363,6 +364,7 @@ Read the next post of this series: Other BSD related posts are: +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi 2025-07-14 f3s: Kubernetes with FreeBSD - Part 6: Storage => ./2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi 2025-05-11 f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network => ./2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi 2025-04-05 f3s: Kubernetes with FreeBSD - Part 4: Rocky Linux Bhyve VMs => ./2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi 2025-02-01 f3s: Kubernetes with FreeBSD - Part 3: Protecting from power cuts (You are currently reading this) diff --git a/gemfeed/2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi b/gemfeed/2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi index 2a5416f9..e5c3a5ae 100644 --- a/gemfeed/2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi +++ b/gemfeed/2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi @@ -9,6 +9,7 @@ This is the fourth blog post about the f3s series for self-hosting demands in a => ./2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi 2025-02-01 f3s: Kubernetes with FreeBSD - Part 3: Protecting from power cuts => ./2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi 2025-04-05 f3s: Kubernetes with FreeBSD - Part 4: Rocky Linux Bhyve VMs (You are currently reading this) => ./2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi 2025-05-11 f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi 2025-07-14 f3s: Kubernetes with FreeBSD - Part 6: Storage => ./f3s-kubernetes-with-freebsd-part-1/f3slogo.png f3s logo @@ -509,6 +510,7 @@ Read the next post of this series: Other *BSD-related posts: +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi 2025-07-14 f3s: Kubernetes with FreeBSD - Part 6: Storage => ./2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi 2025-05-11 f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network => ./2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi 2025-04-05 f3s: Kubernetes with FreeBSD - Part 4: Rocky Linux Bhyve VMs (You are currently reading this) => ./2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi 2025-02-01 f3s: Kubernetes with FreeBSD - Part 3: Protecting from power cuts diff --git a/gemfeed/2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi b/gemfeed/2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi index 77a06c55..f22ddc15 100644 --- a/gemfeed/2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi +++ b/gemfeed/2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi @@ -13,6 +13,7 @@ These are all the posts so far: => ./2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi 2025-02-01 f3s: Kubernetes with FreeBSD - Part 3: Protecting from power cuts => ./2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi 2025-04-05 f3s: Kubernetes with FreeBSD - Part 4: Rocky Linux Bhyve VMs => ./2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi 2025-05-11 f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network (You are currently reading this) +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi 2025-07-14 f3s: Kubernetes with FreeBSD - Part 6: Storage => ./f3s-kubernetes-with-freebsd-part-1/f3slogo.png f3s logo @@ -924,10 +925,13 @@ peer: 2htXdNcxzpI2FdPDJy4T4VGtm1wpMEQu1AkQHjNY6F8= Having a mesh network on our hosts is great for securing all the traffic between them for our future k3s setup. A self-managed WireGuard mesh network is better than Tailscale as it eliminates reliance on a third party and provides full control over the configuration. It reduces unnecessary abstraction and "magic," enabling easier debugging and ensuring full ownership of our network. -I look forward to the next blog post in this series. We may start setting up k3s or take a first look at the NFS server (for persistent storage) side of things. I hope you liked all the posts so far in this series. +Read the next post of this series: + +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi f3s: Kubernetes with FreeBSD - Part 6: Storage Other *BSD-related posts: +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi 2025-07-14 f3s: Kubernetes with FreeBSD - Part 6: Storage => ./2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi 2025-05-11 f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network (You are currently reading this) => ./2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi 2025-04-05 f3s: Kubernetes with FreeBSD - Part 4: Rocky Linux Bhyve VMs => ./2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi 2025-02-01 f3s: Kubernetes with FreeBSD - Part 3: Protecting from power cuts diff --git a/gemfeed/2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi b/gemfeed/2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi new file mode 100644 index 00000000..bc1d7c00 --- /dev/null +++ b/gemfeed/2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi @@ -0,0 +1,1610 @@ +# f3s: Kubernetes with FreeBSD - Part 6: Storage + +> Published at 2025-07-13T16:44:29+03:00 + +This is the sixth blog post about the f3s series for self-hosting demands in a home lab. f3s? The "f" stands for FreeBSD, and the "3s" stands for k3s, the Kubernetes distribution used on FreeBSD-based physical machines. + +=> ./2024-11-17-f3s-kubernetes-with-freebsd-part-1.gmi 2024-11-17 f3s: Kubernetes with FreeBSD - Part 1: Setting the stage +=> ./2024-12-03-f3s-kubernetes-with-freebsd-part-2.gmi 2024-12-03 f3s: Kubernetes with FreeBSD - Part 2: Hardware and base installation +=> ./2025-02-01-f3s-kubernetes-with-freebsd-part-3.gmi 2025-02-01 f3s: Kubernetes with FreeBSD - Part 3: Protecting from power cuts +=> ./2025-04-05-f3s-kubernetes-with-freebsd-part-4.gmi 2025-04-05 f3s: Kubernetes with FreeBSD - Part 4: Rocky Linux Bhyve VMs +=> ./2025-05-11-f3s-kubernetes-with-freebsd-part-5.gmi 2025-05-11 f3s: Kubernetes with FreeBSD - Part 5: WireGuard mesh network +=> ./2025-07-14-f3s-kubernetes-with-freebsd-part-6.gmi 2025-07-14 f3s: Kubernetes with FreeBSD - Part 6: Storage (You are currently reading this) + +=> ./f3s-kubernetes-with-freebsd-part-1/f3slogo.png f3s logo + +## Table of Contents + +* ⇢ f3s: Kubernetes with FreeBSD - Part 6: Storage +* ⇢ ⇢ Introduction +* ⇢ ⇢ Additional storage capacity +* ⇢ ⇢ ZFS encryption keys +* ⇢ ⇢ ⇢ UFS on USB keys +* ⇢ ⇢ ⇢ Generating encryption keys +* ⇢ ⇢ ⇢ Configuring `zdata` ZFS pool encryption +* ⇢ ⇢ ⇢ Migrating Bhyve VMs to an encrypted `bhyve` ZFS volume +* ⇢ ⇢ ZFS Replication with `zrepl` +* ⇢ ⇢ ⇢ Understanding Replication Requirements +* ⇢ ⇢ ⇢ Installing `zrepl` +* ⇢ ⇢ ⇢ Configuring `zrepl` on `f1` (sink) +* ⇢ ⇢ ⇢ Enabling and starting `zrepl` services +* ⇢ ⇢ ⇢ Monitoring replication +* ⇢ ⇢ ⇢ Verifying replication after reboot +* ⇢ ⇢ ⇢ Understanding Failover Limitations and Design Decisions +* ⇢ ⇢ ⇢ Mounting the NFS datasets +* ⇢ ⇢ ⇢ Troubleshooting: Files not appearing in replication +* ⇢ ⇢ ⇢ Configuring automatic key loading on boot +* ⇢ ⇢ CARP (Common Address Redundancy Protocol) +* ⇢ ⇢ ⇢ How CARP Works +* ⇢ ⇢ ⇢ Configuring CARP +* ⇢ ⇢ ⇢ CARP State Change Notifications +* ⇢ ⇢ NFS Server Configuration +* ⇢ ⇢ ⇢ Setting up NFS on `f0` (Primary) +* ⇢ ⇢ ⇢ Configuring Stunnel for NFS Encryption with CARP Failover +* ⇢ ⇢ ⇢ Creating a Certificate Authority for Client Authentication +* ⇢ ⇢ ⇢ Install and Configure Stunnel on `f0` +* ⇢ ⇢ ⇢ Setting up NFS on `f1` (Standby) +* ⇢ ⇢ ⇢ CARP Control Script for Clean Failover +* ⇢ ⇢ ⇢ CARP Management Script +* ⇢ ⇢ ⇢ Automatic Failback After Reboot +* ⇢ ⇢ Client Configuration for Stunnel +* ⇢ ⇢ ⇢ Configuring Rocky Linux Clients (`r0`, `r1`, `r2`) +* ⇢ ⇢ ⇢ Testing NFS Mount with Stunnel +* ⇢ ⇢ ⇢ Testing CARP Failover with mounted clients and stale file handles: +* ⇢ ⇢ ⇢ Complete Failover Test +* ⇢ ⇢ Conclusion +* ⇢ ⇢ Future Storage Explorations +* ⇢ ⇢ ⇢ MinIO for S3-Compatible Object Storage +* ⇢ ⇢ ⇢ MooseFS for Distributed High Availability + +## Introduction + +In the previous posts, we set up a FreeBSD-based Kubernetes cluster using k3s. While the base system works well, Kubernetes workloads often require persistent storage for databases, configuration files, and application data. Local storage on each node has significant limitations: + +* No data sharing: Pods (once we run Kubernetes) on different nodes can't access the same data +* Pod mobility: If a pod moves to another node, it loses access to its data +* No redundancy: Hardware failure means data loss + +This post implements a robust storage solution using: + +* CARP: For high availability with automatic IP failover +* NFS over stunnel: For secure, encrypted network storage +* ZFS: For data integrity, encryption, and efficient snapshots +* `zrepl`: For continuous ZFS replication between nodes + +The result is a highly available, encrypted storage system that survives node failures while providing shared storage to all Kubernetes pods. + +Other than what was mentioned in the first post of this blog series, we aren't using HAST, but `zrepl` for data replication. Read more about it later in this blog post. + +## Additional storage capacity + +We add 1 TB of additional storage to each of the nodes (`f0`, `f1`, `f2`) in the form of an SSD drive. The Beelink mini PCs have enough space in the chassis for the extra space. + +=> ./f3s-kubernetes-with-freebsd-part-6/drives.jpg + +Upgrading the storage was as easy as unscrewing, plugging the drive in, and then screwing it back together again. The procedure was uneventful! We're using two different SSD models (Samsung 870 EVO and Crucial BX500) to avoid simultaneous failures from the same manufacturing batch. + +We then create the `zdata` ZFS pool on all three nodes: + +```sh +paul@f0:~ % doas zpool create -m /data zdata /dev/ada1 +paul@f0:~ % zpool list +NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT +zdata 928G 12.1M 928G - - 0% 0% 1.00x ONLINE - +zroot 472G 29.0G 443G - - 0% 6% 1.00x ONLINE - + +paul@f0:/ % doas camcontrol devlist +<512GB SSD D910R170> at scbus0 target 0 lun 0 (pass0,ada0) +<Samsung SSD 870 EVO 1TB SVT03B6Q> at scbus1 target 0 lun 0 (pass1,ada1) +paul@f0:/ % +``` + +To verify that we have a different SSD on the second node (the third node has the same drive as the first): + +```sh +paul@f1:/ % doas camcontrol devlist +<512GB SSD D910R170> at scbus0 target 0 lun 0 (pass0,ada0) +<CT1000BX500SSD1 M6CR072> at scbus1 target 0 lun 0 (pass1,ada1) +``` + +## ZFS encryption keys + +ZFS native encryption requires encryption keys to unlock datasets. We need a secure method to store these keys that balances security with operational needs: + +* Security: Keys must not be stored on the same disks they encrypt +* Availability: Keys must be available at boot for automatic mounting +* Portability: Keys should be easily moved between systems for recovery + +Using USB flash drives as hardware key storage provides a convenient and elegant solution. The encrypted data is unreadable without physical access to the USB key, protecting against disk theft or improper disposal. In production environments, you may use enterprise key management systems; however, for a home lab, USB keys offer good security with minimal complexity. + +### UFS on USB keys + +We'll format the USB drives with UFS (Unix File System) rather than ZFS for simplicity. There is no need to use ZFS. + +Let's see the USB keys: + +=> ./f3s-kubernetes-with-freebsd-part-6/usbkeys1.jpg USB keys + +To verify that the USB key (flash disk) is there: + +``` +paul@f0:/ % doas camcontrol devlist +<512GB SSD D910R170> at scbus0 target 0 lun 0 (pass0,ada0) +<Samsung SSD 870 EVO 1TB SVT03B6Q> at scbus1 target 0 lun 0 (pass1,ada1) +<Generic Flash Disk 8.07> at scbus2 target 0 lun 0 (da0,pass2) +paul@f0:/ % +``` + +Let's create the UFS file system and mount it (done on all three nodes `f0`, `f1` and `f2`): + +```sh +paul@f0:/ % doas newfs /dev/da0 +/dev/da0: 15000.0MB (30720000 sectors) block size 32768, fragment size 4096 + using 24 cylinder groups of 625.22MB, 20007 blks, 80128 inodes. + with soft updates +super-block backups (for fsck_ffs -b #) at: + 192, 1280640, 2561088, 3841536, 5121984, 6402432, 7682880, 8963328, 10243776, +11524224, 12804672, 14085120, 15365568, 16646016, 17926464, 19206912,k 20487360, +... + +paul@f0:/ % echo '/dev/da0 /keys ufs rw 0 2' | doas tee -a /etc/fstab +/dev/da0 /keys ufs rw 0 2 +paul@f0:/ % doas mkdir /keys +paul@f0:/ % doas mount /keys +paul@f0:/ % df | grep keys +/dev/da0 14877596 8 13687384 0% /keys +``` + +=> ./f3s-kubernetes-with-freebsd-part-6/usbkeys2.jpg USB keys stuck in + +### Generating encryption keys + +The following keys will later be used to encrypt the ZFS file systems. They will be stored on all three nodes, serving as a backup in case one of the keys is lost or corrupted. When we later replicate encrypted ZFS volumes from one node to another, the keys must also be available on the destination node. + +``` +paul@f0:/keys % doas openssl rand -out /keys/f0.lan.buetow.org:bhyve.key 32 +paul@f0:/keys % doas openssl rand -out /keys/f1.lan.buetow.org:bhyve.key 32 +paul@f0:/keys % doas openssl rand -out /keys/f2.lan.buetow.org:bhyve.key 32 +paul@f0:/keys % doas openssl rand -out /keys/f0.lan.buetow.org:zdata.key 32 +paul@f0:/keys % doas openssl rand -out /keys/f1.lan.buetow.org:zdata.key 32 +paul@f0:/keys % doas openssl rand -out /keys/f2.lan.buetow.org:zdata.key 32 +paul@f0:/keys % doas chown root * +paul@f0:/keys % doas chmod 400 * + +paul@f0:/keys % ls -l +total 20 +*r-------- 1 root wheel 32 May 25 13:07 f0.lan.buetow.org:bhyve.key +*r-------- 1 root wheel 32 May 25 13:07 f1.lan.buetow.org:bhyve.key +*r-------- 1 root wheel 32 May 25 13:07 f2.lan.buetow.org:bhyve.key +*r-------- 1 root wheel 32 May 25 13:07 f0.lan.buetow.org:zdata.key +*r-------- 1 root wheel 32 May 25 13:07 f1.lan.buetow.org:zdata.key +*r-------- 1 root wheel 32 May 25 13:07 f2.lan.buetow.org:zdata.key +```` + +After creation, these are copied to the other two nodes, `f1` and `f2`, into the `/keys` partition (I won't provide the commands here; create a tarball, copy it over, and extract it on the destination nodes). + +### Configuring `zdata` ZFS pool encryption + +Let's encrypt our `zdata` ZFS pool. We are not encrypting the whole pool, but everything within the `zdata/enc` data set: + +```sh +paul@f0:/keys % doas zfs create -o encryption=on -o keyformat=raw -o \ + keylocation=file:///keys/`hostname`:zdata.key zdata/enc +paul@f0:/ % zfs list | grep zdata +zdata 836K 899G 96K /data +zdata/enc 200K 899G 200K /data/enc + +paul@f0:/keys % zfs get all zdata/enc | grep -E -i '(encryption|key)' +zdata/enc encryption aes-256-gcm - +zdata/enc keylocation file:///keys/f0.lan.buetow.org:zdata.key local +zdata/enc keyformat raw - +zdata/enc encryptionroot zdata/enc - +zdata/enc keystatus available - +```` + +All future data sets within `zdata/enc` will inherit the same encryption key. + +### Migrating Bhyve VMs to an encrypted `bhyve` ZFS volume + +We set up Bhyve VMs in a previous blog post. Their ZFS data sets rely on `zroot`, which is the default ZFS pool on the internal 512GB NVME drive. They aren't encrypted yet, so we encrypt the VM data sets as well now. To do so, we first shut down the VMs on all three nodes: + +```sh +paul@f0:/keys % doas vm stop rocky +Sending ACPI shutdown to rocky + +paul@f0:/keys % doas vm list +NAME DATASTORE LOADER CPU MEMORY VNC AUTO STATE +rocky default uefi 4 14G - Yes [1] Stopped +``` + +After this, we rename the unencrypted data set to `_old`, create a new encrypted data set, and also snapshot it as `@hamburger`. + +```sh +paul@f0:/keys % doas zfs rename zroot/bhyve zroot/bhyve_old +paul@f0:/keys % doas zfs set mountpoint=/mnt zroot/bhyve_old +paul@f0:/keys % doas zfs snapshot zroot/bhyve_old/rocky@hamburger + +paul@f0:/keys % doas zfs create -o encryption=on -o keyformat=raw -o \ + keylocation=file:///keys/`hostname`:bhyve.key zroot/bhyve +paul@f0:/keys % doas zfs set mountpoint=/zroot/bhyve zroot/bhyve +paul@f0:/keys % doas zfs set mountpoint=/zroot/bhyve/rocky zroot/bhyve/rocky +``` + +Once done, we import the snapshot into the encrypted dataset and also copy some other metadata files from `vm-bhyve` back over. + +``` +paul@f0:/keys % doas zfs send zroot/bhyve_old/rocky@hamburger | \ + doas zfs recv zroot/bhyve/rocky +paul@f0:/keys % doas cp -Rp /mnt/.config /zroot/bhyve/ +paul@f0:/keys % doas cp -Rp /mnt/.img /zroot/bhyve/ +paul@f0:/keys % doas cp -Rp /mnt/.templates /zroot/bhyve/ +paul@f0:/keys % doas cp -Rp /mnt/.iso /zroot/bhyve/ +``` + +We also have to make encrypted ZFS data sets mount automatically on boot: + +```sh +paul@f0:/keys % doas sysrc zfskeys_enable=YES +zfskeys_enable: -> YES +paul@f0:/keys % doas vm init +paul@f0:/keys % doas reboot +. +. +. +paul@f0:~ % doas vm list +paul@f0:~ % doas vm list +NAME DATASTORE LOADER CPU MEMORY VNC AUTO STATE +rocky default uefi 4 14G 0.0.0.0:5900 Yes [1] Running (2265) +``` + +As you can see, the VM is running. This means the encrypted `zroot/bhyve` was mounted successfully after the reboot! Now we can destroy the old, unencrypted, and now unused bhyve dataset: + +```sh +paul@f0:~ % doas zfs destroy -R zroot/bhyve_old +``` + +To verify once again that `zroot/bhyve` and `zroot/bhyve/rocky` are now both encrypted, we run: + +```sh +paul@f0:~ % zfs get all zroot/bhyve | grep -E '(encryption|key)' +zroot/bhyve encryption aes-256-gcm - +zroot/bhyve keylocation file:///keys/f0.lan.buetow.org:bhyve.key local +zroot/bhyve keyformat raw - +zroot/bhyve encryptionroot zroot/bhyve - +zroot/bhyve keystatus available - + +paul@f0:~ % zfs get all zroot/bhyve/rocky | grep -E '(encryption|key)' +zroot/bhyve/rocky encryption aes-256-gcm - +zroot/bhyve/rocky keylocation none default +zroot/bhyve/rocky keyformat raw - +zroot/bhyve/rocky encryptionroot zroot/bhyve - +zroot/bhyve/rocky keystatus available - +``` + +## ZFS Replication with `zrepl` + +Data replication is the cornerstone of high availability. While CARP handles IP failover (see later in this post), we need continuous data replication to ensure the backup server has current data when it becomes active. Without replication, failover would result in data loss or require shared storage (like iSCSI), which introduces a single point of failure. + +### Understanding Replication Requirements + +Our storage system has different replication needs: + +* NFS data (`/data/nfs/k3svolumes`): Soon, it will contain active Kubernetes persistent volumes. Needs frequent replication (every minute) to minimise data loss during failover. +* VM data (`/zroot/bhyve/fedora`): Contains VM images that change less frequently. Can tolerate longer replication intervals (every 10 minutes). + +The 1-minute replication window is perfectly acceptable for my personal use cases. This isn't a high-frequency trading system or a real-time database—it's storage for personal projects, development work, and home lab experiments. Losing at most 1 minute of work in a disaster scenario is a reasonable trade-off for the reliability and simplicity of snapshot-based replication. Additionally, in the case of a "1 minute of data loss," I would likely still have the data available on the client side. + +Why use `zrepl` instead of HAST? While HAST (Highly Available Storage) is FreeBSD's native solution for high-availability storage and supports synchronous replication—thus eliminating the mentioned 1-minute window—I've chosen `zrepl` for several important reasons: + +* HAST can cause ZFS corruption: HAST operates at the block level and doesn't understand ZFS's transactional semantics. During failover, in-flight transactions can lead to corrupted zpools. I've experienced this firsthand (I am confident I have configured something wrong) - the automatic failover would trigger while ZFS was still writing, resulting in an unmountable pool. +* ZFS-aware replication: `zrepl` understands ZFS datasets and snapshots. It replicates at the dataset level, ensuring each snapshot is a consistent point-in-time copy. This is fundamentally safer than block-level replication. +* Snapshot history: With `zrepl`, you get multiple recovery points (every minute for NFS data in our setup). If corruption occurs, you can roll back to any previous snapshot. HAST only gives you the current state. +* Easier recovery: When something goes wrong with `zrepl`, you still have intact snapshots on both sides. With HAST, a corrupted primary often means a corrupted secondary as well. + +=> https://wiki.freebsd.org/HighlyAvailableStorage FreeBSD HAST + +### Installing `zrepl` + +First, install `zrepl` on both hosts involved (we will replicate data from `f0` to `f1`): + +```sh +paul@f0:~ % doas pkg install -y zrepl +``` + +Then, we verify the pools and datasets on both hosts: + +```sh +# On f0 +paul@f0:~ % doas zpool list +NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT +zdata 928G 1.03M 928G - - 0% 0% 1.00x ONLINE - +zroot 472G 26.7G 445G - - 0% 5% 1.00x ONLINE - + +paul@f0:~ % doas zfs list -r zdata/enc +NAME USED AVAIL REFER MOUNTPOINT +zdata/enc 200K 899G 200K /data/enc + +# On f1 +paul@f1:~ % doas zpool list +NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT +zdata 928G 956K 928G - - 0% 0% 1.00x ONLINE - +zroot 472G 11.7G 460G - - 0% 2% 1.00x ONLINE - + +paul@f1:~ % doas zfs list -r zdata/enc +NAME USED AVAIL REFER MOUNTPOINT +zdata/enc 200K 899G 200K /data/enc +``` + +Since we have a WireGuard tunnel between `f0` and f1, we'll use TCP transport over the secure tunnel instead of SSH. First, check the WireGuard IP addresses: + +```sh +# Check WireGuard interface IPs +paul@f0:~ % ifconfig wg0 | grep inet + inet 192.168.2.130 netmask 0xffffff00 + +paul@f1:~ % ifconfig wg0 | grep inet + inet 192.168.2.131 netmask 0xffffff00 +``` + +Let's create a dedicated dataset for NFS data that will be replicated: + +```sh +# Create the nfsdata dataset that will hold all data exposed via NFS +paul@f0:~ % doas zfs create zdata/enc/nfsdata +``` + +Afterwards, we create the `zrepl` configuration on `f0`: + +```sh +paul@f0:~ % doas tee /usr/local/etc/zrepl/zrepl.yml <<'EOF' +global: + logging: + - type: stdout + level: info + format: human + +jobs: + - name: f0_to_f1_nfsdata + type: push + connect: + type: tcp + address: "192.168.2.131:8888" + filesystems: + "zdata/enc/nfsdata": true + send: + encrypted: true + snapshotting: + type: periodic + prefix: zrepl_ + interval: 1m + pruning: + keep_sender: + - type: last_n + count: 10 + keep_receiver: + - type: last_n + count: 10 + + - name: f0_to_f1_fedora + type: push + connect: + type: tcp + address: "192.168.2.131:8888" + filesystems: + "zroot/bhyve/fedora": true + send: + encrypted: true + snapshotting: + type: periodic + prefix: zrepl_ + interval: 10m + pruning: + keep_sender: + - type: last_n + count: 10 + keep_receiver: + - type: last_n + count: 10 +EOF +``` + + We're using two separate replication jobs with different intervals: + +* `f0_to_f1_nfsdata`: Replicates NFS data every minute for faster failover recovery +* `f0_to_f1_fedora`: Replicates Fedora VM every ten minutes (less critical) + +The Fedora VM is only used for development purposes, so it doesn't require as frequent replication as the NFS data. It's off-topic to this blog series, but it showcases, hows `zrepl`'s flexibility in handling different datasets with varying replication needs. + +Furthermore: + +* We're specifically replicating `zdata/enc/nfsdata` instead of the entire `zdata/enc` dataset. This dedicated dataset will contain all the data we later want to expose via NFS, keeping a clear separation between replicated NFS data and other local encrypted data. +* The `send: encrypted: false` option turns off ZFS native encryption for the replication stream. Since we're using a WireGuard tunnel between `f0` and `f1`, the data is already encrypted in transit. Disabling ZFS stream encryption reduces CPU overhead and improves replication performance. + +### Configuring `zrepl` on `f1` (sink) + +On `f1` (the sink, meaning it's the node receiving the replication data), we configure `zrepl` to receive the data as follows: + +```sh +# First, create a dedicated sink dataset +paul@f1:~ % doas zfs create zdata/sink + +paul@f1:~ % doas tee /usr/local/etc/zrepl/zrepl.yml <<'EOF' +global: + logging: + - type: stdout + level: info + format: human + +jobs: + - name: sink + type: sink + serve: + type: tcp + listen: "192.168.2.131:8888" + clients: + "192.168.2.130": "f0" + recv: + placeholder: + encryption: inherit + root_fs: "zdata/sink" +EOF +``` + +### Enabling and starting `zrepl` services + +We then enable and start `zrepl` on both hosts via: + +```sh +# On f0 +paul@f0:~ % doas sysrc zrepl_enable=YES +zrepl_enable: -> YES +paul@f0:~ % doas service `zrepl` start +Starting zrepl. + +# On f1 +paul@f1:~ % doas sysrc zrepl_enable=YES +zrepl_enable: -> YES +paul@f1:~ % doas service `zrepl` start +Starting zrepl. +``` + +To check the replication status, we run: + +```sh +# On f0, check `zrepl` status (use raw mode for non-tty) +paul@f0:~ % doas pkg install jq +paul@f0:~ % doas zrepl status --mode raw | grep -A2 "Replication" | jq . +"Replication":{"StartAt":"2025-07-01T22:31:48.712143123+03:00"... + +# Check if services are running +paul@f0:~ % doas service zrepl status +zrepl is running as pid 2649. + +paul@f1:~ % doas service zrepl status +zrepl is running as pid 2574. + +# Check for `zrepl` snapshots on source +paul@f0:~ % doas zfs list -t snapshot -r zdata/enc | grep zrepl +zdata/enc@zrepl_20250701_193148_000 0B - 176K - + +# On f1, verify the replicated datasets +paul@f1:~ % doas zfs list -r zdata | grep f0 +zdata/f0 576K 899G 200K none +zdata/f0/zdata 376K 899G 200K none +zdata/f0/zdata/enc 176K 899G 176K none + +# Check replicated snapshots on f1 +paul@f1:~ % doas zfs list -t snapshot -r zdata | grep zrepl +zdata/f0/zdata/enc@zrepl_20250701_193148_000 0B - 176K - +zdata/f0/zdata/enc@zrepl_20250701_194148_000 0B - 176K - +. +. +. +``` + +### Monitoring replication + +You can monitor the replication progress with: + +```sh +paul@f0:~ % doas zrepl status +``` + +=> ./f3s-kubernetes-with-freebsd-part-6/zrepl.png zrepl status + +With this setup, both `zdata/enc/nfsdata` and `zroot/bhyve/fedora` on `f0` will be automatically replicated to `f1` every 1 minute (or 10 minutes in the case of the Fedora VM), with encrypted snapshots preserved on both sides. The pruning policy ensures that we keep the last 10 snapshots while managing disk space efficiently. + +The replicated data appears on `f1` under `zdata/sink/` with the source host and dataset hierarchy preserved: + +* `zdata/enc/nfsdata` → `zdata/sink/f0/zdata/enc/nfsdata` +* `zroot/bhyve/fedora` |
