Kubernetes im Homelab installieren (mit BGP und Gateway API Support)

Da ich mein Homelab noch weiter automatisieren wollte habe ich beschlossen anstelle eines Docker-Hosts einen Single-Node-K8S-Cluster aufzusetzen.

Als Kubernetes-Distribution habe ich mich für RKE2 entschieden. RKE2 ist einfach zu installieren und gut supported. Die mitgelieferte Kubernetes-Version ist meist sehr aktuell. Schon die Standard-Installation bringt einen guten Umfang mit, mit dem man sofort arbeiten kann.

Da ich als Container-Netzwerk aber Cilium in der neusten Version, BGP und Envoy Gateway nutzen möchte sind bei der Installation ein paar Dinge anzupassen.

Voraussetzungen:

  • BGP-Konfiguration am Router (hier am Beispiel eines Unifi Cloud Gateway Fiber)
  • dedizierter Server mit installiertem Linux-Betriebssystem (in meinem Fall ein ASUS PN51-E1-Mini-PC mit openSUSE 16)

Vorbereitung am Router – BGP Konfiguration:

Die BGP-Konfiguration speichere ich in der Datei bgp.conf um sie anschliessend im UI des Gateway hoch zu laden.

frr version 9.1
frr defaults traditional
hostname UniFi-Gateway
log syslog informational
service integrated-vtysh-config
!
router bgp 65100
 bgp router-id 192.168.1.1
 no bgp ebgp-requires-policy
 neighbor RKE2 peer-group
 neighbor RKE2 remote-as 65200
 neighbor 192.168.1.238 peer-group RKE2
 address-family ipv4 unicast
  neighbor RKE2 activate
  neighbor RKE2 next-hop-self
  redistribute connected
 exit-address-family
!
end
bgp.conf

Die Konfiguration importieren wir unter «Einstellungen ⚙️ => Richtlinientabelle 🪄 => Dynamisches Routing => BGP-Konfiguration importieren«

Nach einem Klick auf den Hinzufügen-Button ist die Konfiguration im Gateway und wird automatisch geladen.

Jetzt können wir mit der Installation auf dem Server beginnen.

K8S Installation auf dem Server

# RKE2 installieren
curl -sfL https://get.rke2.io | sudo INSTALL_RKE2_TYPE="server" sh
Zsh
[WARN]  /usr/local is read-only or a mount point; installing to /opt/rke2
[INFO]  finding release for channel stable
[INFO]  using v1.34.3+rke2r1 as release
[INFO]  downloading checksums at https://github.com/rancher/rke2/releases/download/v1.34.3%2Brke2r1/sha256sum-amd64.txt
[INFO]  downloading tarball at https://github.com/rancher/rke2/releases/download/v1.34.3%2Brke2r1/rke2.linux-amd64.tar.gz
[INFO]  verifying tarball
[INFO]  unpacking tarball file to /opt/rke2
[INFO]  updating tarball contents to reflect install path
[INFO]  moving systemd units to /etc/systemd/system
[INFO]  install complete; you may want to run:  export PATH=$PATH:/opt/rke2/bin
Zsh

Bevor wir RKE2 starten, passen wir die Konfiguration an:

# Verzeichnis für die Konfiguration anlegen
sudo mkdir -p /etc/rancher/rke2

# Konfigurationsdatei erstellen
cat <<EOF | sudo tee /etc/rancher/rke2/config.yaml
cni: none
disable-kube-proxy: true
disable:
  - rke2-canal
  - rke2-ingress-nginx  # (Da wir Envoy nutzen wollen)
EOF
Zsh

Jetzt starten wir RKE2 und sorgen gleich dafür, dass es bei einem Server-Neustart immer automatisch gestartet wird:

sudo systemctl enable --now rke2-server.service
Zsh

Die .kube-config von RKE2 befindet sich hier:

/etc/rancher/rke2/rke2.yaml
Zsh

Ich kopiere diese Datei immer auf meinen lokalen Rechner und passe die Namen und URL entsprechend an um bequemer mit dem Cluster zu agieren.

Der Cluster sollte nach kurzer Zeit erreichbar sein, aber in einem «NotReady»-Status, weil kein Cluster Network Interface (CNI) installiert ist:

 k get nodes
NAME           STATUS     ROLES                AGE   VERSION
asus-pn51-e1   NotReady   control-plane,etcd   79s   v1.34.3+rke2r1
Zsh
 k describe node asus-pn51-e1
  RenewTime:       Sun, 11 Jan 2026 19:21:35 +0100
