starting homelab RKE2 cluster

This commit is contained in:
Matthias Hinrichs
2026-01-13 14:42:19 +01:00
commit 9e5a1d7546
14 changed files with 464 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
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
@@ -0,0 +1,22 @@
direkt auf dem Server ausführen
# RKE2 installieren
curl -sfL https://get.rke2.io | sudo INSTALL_RKE2_TYPE="server" sh
# 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
# RKE2-Service enablen und starten
sudo systemctl enable --now rke2-server.service
# K8S kube-config Datei auf Workstation kopieren
sudo cat /etc/rancher/rke2/rke2.yaml
@@ -0,0 +1,22 @@
# notwendige Tools installieren
brew install cilium-cli kubernetes-cli helm egctl switcher cmctl
# !!! zum richtigen cluster wechseln !!! Ich nutze dafür switcher
# cilium installieren
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
# Cilium überprüfen
cilium status
# envoy installieren
helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.6.1 -n envoy-gateway-system --create-namespace
# Envoy Gateway Dashboard
egctl experimental dashboard envoy-gateway
@@ -0,0 +1,92 @@
# 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
---
apiVersion: "cilium.io/v2alpha1"
kind: CiliumLoadBalancerIPPool
metadata:
name: "envoy-gateway-pool"
spec:
blocks:
- cidr: "192.168.200.240/28"
serviceSelector:
matchLabels: {}
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: envoy-gateway-class
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
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
- name: https
protocol: HTTPS
port: 443
allowedRoutes:
namespaces:
from: All
@@ -0,0 +1,40 @@
fullnameOverride: external-dns-unifi
# Konfiguration des Webhook-Providers (der "Übersetzer" für UniFi)
provider:
name: webhook
webhook:
image:
repository: ghcr.io/kashalls/external-dns-unifi-webhook
tag: main
env:
- name: UNIFI_HOST
value: https://192.168.1.1 # Deine Gateway IP
- name: UNIFI_API_KEY
value: "J5VRZY-rGrtdGTf-fj1mTdZeUpLGzDBH"
- name: UNIFI_EXTERNAL_CONTROLLER
value: "false" # false, da der Controller auf dem Gateway selbst läuft
- name: LOG_LEVEL
value: info
livenessProbe:
httpGet:
path: /healthz
port: http-webhook
readinessProbe:
httpGet:
path: /readyz
port: http-webhook
# Allgemeine External-DNS Einstellungen
policy: sync # "sync" löscht auch Einträge, die nicht mehr in K8s sind. "upsert-only" ist sicherer.
sources:
- service
- ingress
- gateway-httproute
# WICHTIG: Nur diese Domain verwalten
domainFilters:
- "k8s.hnrx.net" # <--- ÄNDERE DIES auf deine Domain (muss im UniFi als Domain konfiguriert sein?)
- "hnrx.net"
# Registry (verhindert, dass external-dns fremde Einträge überschreibt)
txtOwnerId: "k8s-cluster"
@@ -0,0 +1,31 @@
# Basic requirements
In diesem Schritt installieren wir
- phase-secrets-operator
- cert-manager
- external-DNS mit Webhook Provider für Unifi
## Phase-Secrets-Operator
helm repo add phase https://helm.phase.dev && helm repo update
helm install phase-secrets-operator phase/phase-kubernetes-operator --set image.tag=v1.3.0
kubectl create secret generic phase-service-token \
--from-literal=token=pss_service:v2:XXXXXXXXXXXXXXXXXXXXX \
--type=Opaque \
--namespace=default
## Cert-Manager und Cluster-Issuer
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.2/cert-manager.yaml
k apply -f manifests
## External-DNS
helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
kubectl create ns external-dns
helm upgrade --install external-dns external-dns/external-dns --namespace external-dns --version 1.19.0 -f external-dns-values.yaml
@@ -0,0 +1,19 @@
apiVersion: secrets.phase.dev/v1alpha1
kind: PhaseSecret
metadata:
name: cloudflare-api-key-phase-secret
namespace: cert-manager
spec:
phaseApp: 'cert-manager' # The name of your Phase application
phaseAppEnv: 'production' # OPTIONAL - The Phase App Environment to fetch secrets from
phaseAppEnvPath: '/' # OPTIONAL Path within the Phase application environment to fetch secrets from
phaseHost: 'https://phase.hnrx.net' # OPTIONAL - URL of a Phase Console instance
authentication:
serviceToken:
serviceTokenSecretReference:
secretName: 'phase-service-token' # Name of the Phase Service Token with access to your application
secretNamespace: 'default'
managedSecretReferences:
- secretName: 'cloudflare-api-key' # Name of the Kubernetes managed secret that Phase will sync
secretNamespace: 'cert-manager'
@@ -0,0 +1,16 @@
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: cloudflare-cluster-issuer
spec:
acme:
email: matthias.hinrichs@me.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: cluster-issuer-account-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-key
key: CLOUDFLARE_API_KEY
+54
View File
@@ -0,0 +1,54 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: argocd
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: argocd-gateway
namespace: argocd
labels:
bgp.cilium.io/ip-pool: default # Damit bekommt das Gateway eine IP aus deinem Pool
annotations:
# Damit external-dns diesen Gateway findet und einen DNS-Eintrag erstellt
# (falls external-dns Gateway API unterstützt, was es tut)
cert-manager.io/cluster-issuer: cloudflare-cluster-issuer
spec:
gatewayClassName: envoy-gateway-class
listeners:
- name: https
hostname: "argocd.k8s.hnrx.net"
protocol: HTTPS
port: 443
allowedRoutes:
namespaces:
from: All
tls:
mode: Terminate
certificateRefs:
- name: argocd-gateway-tls
- name: http
hostname: "argocd.k8s.hnrx.net"
protocol: HTTP
port: 80
allowedRoutes:
kinds:
- kind: HTTPRoute
- kind: GRPCRoute
namespaces:
from: All
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: argocd-gateway-tls
namespace: argocd
spec:
secretName: argocd-gateway-tls
dnsNames:
- argocd.k8s.hnrx.net
issuerRef:
name: cloudflare-cluster-issuer
kind: ClusterIssuer
+100
View File
@@ -0,0 +1,100 @@
global:
domain: "argocd.k8s.hnrx.net"
extraObjects:
- apiVersion: secrets.phase.dev/v1alpha1
kind: PhaseSecret
metadata:
name: argocd-phase-secret
namespace: argocd
labels:
app.kubernetes.io/part-of: argocd
spec:
phaseApp: 'argocd' # The name of your Phase application
phaseAppEnv: 'production' # OPTIONAL - The Phase App Environment to fetch secrets from
phaseAppEnvPath: '/' # OPTIONAL Path within the Phase application environment to fetch secrets from
phaseHost: 'https://phase.hnrx.net' # OPTIONAL - URL of a Phase Console instance
authentication:
serviceToken:
serviceTokenSecretReference:
secretName: 'phase-service-token' # Name of the Phase Service Token with access to your application
secretNamespace: 'default'
managedSecretReferences:
- secretName: 'argocd-authentik-client-secret' # Name of the Kubernetes managed secret that Phase will sync
secretNamespace: 'argocd'
configs:
cm:
url: https://argocd.k8s.hnrx.net
dex.config: |
connectors:
- config:
issuer: ${AUTHENTIK_ISSUER_URL}
clientID: ${AUTHENTIK_CLIENT_ID}
clientSecret: ${AUTHENTIK_CLIENT_SECRET}
insecureEnableGroups: true
scopes:
- openid
- profile
- email
name: authentik
type: oidc
id: authentik
params:
server.insecure: true
rbac:
policy.csv: |
g, ArgoCD Admins, role:admin
g, ArgoCD Viewers, role:readonly
secret:
extra:
dex.authentik.clientSecret: "${AUTHENTIK_CLIENT_SECRET}"
cmp:
credentialTemplates:
https-creds:
url: https://git.hnrx.net
username: ${GIT_USER}
password: ${GIT_PASSWORD}
dex:
envFrom:
- secretRef:
name: argocd-authentik-client-secret
server:
httproute:
enabled: true
parentRefs:
- name: argocd-gateway
namespace: argocd
sectionName: https
hostnames:
- "argocd.k8s.hnrx.net"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: argocd-server
port: 80
grpcroute:
enabled: true
parentRefs:
- name: argocd-gateway
namespace: argocd
sectionName: http
hostnames:
- "argocd.k8s.hnrx.net"
rules:
- matches:
- method:
type: Exact
service: "cluster.argoproj.v1alpha1.repositorieservice"
method: "List"
backendRefs:
- name: argocd-server
port: 443
@@ -0,0 +1,5 @@
# Argo-CD Installation mit SSO über Authentik
helm repo add argo https://argoproj.github.io/argo-helm
helm upgrade --install argocd argo/argo-cd --namespace argocd -f argo-values.yaml
@@ -0,0 +1,10 @@
# Zwischenstand
Der Cluster hat bereits folgendes installiert:
- Cilium Netzwerk mit BGP
- Envoy Gateway API
- Cert-Manager
- External DNS für Einträge im Unifi-DNS-Server
## Weitere Infrastuktur-Bestandteile installieren
Diese werden mit ArgoCD gemanagt.
+27
View File
@@ -0,0 +1,27 @@
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: cluster-infra
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/dein-user/git-repo.git
revision: HEAD
directories:
- path: 08_cluster_infrastructure/*
template:
metadata:
name: '{{path.basename}}'
spec:
project: cluster-infra
source:
repoURL: https://github.com/dein-user/git-repo.git
path: '{{path}}'
helm:
values: values-{{cluster.name}}.yaml # Env-spezifisch
destination:
server: https://kubernetes.default.svc
namespace: '{{path.basename}}'
syncPolicy:
automated: {prune: true, selfHeal: true}
+7
View File
@@ -0,0 +1,7 @@
# RKE2 Single-Node-Cluster for Homelab
This is how I set up my RKE2 single node cluster for my homelab.
## External dependencies
- Authentik instance for SSO
- Phase Secrets Manager to keep all secrets in a safe space