Cluster Topology#
Hardware#
The cluster runs on 4x Raspberry Pi 5 single-board computers, each equipped with an NVMe SSD for storage. All nodes run the aarch64 (ARM64) architecture.
| Node | Role | IP Address | Hardware |
|---|---|---|---|
| node0 | k3s server (control plane) | 10.0.0.140 | Raspberry Pi 5, aarch64, NVMe SSD |
| node1 | k3s agent (worker) | 10.0.0.141 | Raspberry Pi 5, aarch64, NVMe SSD |
| node2 | k3s agent (worker) | 10.0.0.142 | Raspberry Pi 5, aarch64, NVMe SSD |
| node3 | k3s agent (worker) | 10.0.0.143 | Raspberry Pi 5, aarch64, NVMe SSD |
Operating System#
All nodes run NixOS (unstable channel), managed in a separate repository (rpi5-nixos). Key OS-level configuration:
- k3s v1.33 — lightweight Kubernetes distribution
- open-iscsi — required by Longhorn for distributed block storage
- Disabled k3s defaults —
servicelbandtraefikare disabled in favor of MetalLB and a custom Traefik deployment
Network Layout#
graph TB
subgraph Internet
CF[Cloudflare DNS<br/>*.cowlab.org]
CFT[Cloudflare Tunnel]
end
subgraph "Home Network (10.0.0.0/24)"
subgraph "k3s Cluster"
VIP["kube-vip VIP<br/>10.0.0.200<br/>(k3s API server)"]
N0["node0 (server)<br/>10.0.0.140"]
N1["node1 (agent)<br/>10.0.0.141"]
N2["node2 (agent)<br/>10.0.0.142"]
N3["node3 (agent)<br/>10.0.0.143"]
subgraph "Kubernetes Services"
MLB["MetalLB<br/>(IP allocation)"]
TFK["Traefik LB<br/>10.0.0.99"]
SVC["Application Services<br/>(sonarr, radarr, jellyfin, etc.)"]
end
end
end
CF -->|"Wildcard A record<br/>*.cowlab.org → 10.0.0.99"| TFK
CFT -->|"Tunnel route<br/>jellyfin.cowlab.org"| TFK
VIP --> N0
N0 --- N1
N0 --- N2
N0 --- N3
MLB --> TFK
TFK -->|"IngressRoute rules"| SVC
Key Network Components#
- kube-vip (10.0.0.200) — Virtual IP for the k3s API server, providing a stable endpoint for
kubectland agent registration - MetalLB — Bare-metal load balancer that assigns external IPs to
LoadBalancer-type services - Traefik (10.0.0.99) — Ingress controller and reverse proxy. All HTTP(S) traffic enters the cluster through this IP
- Cloudflare DNS — Wildcard
*.cowlab.orgresolves to 10.0.0.99. Additional A/CNAME records exist for services outside the cluster (pihole, pikvm, vaultwarden, etc.) - Cloudflare Zero Trust Tunnel — Provides public internet access to select services (currently
jellyfin.cowlab.org) without exposing any ports on the home network
Traffic Flow#
- External request for
<service>.cowlab.orgresolves via Cloudflare DNS to10.0.0.99 - Traefik receives the request on the
websecure(443) entrypoint - Traefik matches the
Host()rule in the service'sIngressRoute - Traefik forwards the request to the Kubernetes Service
- The Service routes to the appropriate pod(s)
For public-facing services via Cloudflare Tunnel:
- Request hits Cloudflare's edge network
- Cloudflare Tunnel routes it to
traefik.traefik.svc.cluster.local:80inside the cluster - Traefik processes the request as normal