Conditions:
  Type             Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message
  ----             ------  -----------------                 ------------------                ------                       -------
  EtcdIsVoter      True    Sun, 11 Jan 2026 19:18:41 +0100   Sun, 11 Jan 2026 19:18:41 +0100   MemberNotLearner             Node is a voting member of the etcd cluster
  MemoryPressure   False   Sun, 11 Jan 2026 19:21:34 +0100   Sun, 11 Jan 2026 19:18:31 +0100   KubeletHasSufficientMemory   kubelet has sufficient memory available
  DiskPressure     False   Sun, 11 Jan 2026 19:21:34 +0100   Sun, 11 Jan 2026 19:18:31 +0100   KubeletHasNoDiskPressure     kubelet has no disk pressure
  PIDPressure      False   Sun, 11 Jan 2026 19:21:34 +0100   Sun, 11 Jan 2026 19:18:31 +0100   KubeletHasSufficientPID      kubelet has sufficient PID available
  Ready            False   Sun, 11 Jan 2026 19:21:34 +0100   Sun, 11 Jan 2026 19:18:31 +0100   KubeletNotReady              container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized
Addresses:
  InternalIP:  192.168.1.238
  Hostname:    asus-pn51-e1
Capacity:
  cpu:                16
  ephemeral-storage:  485825Mi
  hugepages-1Gi:      0
Zsh

Um Cilium zu installieren benutze ich die Cilium-CLI (diese kann man am einfachsten mit Brew installieren: brew install cilium-cli)

cilium install \
  --version 1.18.5 \
  --set bgpControlPlane.enabled=true \
  --set bgpControlPlane.defaultInstance.type=cluster \
  --set kubeProxyReplacement=true \
  --set operator.replicas=1 \
  --set gatewayAPI.enabled=true
Zsh

Nach kurzer Zeit ist das Cilium CNI installiert und einsatzbereit:

 cilium status
    /¯¯\
 /¯¯\__/¯¯\    Cilium:             OK
 \__/¯¯\__/    Operator:           OK
 /¯¯\__/¯¯\    Envoy DaemonSet:    OK
 \__/¯¯\__/    Hubble Relay:       disabled
    \__/       ClusterMesh:        disabled

DaemonSet              cilium                   Desired: 1, Ready: 1/1, Available: 1/1
DaemonSet              cilium-envoy             Desired: 1, Ready: 1/1, Available: 1/1
Deployment             cilium-operator          Desired: 1, Ready: 1/1, Available: 1/1
Containers:            cilium                   Running: 1
                       cilium-envoy             Running: 1
                       cilium-operator          Running: 1
                       clustermesh-apiserver
                       hubble-relay
Cluster Pods:          4/4 managed by Cilium
Helm chart version:    1.18.5
Image versions         cilium             quay.io/cilium/cilium:v1.18.5@sha256:2c92fb05962a346eaf0ce11b912ba434dc10bd54b9989e970416681f4a069628: 1
                       cilium-envoy       quay.io/cilium/cilium-envoy:v1.34.12-1765374555-6a93b0bbba8d6dc75b651cbafeedb062b2997716@sha256:3108521821c6922695ff1f6ef24b09026c94b195283f8bfbfc0fa49356a156e1: 1
                       cilium-operator    quay.io/cilium/operator-generic:v1.18.5@sha256:36c3f6f14c8ced7f45b40b0a927639894b44269dd653f9528e7a0dc363a4eb99: 1
Zsh

Auch der Cluster-Status sollte sich zu «Ready» geändert haben:

 k get nodes
NAME           STATUS   ROLES                AGE   VERSION
asus-pn51-e1   Ready    control-plane,etcd   13m   v1.34.3+rke2r1
Zsh

Jetzt können wir Envoy Gateway installieren. Das mache ich mit Helm:

helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.6.1 -n envoy-gateway-system --create-namespace
Zsh
Pulled: docker.io/envoyproxy/gateway-helm:v1.6.1
Digest: sha256:6ede7b1df2938132758290dc8e8038d1a8c11b9fda36917c9bc15da2d06bc85a
NAME: eg
LAST DEPLOYED: Sun Jan 11 19:44:57 2026
NAMESPACE: envoy-gateway-system
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None
NOTES:
**************************************************************************
*** PLEASE BE PATIENT: Envoy Gateway may take a few minutes to install ***
**************************************************************************

Envoy Gateway is an open source project for managing Envoy Proxy as a standalone or Kubernetes-based application gateway.

Thank you for installing Envoy Gateway! 🎉

Your release is named: eg. 🎉

Your release is in namespace: envoy-gateway-system. 🎉

To learn more about the release, try:

  $ helm status eg -n envoy-gateway-system
  $ helm get all eg -n envoy-gateway-system

