Drie mini-PCs staan op een plank bij mij thuis. Samen draaien ze mijn GitLab, mijn monitoring stack, home automation, file sync, password manager, en een stapel andere dingen die ik weiger toe te vertrouwen aan andermans computer. De hele setup kostte minder dan één maand van de vergelijkbare managed Kubernetes rekening, en ik begrijp elke laag ervan omdat ik het zelf heb gebouwd.

Jij kunt hetzelfde hebben. Geen cloud account, geen rack, geen budget met vijf nullen. Je hebt drie tweedehands machines nodig en een middag waarop niemand je lastigvalt. Dit is het cluster waar ik op terugkom als ik het heb over sovereign infrastructuur, en hier is precies hoe het in elkaar zit.

Begin met het kleinste dat werkt

Voordat we het over hardware hebben: dit is het deel dat mensen verrast. Een werkend Kubernetes cluster is één commando.

curl -sfL https://get.k3s.io | sh -

Dat geeft je een single-node K3s installatie. Echte Kubernetes API, echte kubectl, echte workloads. Als je alleen maar wat met manifests wilt prutsen op een oude laptop, kun je hier stoppen met lezen en heb je al iets bruikbaars.

K3s is de reden dat dit zo pijnloos is. Het is de volledige Kubernetes API gepropt in één binary van ~70MB, met de onderdelen die de meeste homelabs nooit gebruiken eruit gehaald en verstandige defaults aangezet voor de rest. Het draait op een Raspberry Pi, en het draait prachtig op een mini-PC. Mensen noemen het soms “Kubernetes lite” en dat doet het tekort. De workloads die je deployt zijn identiek aan wat je op een groot managed cluster zou draaien. Je slaat alleen de enterprise overhead over die je toch nooit ging aanraken.

De rest van deze post gaat over hoe je van dat ene commando iets maakt dat je zou vertrouwen met data waar je om geeft: drie nodes, embedded etcd, een virtueel IP dat een stervende node overleeft, en de storage en ingress om er daadwerkelijk dingen op te hosten.

De hardware: waarom refurbished mini-PCs

Je kunt dit op van alles bouwen, maar ik blijf hetzelfde aanraden omdat het steeds opnieuw klopt. Refurbished business mini-PCs raken een sweet spot die niets anders evenaart voor het geld.

WatWaarom
HP EliteDesk 800 G3Betrouwbaar, goedkoop, overal beschikbaar
Lenovo ThinkCentre M720qGoede thermals, stil
Dell OptiPlex 3060 MicroGoede uitbreidingsopties

Mik ongeveer op dit per node:

  • CPU: Intel i5 6e-8e gen (4-6 cores)
  • RAM: 16-32GB (DDR4, upgradeable)
  • Storage: 256GB+ NVMe/SSD
  • Netwerk: Gigabit Ethernet

Reken op ongeveer €100-200 per node, dus €300-600 voor drie.

Mensen vragen altijd naar een Raspberry Pi cluster, en Pis werken op zich. Ik heb er een tijd één gedraaid. Maar je vecht tegen het platform: ARM images die nog niet bestaan voor het ding dat je wilt, SD kaarten die zichzelf corrupten als een node stroom verliest, 8GB RAM als harde grens op een Pi 4, en USB-attached storage die afhaakt op het slechtst mogelijke moment. Een refurbished mini-PC geeft je x86, een NVMe slot, fatsoenlijk RAM, en een machine die gebouwd is om jarenlang business workloads te draaien zonder te klagen. Het verschil in betrouwbaarheid is niet subtiel.

Mijn eigen cluster is drie keer dezelfde doos, wat het leven makkelijker maakt:

Node 1: HP EliteDesk 800 G3 Mini
        i5-7500T, 32GB RAM, 512GB NVMe
        Rol: Control plane + Worker

Node 2: HP EliteDesk 800 G3 Mini
        i5-7500T, 32GB RAM, 512GB NVMe
        Rol: Control plane + Worker

Node 3: HP EliteDesk 800 G3 Mini
        i5-7500T, 16GB RAM, 256GB NVMe
        Rol: Control plane + Worker

