❯ infra · Running

Construction d'un cluster k3s HA sur Raspberry Pi Building a 6-Node RPi k3s HA Cluster

Comment j'ai construit un cluster Kubernetes haute disponibilité sur six Raspberry Pi — choix matériels, bootstrap k3s, conception réseau et leçons apprises. How I built a production-grade, high-availability Kubernetes cluster on six Raspberry Pi boards — hardware choices, k3s bootstrapping, network design, and lessons learned.

· 7 min de lecture 7 min read ·
k3s-clusterk8sinfrarpiha
🇫🇷 Lire cet article en français

Why a Raspberry Pi homelab?

512 MB. That is all the headroom k3s leaves on a master with 8 GB of RAM once etcd and the kube-apiserver are running. The question comes up often: why put yourself through that constraint when cloud credits are available for free? Because constraint is the best teacher. On an RPi with 8 GB of RAM shared between etcd, the kube-apiserver and a dozen application pods, every megabyte counts. That constraint forces a deep understanding of what Kubernetes components actually consume.

This cluster was designed with a clear goal: reproduce the practices of a production cloud-native infrastructure, but on personal hardware, on a controlled budget.

Hardware architecture

The cluster is made up of 6 Raspberry Pi nodes:

NodeModelRAMRoleIP
master-1RPi 48 GBcontrol-plane + etcd10.0.0.11
master-2RPi 48 GBcontrol-plane + etcd10.0.0.12
master-3RPi 48 GBcontrol-plane + etcd10.0.0.13
worker-1RPi 516 GBmain worker10.0.0.21
worker-2RPi 516 GBmain worker10.0.0.22
worker-3RPi 42 GBauxiliary worker10.0.0.23

The masters use 32 GB SD cards for the operating system, with NVMe SSDs over USB 3.0 for Longhorn storage. The RPi 5 workers have M.2 SSDs through the official HAT, which delivers noticeably better performance.

Network: every node is wired over Gigabit Ethernet through an unmanaged switch. IPs are fixed via DHCP reservations on the router. No VLAN in this version — simplicity first.

Choosing k3s

K3s won over kubeadm for several reasons:

  • Reduced memory footprint: k3s uses ~512 MB on the masters versus ~1.5 GB for a standard kubeadm cluster.
  • Single binary: one executable bundles every control-plane component. Fewer moving parts to manage.
  • Built-in etcd: k3s ships an automatically managed etcd distribution, perfect for a 3-node HA setup.
  • Native ARM64: official images for armv7 and arm64, no fiddling required.

The one notable limitation: k3s uses its own implementation of certain features (the datastore in particular), which can create slight divergences from a “vanilla” Kubernetes cluster. In practice, that has never caused a problem.

Bootstrapping the cluster

First master

# On master-1
curl -sfL https://get.k3s.io | sh -s - server \
  --cluster-init \
  --tls-san 10.0.0.11 \
  --tls-san k3s.local \
  --disable traefik \
  --disable servicelb \
  --node-label node-type=master

The --disable traefik option is intentional: Traefik will be installed through Helm with a custom configuration, not through the k3s bundle. --cluster-init initialises a new etcd cluster.

# Retrieve the token for the other nodes
sudo cat /var/lib/rancher/k3s/server/node-token

Additional masters

# On master-2 (adjust --tls-san per node: 10.0.0.12 or 10.0.0.13)
curl -sfL https://get.k3s.io | sh -s - server \
  --server https://10.0.0.11:6443 \
  --token <TOKEN> \
  --tls-san 10.0.0.12 \
  --disable traefik \
  --disable servicelb \
  --node-label node-type=master

Workers

# On each worker
curl -sfL https://get.k3s.io | K3S_URL=https://10.0.0.11:6443 \
  K3S_TOKEN=<TOKEN> sh -s - agent \
  --node-label node-type=worker

After a few minutes:

$ sudo kubectl get nodes
NAME       STATUS   ROLES                       AGE   VERSION
master-1   Ready    control-plane,etcd,master   5m    v1.34.3+k3s1
master-2   Ready    control-plane,etcd,master   4m    v1.34.3+k3s1
master-3   Ready    control-plane,etcd,master   4m    v1.34.3+k3s1
worker-1   Ready    <none>                      3m    v1.34.3+k3s1
worker-2   Ready    <none>                      3m    v1.34.3+k3s1
worker-3   Ready    <none>                      2m    v1.34.3+k3s1

High availability — how does it work?

HA in k3s rests on three mechanisms:

1. etcd quorum: with 3 masters, the cluster can survive the loss of one node. etcd requires (n/2)+1 members to reach quorum — that is 2 out of 3. Losing 2 masters at once interrupts the control-plane (existing workloads nevertheless keep running on the workers).

2. Distributed kube-apiserver: each master exposes the Kubernetes API. If a master fails, kubectl and the controllers fail over automatically to the remaining masters. The KUBECONFIG configuration points at one master’s IP, ideally behind a load balancer (not implemented here — a future improvement).

3. Self-healing: the kubelet on each node restarts crashed pods. k3s itself is configured as a systemd service with Restart=always.

Configuring kubectl locally

To avoid having to SSH in for every command:

# Copy the kubeconfig from master-1
scp [email protected]:/etc/rancher/k3s/k3s.yaml ~/.kube/config

# Change the server IP
sed -i 's/127.0.0.1/10.0.0.11/g' ~/.kube/config
chmod 600 ~/.kube/config

Lessons learned

Node labels from the start: the nodeSelector fields in the manifests depend on labels. Setting node-type=master and node-type=worker at install time avoids catching up later.

Disable the bundled components: k3s includes Traefik and ServiceLB by default. Disabling them at bootstrap (--disable traefik --disable servicelb) avoids conflicts when installing Helm versions.

Master storage: use SSDs even for the masters. An SD card degrades quickly under etcd load. A €20 USB 3.0 SSD changes everything.

worker-3 at 2 GB: this node turned out to be too limited to host substantial workloads. Today it mainly serves to raise the Longhorn quorum (3rd replica) for the volumes that need it. Never underestimate the minimum RAM required by the Kubernetes agents.

Going further

The logical next step: encrypt Kubernetes secrets before committing them into Git with Sealed Secrets, then automate deployments with ArgoCD.

k3s-clusterk8sinfrarpiha