planck — 20-node Raspberry Pi 4 home lab

20× Raspberry Pi 4 (8GB, ARM64), each booting a 240GB NVMe-in-USB stick. Fresh Raspberry Pi OS Lite (Debian 13) everywhere, running a k3s Kubernetes cluster: servers planck002/003/004 (embedded etcd), workers 001 + 005020.

Home page: https://carr.pub (served by this cluster).

The map

Path What it is
docs/PLAN.md Full history: what was built, every bug and its fix, postmortems. READ FIRST
docs/IPS.md Node ↔ IP ↔ MAC table (static IPs, pinned on nodes)
docs/INVENTORY.md Hardware inventory per node
scripts/flash-one.sh Reimage a node's NVMe stick (run on laptop via pkexec)
scripts/k3s-join.sh Join servers/agents to the cluster (needs K3S_TOKEN)
scripts/loadtest.sh Spawn a k6 load swarm across N nodes
scripts/inventory.sh Regenerate hardware inventory
manifests/monitoring/ Prometheus, node-exporter, Grafana, DDNS CronJob
manifests/loadtest/ k6 rig + practice target
manifests/web/ The carr.pub site + Ingress
manifests/cluster-issuer.yaml Let's Encrypt issuer
configs/ Node password (gitignored), installer internals, retired netboot servers

Daily operations

  • kubectl on the laptop: ~/.local/bin/kubectl (kubeconfig ~/.kube/config)
  • SSH any node: ssh adamcarr@planck0NN.local (passwordless sudo)
  • Grafana: kubectl -n monitoring port-forward svc/grafana 3000:3000
  • Load test: scripts/loadtest.sh <url> [pods] [vus-per-pod] [duration]
  • New public site: Ingress in web namespace on <name>.carr.pub with the cert-manager.io/cluster-issuer: letsencrypt-prod annotation + DNSimple record

Agent documentation lives in ~/.agents/skills/planck-cluster/SKILL.md (loaded automatically by opencode sessions).

Safety rules

  • Never reboot more than ONE of planck002/003/004 at once (etcd quorum)
  • Images must be linux/arm64 multi-arch
  • The cluster room switch shares a PSU with nodes 011015
Description
20-node Raspberry Pi k3s home lab
Readme 83 KiB
Languages
Shell 75%
Python 25%