Dat is zo’n €450 voor 12 cores, 80GB RAM, en 1.2TB storage die op mijn plank staat en aan niemand verantwoording aflegt.

De nodes klaarzetten

Twee dingen regelen voor K3s: adressering en het OS.

Voorspelbare adressen

Elke node heeft een IP nodig dat niet beweegt. Zet of statische IPs, of makkelijker, geef DHCP reserveringen uit vanaf je router.

192.168.1.10  k3s-node-1
192.168.1.11  k3s-node-2
192.168.1.12  k3s-node-3
192.168.1.100 k3s-api      # VIP voor API server

Die .100 is een virtueel IP dat we later opzetten zodat de API één stabiel adres heeft, ook als een node sterft. Geef de nodes ook DNS namen, in je lokale resolver of /etc/hosts als je haast hebt:

192.168.1.10  k3s-node-1.home.lan
192.168.1.11  k3s-node-2.home.lan
192.168.1.12  k3s-node-3.home.lan
192.168.1.100 k3s.home.lan

Het OS

Om te beginnen is Ubuntu Server 22.04 LTS minimal de weg van de minste weerstand. Installeer het, update, en zet swap uit (Kubernetes wil swap uit):

# Na installatie, update
sudo apt update && sudo apt upgrade -y

# Installeer handige tools
sudo apt install -y curl wget vim htop

# Disable swap (vereist voor Kubernetes)
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab

Wil je de immutable, dichtgetimmerde versie hiervan, dan stuur ik je daarna naar Talos Linux. Het is een betere fundering en een slechter startpunt, dus dat bewaar ik voor ronde twee. Met Ubuntu heb je vandaag een cluster.

Laag één: een echt HA cluster

Dit is de stap die je van “Kubernetes op een laptop” naar “Kubernetes dat ik zou vertrouwen” brengt. De cruciale flag is --cluster-init, die embedded etcd aanzet zodat de control plane een node failure overleeft.

Op node 1:

# Op node 1
curl -sfL https://get.k3s.io | sh -s - server \
  --cluster-init \
  --tls-san k3s.home.lan \
  --tls-san 192.168.1.100 \
  --disable traefik \
  --disable servicelb

Wat die flags doen:

  • --cluster-init: zet embedded etcd aan voor HA
  • --tls-san: voeg SANs toe zodat het API cert geldig is voor de namen en VIP die we gaan gebruiken
  • --disable traefik: sla de ingebouwde ingress over, we nemen onze eigen
  • --disable servicelb: sla de ingebouwde load balancer over, MetalLB doet dit beter

Pak het join token:

sudo cat /var/lib/rancher/k3s/server/node-token

Breng dan node 2 en 3 omhoog, wijzend naar node 1 en met dezelfde flags:

# Op node 2 en 3
curl -sfL https://get.k3s.io | sh -s - server \
  --server https://192.168.1.10:6443 \
  --token <TOKEN_VAN_NODE_1> \
  --tls-san k3s.home.lan \
  --tls-san 192.168.1.100 \
  --disable traefik \
  --disable servicelb

Haal de kubeconfig op, wijs hem naar je workstation, en check dat alle drie de nodes erbij kwamen:

# Haal kubeconfig op
sudo cat /etc/rancher/k3s/k3s.yaml

# Kopieer naar je workstation, update server URL
export KUBECONFIG=~/.kube/k3s-config

# Check nodes
kubectl get nodes
NAME         STATUS   ROLES                       AGE   VERSION
k3s-node-1   Ready    control-plane,etcd,master   5m    v1.28.5+k3s1
k3s-node-2   Ready    control-plane,etcd,master   3m    v1.28.5+k3s1
k3s-node-3   Ready    control-plane,etcd,master   2m    v1.28.5+k3s1

Drie control-plane nodes, elk met een etcd member. Nu kan elk van de drie omvallen en blijft het cluster draaien. Dat is precies waarom je dit doet in plaats van één node draaien.

Eén adres dat een dode node overleeft

Er blijft een gat over. Je kubeconfig wijst naar het IP van één node, en als dat de node is die sterft, stopt je kubectl met werken terwijl het cluster prima in orde is. Je hebt één API adres nodig dat zweeft over welke control-plane nodes ook leven.

