Architecture
A homelab of six mixed machines at home: recycled x86 boxes, mini-PCs and a Raspberry Pi. Together they form a k3s cluster with a highly available control plane. It is a lab for DevOps, observability and security, and it hosts a few services used every day (DNS with Pi-hole, n8n, Jira and Confluence).
Overview
flowchart TB
internet((Internet)) -. no routes .-> cf[Cloudflare<br/>*.example.com]
cf -. tunnel .-> cfd[cloudflared<br/>master-3]
lan((LAN 192.0.2.0/24)) --> vip
lan --> lb
subgraph cp[HA control plane · 3-member etcd]
direction LR
master-1[master-1<br/>.112 · VIP MASTER]
master-2[master-2<br/>.139]
g2[master-3<br/>.129]
end
vip[VIP 192.0.2.250:6443<br/>keepalived + haproxy] --> cp
subgraph wk[Workers]
direction LR
sz[worker-2 .164]
nuc[worker-1 .128]
worker-3[worker-3 .197]
end
cp --- wk
cfd -.-> traefik
lb[MetalLB L2<br/>192.0.2.240-245] --> traefik[Traefik .240<br/>Ingress]
lb --> pihole[Pi-hole DNS .241]
traefik --> apps[Jira · Confluence · n8n ⏸]
Layers
| Layer | Technology | Details |
|---|---|---|
| Operating system | Ubuntu 26.04 (Debian 13 on the worker-3) | nodes/README.md |
| Kubernetes distribution | k3s v1.34.6 | Bundles containerd, flannel, CoreDNS, Traefik, metrics-server and local-path |
| Control plane | 3 masters with embedded etcd | Survives losing one master |
| API endpoint | VIP 192.0.2.250 with keepalived (VRRP) and haproxy (TCP) |
red.md |
| Pod network | flannel VXLAN (the k3s default) | --flannel-iface pinned per node |
LoadBalancer |
MetalLB in L2 mode, pool 192.0.2.240-245 |
ServiceLB/Klipper disabled |
| Ingress | Traefik (the one bundled with k3s) on 192.0.2.240 |
|
| Publishing to the Internet | Cloudflare Tunnel (cloudflared on master-3) |
No ports open on the router. Since 2026-11-01 it has no routes: nothing is published (anything published again will have Cloudflare Access in front) |
| Storage | Longhorn (replicated) and local-path (the node's disk) | almacenamiento.md |
| GitOps | Argo CD against the example-org/GitOps repo |
4 applications (filebeat-routing-job, jenkins-lab, vector-kafka-elastic and homepage); the rest is installed by hand with Helm (R3) |
| Observability | kube-prometheus-stack, ECK (Elasticsearch and Kibana), Kafka, Flink and Vector | aplicaciones.md |
| Security | Kyverno, Falco, Trivy Operator, Policy Reporter | seguridad.md |
Design decisions
- k3s instead of kubeadm: a single binary, light on resources, a good fit for modest hardware and mixed architectures (amd64 and arm64).
- Embedded etcd with 3 masters: a master can be drained and rebooted without losing the API. Migrated from SQLite on 2026-07-03.
- The VIP on a
dummy0interface as a/32: if the VIP is a secondary IP on the real interface, flannel picks it as the node's public IP and breaks the overlay network. With/24instead of/32it hijacks the route for the whole subnet. Both things happened; see incidentes.md. - MetalLB instead of ServiceLB: stable, dedicated IPs per service (Pi-hole needs a fixed IP for DNS).
- Cloudflare Tunnel: it published services without opening ports or exposing the home IP. Since 2026-11-01 it has no routes (nothing on the Internet); whatever is published again will have Cloudflare Access in front.
- Masters that also run workloads: master-2 and master-3 have no control-plane taint, to make the most of the hardware. The trade-off: an I/O-heavy workload on a master affects etcd. It already happened with filebeat and trivy (see incidentes.md).