To have a quickstart of Envoy Gateway, please refer to https://gateway.envoyproxy.io/latest/tasks/quickstart.

To get more details, please visit https://gateway.envoyproxy.io and https://github.com/envoyproxy/gateway.
Zsh

Nach kurzer Zeit ist auch Envoy bereit und wir können mit «egctl» (brew install egctl) das Admin-Dashboard öffnen:

 egctl x dashboard eg
Found Envoy Gateway pod: envoy-gateway-64d8866b44-kz9tc
Setting up port-forward to Envoy Gateway admin server...
Envoy Gateway admin console URL: http://localhost:56859
Opening browser to access the Envoy Gateway admin console...
Press Ctrl+C to quit
http://localhost:56859
Zsh

Damit haben wir die grundlegende Installation des Clusters abgeschlossen und haben jetzt:

  • RKE2 in der aktuellsten Version
  • Cilium als CNI mit Unterstützung für Gateway API und BGP
  • Envoy Gateway

Jetzt können wir noch kurz ein Gateway zum Test erstellen:

# 1. Definiert, WAS angekündigt wird (LoadBalancer IPs)
apiVersion: cilium.io/v2
kind: CiliumBGPAdvertisement
metadata:
  name: bgp-advertisement
  labels:
    bgp.cilium.io/advertise: loadbalancer-services
spec:
  advertisements:
    - advertisementType: "Service"
      service:
        addresses:
          - LoadBalancerIP
      selector:
        matchLabels: {}

---
# 2. Definiert, an WEN wir senden (Dein UniFi Router)
apiVersion: cilium.io/v2
kind: CiliumBGPPeerConfig
metadata:
  name: unifi-peer-config
spec:
  families:
    - afi: ipv4
      safi: unicast
      advertisements:
        matchLabels:
          bgp.cilium.io/advertise: loadbalancer-services
  gracefulRestart:
    enabled: true

---
# 3. Verknüpft alles mit deinem Node
apiVersion: cilium.io/v2
kind: CiliumBGPClusterConfig
metadata:
  name: bpg-cluster-config
spec:
  nodeSelector:
    matchLabels:
      kubernetes.io/os: linux
  bgpInstances:
    - name: "asus-pn51-e1"
      localASN: 65200
      peers:
        - name: "unifi-router"
          peerAddress: 192.168.1.1
          peerASN: 65100  # Hier gehört die Remote-ASN jetzt hin!
          peerConfigRef:
            name: unifi-peer-config

---
# 4. Aus diesem Pool werden die IP-Adressen genommen
apiVersion: "cilium.io/v2alpha1"
kind: CiliumLoadBalancerIPPool
metadata:
  name: "envoy-gateway-pool"
spec:
  blocks:
    - cidr: "192.168.200.240/28"
  serviceSelector:
    matchLabels: {}

---
# 5. Gateway-Klassen-Definition
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: envoy-gateway-class
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller

---
# 6. Das Gateway, über das wir unsere Routen erreichbar machen
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: external-gateway
  namespace: default
spec:
  gatewayClassName: envoy-gateway-class
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: All
Zsh

Wir sollten jetzt unser Gateway sehen und die IP-Adresse über die es jetzt erreichbar ist. Manchmal dauert es etwas bis es eine externe IP vom Unifi Gateway bekommt. Aber spätestens nach 1-2 Minuten sollte die Ausgabe so aussehen.

 k get gtw
NAME               CLASS                 ADDRESS           PROGRAMMED   AGE
external-gateway   envoy-gateway-class   192.168.200.240   True         122m
Zsh

Und anschliessend noch eine kleine Test-Applikation:

# 1. Ein einfacher Test-Pod + Service
apiVersion: v1
kind: Pod
metadata:
  name: whoami
  labels:
    app: whoami
spec:
  containers:
  - name: whoami
    image: traefik/whoami
---
apiVersion: v1
kind: Service
metadata:
  name: whoami-service
spec:
  selector:
    app: whoami
  ports:
  - port: 80
---
# 2. Die Route zum Gateway
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: whoami-route
spec:
  parentRefs:
    - name: external-gateway
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: whoami-service
          port: 80
Zsh

Jetzt ist unsere kleine Demo-App über das Envoy Gateway erreichbar:

Damit haben wir den Grundstein für unseren K8S Server gelegt und können jetzt weitere Tools und Applikationen installieren.

Als nächstes werden wir Tools wie External-DNS installieren um automatisch A-Records im DNS-Server zu erstellen. Dann werden werden wir mit Cert-Manager Let’s-Encrypt-Zertifikate installieren um unsere Apps über HTTPS erreichen zu können.

Leave a Comment