Mijn keuze is kube-vip. Het draait als een DaemonSet op de control-plane nodes en beantwoordt voor het VIP (192.168.1.100) via ARP, en verplaatst het automatisch naar een gezonde node.

# Op elke control plane node
kubectl apply -f https://kube-vip.io/manifests/rbac.yaml

# Maak kube-vip manifest
cat <<EOF | sudo tee /var/lib/rancher/k3s/server/manifests/kube-vip.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-vip
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app: kube-vip
  template:
    metadata:
      labels:
        app: kube-vip
    spec:
      hostNetwork: true
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule
      containers:
        - name: kube-vip
          image: ghcr.io/kube-vip/kube-vip:latest
          args:
            - manager
          env:
            - name: vip_interface
              value: eth0
            - name: vip_address
              value: 192.168.1.100
            - name: vip_arp
              value: "true"
            - name: lb_enable
              value: "true"
EOF

Wil je de VIP logica liever buiten het cluster houden, en draait je router HAProxy, dan kun je de API daar balancen:

frontend k3s-api
    bind *:6443
    default_backend k3s-servers

backend k3s-servers
    balance roundrobin
    server node1 192.168.1.10:6443 check
    server node2 192.168.1.11:6443 check
    server node3 192.168.1.12:6443 check

Beide werken. kube-vip houdt alles binnen het cluster, wat ik prefereer omdat er dan één doos minder in het failure pad zit. HAProxy op de router is makkelijker te doorgronden als je je router toch al vertrouwt. Kies degene die past bij wat je al draait.

Laag twee: het bruikbaar maken

Een kaal cluster host nog niets. Drie onderdelen maken er een platform van waar je daadwerkelijk naartoe kunt deployen.

LoadBalancer services met MetalLB

We hebben de ingebouwde servicelb uitgezet, dus LoadBalancer services hebben niets dat ze beantwoordt. MetalLB vult dat gat door IPs uit te delen uit een pool op je LAN.

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.3/config/manifests/metallb-native.yaml

Geef het een range om uit te delen, ruim weg van je node- en VIP-adressen:

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  addresses:
    - 192.168.1.200-192.168.1.250
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system

Ingress

We lieten de ingebouwde Traefik vallen zodat we hem op onze eigen voorwaarden kunnen installeren. Hier is hij via Helm, met een LoadBalancer IP uit de MetalLB pool die we net hebben gedefinieerd:

# Traefik (via Helm)
helm repo add traefik https://traefik.github.io/charts
helm install traefik traefik/traefik \
  --namespace traefik \
  --create-namespace \
  --set service.type=LoadBalancer

Storage met Longhorn

Pods die data moeten bewaren hebben echte persistent volumes nodig. Longhorn geeft je gerepliceerde block storage over de drie nodes, zodat een volume een node die omvalt overleeft:

helm repo add longhorn https://charts.longhorn.io
helm install longhorn longhorn/longhorn \
  --namespace longhorn-system \
  --create-namespace

Twijfel je over je storage opties, dan zet ik Longhorn naast het zwaardere alternatief in Longhorn vs Rook-Ceph.

Met het cluster praten vanaf je bureau

Laatste stukje setup: krijg een kubeconfig op je workstation zodat je niet meer voor alles in node 1 hoeft te SSHen. Die K3s genereert wijst naar 127.0.0.1, dus ruil dat in voor je VIP DNS naam en de kubeconfig blijft werken ongeacht welke node antwoordt.

# Op node 1
sudo cat /etc/rancher/k3s/k3s.yaml

# Sla lokaal op, edit server URL
# Verander: server: https://127.0.0.1:6443
# Naar:     server: https://k3s.home.lan:6443

# Set permissions
chmod 600 ~/.kube/k3s-config
export KUBECONFIG=~/.kube/k3s-config

Het in leven houden

Een homelab cluster dat je nooit onderhoudt wordt een blok aan je been op de dag dat het breekt. Een paar gewoontes houden het saai, wat precies is wat je van infrastructuur wilt.

Upgrades

Upgrade één node tegelijk door de install opnieuw te draaien met dezelfde flags, of laat de system-upgrade-controller het voor je orkestreren:

# Op elke node, één voor één
curl -sfL https://get.k3s.io | sh -s - server \
  # zelfde flags als initiële install
kubectl apply -f https://github.com/rancher/system-upgrade-controller/releases/latest/download/system-upgrade-controller.yaml

Backups

etcd is waar je hele cluster state leeft. Snapshot het. K3s kan dit op een schema doen, en dat moet je het laten doen:

# Handmatige snapshot
k3s etcd-snapshot save --name manual-backup

# Geplande backups (voeg toe aan k3s config)
# /etc/rancher/k3s/config.yaml
etcd-snapshot-schedule-cron: "0 */6 * * *"
etcd-snapshot-retention: 5

Een node uit roulatie halen voor onderhoud

Moet je een doos openmaken of rebooten? Drain hem eerst zodat workloads netjes herschikken, en breng hem dan terug:

# Drain node voor onderhoud
kubectl drain k3s-node-2 --ignore-daemonsets --delete-emptydir-data

# Doe onderhoud...

# Uncordon als klaar
kubectl uncordon k3s-node-2

Als er iets stuk is

Een paar failures komen vaak genoeg voor om ze uit je hoofd te kennen. Als kubectl de server niet kan bereiken, check dan of K3s echt draait, of de firewall poort 6443 niet opvreet, en of je kubeconfig wijst waar je denkt:

sudo systemctl status k3s
sudo ufw status

Een node die vastzit op NotReady betekent meestal dat de kubelet, de container runtime, of de disk ongelukkig is:

sudo journalctl -u k3s -f
sudo crictl ps
df -h

En als etcd zelf corrupt raakt, grijp je naar snapshots. Het --cluster-reset pad bestaat, maar behandel het als laatste redmiddel want het breekt het cluster af tot één member:

# Check etcd health
sudo k3s etcd-snapshot list

# Forceer nieuw cluster als etcd corrupt (GEVAARLIJK)
# sudo k3s server --cluster-reset

Het volledige plaatje: waar het eigenlijk voor is

Weet je nog die one-line install bovenaan? Je bent een heel eind verder gekomen. Je hebt nu drie nodes die een HA control plane delen, een VIP dat een dode node van zich afschudt, load balancing, ingress, gerepliceerde storage, en een backup schema. Dat is een platform.

Dit is wat ik op de mijne draai:

  • GitLab voor code hosting
  • Prometheus en Thanos voor monitoring
  • ArgoCD voor GitOps
  • Home Assistant voor automation
  • Nextcloud voor file sync
  • Vaultwarden voor wachtwoorden

Alles zelf-gehost, alles dat alleen aan mij verantwoording aflegt.

De geldkant is bijna oneerlijk. De equivalent in de cloud draaien kost me €150-300 per maand. De plank kost misschien €10-20 aan elektriciteit:

Cloud (3 nodes)Homelab (3 nodes)
Maandelijks€150-300€10-20 (elektriciteit)
Jaarlijks€1800-3600€120-240
3 jaar€5400-10800€360-720 + €450 hardware

De hardware verdient zichzelf terug in drie tot zes maanden. Daarna is het in feite gratis compute die ik volledig bezit.

Maar het geld was voor mij nooit de echte reden. Je eigen cluster draaien dwingt je te leren hoe Kubernetes zich werkelijk gedraagt, niet alleen welk commando het dashboard groen maakt. Je ontdekt wat er echt gebeurt als een node sterft, want op een gegeven moment sterft er een node en sta jij te kijken naar de VIP die failover doet. Je leert capacity planning met je eigen RAM op het spel. Dat begrip is meer waard dan de bespaarde euro’s, en het is het soort begrip dat je niet kunt kopen bij een control panel.

En je krijgt sovereignty over de hele stack. Jouw data, jouw regels, jouw verantwoordelijkheid. De cloud is prima voor werk dat van iemand anders is. De dingen die er voor mij toe doen staan op een plank bij mij thuis, waar ik de hardware kan aanraken, en waar de beste manier om het te begrijpen is om het expres kapot te maken en te kijken hoe het zichzelf herstelt.