My Kubernetes Setup with OPNsense and RKE2
Summary
This post documents my current Kubernetes architecture running in my homelab. The goal was not just “make it work”, but build something that is structured, secure, reproducible, and close to how I would design it in a small production environment.
The stack is built around:
- OPNsense as firewall and entry point
- HAProxy for load balancing
- RKE2 as the Kubernetes distribution
- Proxmox as virtualization layer
- Dedicated database VMs (PostgreSQL and MariaDB)
- WireGuard for secure access
- Restic for encrypted S3 backups
- Traefik + CrowdSec for ingress and security
- MetalLB for static service IPs
Network Design
The internal cluster network runs on:
10.0.0.0/24
I wanted full control over routing and segmentation instead of relying on consumer router defaults. OPNsense sits at the edge and handles:
- WAN connectivity
- Internal routing
- Firewall rules
- HAProxy load balancing
- WireGuard VPN termination
This gives me a clean separation between internet, VPN access, and internal cluster traffic.
Virtualization Layer (Proxmox)
The cluster runs on Proxmox.
Control Plane
- 3x RKE2 master nodes (VMs)
I chose three masters for etcd quorum and high availability. Even in a homelab, I prefer testing HA designs instead of single-node control planes.
Workers
- 1x worker VM on Proxmox
- 2x worker nodes on dedicated Mini PCs
The Mini PCs give me hardware separation from the hypervisor. If Proxmox needs maintenance or reboots, I still have compute capacity outside the main host.
Why RKE2?
RKE2 was chosen because:
- It is lightweight
- It follows upstream Kubernetes closely
- It includes sensible defaults
- Installation is straightforward
The installation was done following the official RKE2 documentation. Bootstrap was simple:
- Install first server node
- Join remaining masters
- Join workers with token
No complex external etcd setup, no heavy platform layer.
I wanted Kubernetes, not a management platform around it.
Entry Point Architecture
One of the most important design decisions was how traffic enters the cluster.
OPNsense + HAProxy
Instead of exposing nodes directly, I used OPNsense as the single entry point.
HAProxy has:
- Backend “k8s-api” → all 3 master nodes
- Backend “k8s-https” → all worker nodes
This provides:
- Load balancing
- Failover
- Single public endpoint
- Clean firewall control
If a node goes down, HAProxy removes it automatically from rotation.
WireGuard for Secure Access
Administrative access to the cluster is only possible via WireGuard.
I do not expose the Kubernetes API publicly.
WireGuard terminates on OPNsense and gives me secure internal access to:
- Kubernetes API
- Proxmox
- Database VMs
- Internal services
This reduces attack surface significantly.
Cloudflare as HTTP Gatekeeper
All HTTP/HTTPS traffic is proxied through Cloudflare.
Firewall rules only allow Cloudflare IP ranges to access the cluster.
This gives:
- DDoS protection
- TLS termination at edge
- IP masking
- Simple certificate handling
The cluster never talks directly to the open internet for web traffic.
Ingress and Security
Inside the cluster:
- Traefik handles ingress routing
- CrowdSec analyzes logs and blocks malicious traffic
Traefik manages:
- Host-based routing
- Path-based routing
- TLS handling (when needed internally)
CrowdSec adds behavioral detection and automated blocking.
This combination gives both structured routing and dynamic protection.
MetalLB for Static Service IPs
For certain services like game servers, I need predictable IPs.
MetalLB provides static IP assignment inside the 10.0.0.0/24 network.
This is useful for:
- Game servers
- Services that require fixed endpoints
- Clean separation of service exposure
Without MetalLB, NodePorts or hostPorts would become messy over time.
Game Server Architecture
Game servers are exposed through:
- A VPS at Hetzner
- WireGuard tunnel into the cluster
The VPS acts as the public-facing node.
This design allows:
- Public IP exposure without exposing the home network
- Flexible routing
- Isolation between web and game traffic
Database Layer
Instead of running databases inside Kubernetes, I created dedicated VMs:
- PostgreSQL VM
- MariaDB VM
Reasons:
- Clear separation of concerns
- Easier performance tuning
- Simpler backup strategy
- Avoid storage coupling with cluster lifecycle
Stateful data remains outside the Kubernetes scheduling layer.
Backup Strategy
Backups are handled using Restic.
- Encrypted repositories
- Regular snapshots
- Upload to S3-compatible storage
I back up:
- Website data
- Database dumps
- Critical application data
Encryption happens before upload, so storage providers never see plaintext data.
Why This Architecture?
The main goals were:
- High availability for control plane
- Clean entry point design
- Minimal public exposure
- Clear separation of stateful and stateless workloads
- Reproducible setup
OPNsense gives me full control over traffic. RKE2 gives me a stable Kubernetes base. Proxmox gives hardware flexibility. WireGuard reduces exposed services. Cloudflare filters external HTTP traffic. MetalLB simplifies internal service IP management. Restic protects data.
Each component has a single responsibility.
Final Thoughts
This setup is not enterprise scale. But it follows production principles:
- Redundant control plane
- Layered security
- Encrypted backups
- Segmented network
- Controlled ingress
It allows me to experiment, host services, run game servers, and test HA behavior in a realistic environment.
And most importantly: everything is documented and reproducible.