This repository hosts my infra-as-code and gitops configurations for my home network. It also contains various bootstrapping scripts for workstations and servers alike.
Workstations running ArchLinux (btw) are provisioned via
scripts/pacstrap.sh
# After booting into ArchLinux Live USB
# https://raw.githubusercontent.com/alxbl/lab/main/scripts/pacstrap.sh
curl -sSfL https://bit.ly/pacstrap | HOST=hostname shEach node is running Fedora Core OS with an additional layer (via
rpm-ostree rebase) which includes ZFS drivers and userland utilities
as well as Typhoon for the Kubernetes bootstrap.
Currently, my homelab cluster is made up of the following nodes:
- HP Proliant D630p G8 (2x 2.5GHz Xeon / 256GB RAM)
- OS Drive: 500GB external SSD (
/) - Data Pool: 8x1.2TB HDDs in a ZFS Raidz2 configuation (
/var/data)
- OS Drive: 500GB external SSD (
The high level diagram can be found here
The LAN must route the service CIDR (10.3.0.0/16) to the cluster nodes.
Without that route on the router, none of these services are reachable from outside the cluster.
10.3.0.2 is shared by the Gateway on 80/443 and media server (smbd) on 445)
LAN clients can also reach ClusterIPs directly (e.g. AdGuard on 10.3.0.53).
NOTE: The dev cluster uses
10.33.0.0/16and announces its LoadBalancer IPs over L2.
Workload volumes are static local-zfs PersistentVolumes (Retain, no provisioner) declared in
infra/system/storage. Each one is pre-bound to its claim and backed by a ZFS dataset data/k8s/<name> of the
data pool, mounted under /var/data/k8s/<name> on the node. To add a volume, create the dataset on the node,
then add a PV next to the existing ones.
- Never
zpool upgrade(it removes the rollback path) and neverzfs destroyor delete a PV/PVC that is bound to a dataset. - Datasets under
data/k8scarry an SELinuxcontextoption; see.rulesfor the exceptions. - Daily backups use Velero with the
node-agent(kopia) into Azure Blob Storage. ItsBackupStorageLocation,backup-policy-zfsand thebackup-pv-dailyschedule (infra/system/velero) are applied by hand, so a rebuild must apply them.
To iterate on the bootstrapping process without hardware, see DEVELOPMENT.md.
For a visual overview of the bootstrapping process, see this diagram
Once the cluster is bootstrapped, it is self-sufficient as long as there is at least one controller node up and running. Bootstrapping is only necessary in case a full recovery is necessary after simultaneous failure of all nodes.
Bootstrapping Synthetic Test Status: Not implemented
The following requirements must be met for bootstrapping to work:
The following tools must be available on the OS:
- zsh
- docker
- docker-compose
- git
- jq
- ip
- sudo
- terraform
- openssl
Additionally, the user running the bootstrap script must be a sudoer (without password for maximum automation)
Lastly, proton-drive must be installed and logged in: the cluster secrets and the PKI live in Proton Drive.
Secrets are stored end-to-end encrypted in Proton Drive.
The path on the drive is configured with (LAB_SECRETS_REMOTE) and defaults to /my-files/Documents/Homelab/_secrets.
segv/
manifests/<namespace>/<secret-name>.yaml Kubernetes Secrets (names listed in scripts/secrets.list)
pki/ easy-rsa PKI: ca.crt, issued/, private/ (CA keys are encrypted)
pki-tooling/ setup.sh and vars used to build that PKI
pre-cutover/ etcd snapshot, /etc tarball, terraform state (taken before reprovisioning) (FIXME: Remove after cutover)
scripts/export-secrets <kubeconfig>copies the listed Secrets from the cluster tomanifests/.scripts/import-secrets [--dry-run] <kubeconfig> [namespace...]applies them to the cluster.scripts/import-certscopies public certificates into the repository'spki/.
scripts/bootstrap.sh is no longer used: it stops DNS services and edits iptables. Run the steps by hand
(this reinstalls the node, wiping its root disk; the ZFS pool on the other disks is untouched):
- Ensure requirements are met,
proton-driveis logged in, andmatchbox.lanresolves (UDM DNS) to this host. scripts/bootstrap-pkiifpki/has no certificates, andscripts/get-fedora-coreos stable <os_version>for theos_versionininfra/clusters/segv/segv.tfif the assets are missing.- Export the pool first if it is imported:
systemctl stop kubeleton the node, thenzpool export data. - Start the netboot stack:
docker compose -f build/bootstrap/docker-compose.yml up -d. Open the PXE ports in the firewall if needed (TCP 8080; UDP 67, 69, 4011) and close them again afterwards. Keep any Raspberry Pi powered off. terraform init && terraform applyininfra/clusters/segv. Power on (or one-time PXE boot) the node. UseTF_VAR_console=ttyS1for a serial console andTF_VAR_zfs_image=...to change the pinned ZFS image.- When the node is up:
docker compose -f build/bootstrap/docker-compose.yml down, import the pool if needed (zpool import data), thenscripts/install-cilium.sh infra/clusters/segv/kube.conf segv. - Install ArgoCD (
kubectl apply --server-side --force-conflicts -k infra/system/argocd, twice), thenscripts/import-secrets <kubeconfig>and sync the catalogs.
terraform will report that it is waiting for provisioning as such:
segv module.segv.null_resource.copy-controller-secrets[0]:
Provisioning with 'file'...
segv module.segv.null_resource.copy-controller-secrets[0]: Still creating... [10s elapsed]
Bootstrapping must provision at least one controller node. The cluster is bootstrapped to be able to provision new nodes as necessary, so nodes can be dynamically added without running the bootstrap script from this repository again other than in disaster recovery scenarios where the entire cluster is lost at once.
Cluster data is backed up to Azure Blob Storage daily using rustic
as well as to an external hard drive via rsync.
The external hard drive should be the first source to attempt recovery from, as it is the simplest, cheapest, and fastest.
If the hard drive is also lost as part of disaster, then recovery from Azure Blob Storage can be achieved by following these steps.