Der Edge eines Clusters — die Stelle, an der externer Traffic auf die Workloads trifft — ist eine der Komponenten, die man besser einmal richtig baut. Bei mir hat dieser Punkt eine kleine Evolution hinter sich: von Traefik als klassischem Ingress-Controller über die Adoption der Gateway-API bis hin zu Envoy Gateway , das diese API heute exklusiv umsetzt. Dieser Beitrag ist eine ausführliche Tour durch den Aufbau.
Von Traefik zur Gateway-API
Angefangen hat alles mit Traefik als Ingress-Controller — die naheliegende Wahl, charmante Dashboard-UI, schnell aufgesetzt. Mit der Zeit stieß ich aber an die konzeptionellen Grenzen der Ingress-Ressource: Sie ist ein einziges, überladenes Objekt, dessen Lücken jeder Controller mit proprietären Annotations stopft. Routing-Logik, die eigentlich deklarativ sein sollte, verkam zu einer Sammlung anbieterspezifischer Magie in metadata.annotations.
Als Traefik begann, die Gateway-API zu unterstützen, bin ich umgestiegen — und schließlich ganz zur Gateway-API als alleinigem Modell übergegangen. Der entscheidende Unterschied ist die Rollentrennung: Die API zerlegt das Ingress-Problem in saubere, getrennt verantwortbare Ressourcen.
GatewayClass, Gateway und HTTPRoute haben jeweils einen klaren Eigentümer und ein klares Schema — kein Annotation-Wildwuchs mehr, sondern typisierte Felder, die jeder Controller gleich interpretiert. Genau diese Trennung macht den Edge im GitOps-Repo lesbar und portabel.
Warum Envoy Gateway
Envoy Gateway setzt die Gateway-API auf Basis des battle-tested
Envoy-Proxy
um — desselben Proxys, der unzählige Service-Meshes und Cloud-Load-Balancer antreibt. Was mich überzeugt hat, ist weniger die Datenebene (die ist über jeden Zweifel erhaben) als die Erweiterungspunkte: ClientTrafficPolicy, BackendTrafficPolicy, SecurityPolicy und EnvoyProxy heben genau die Envoy-Fähigkeiten in die Gateway-API, die man im Betrieb wirklich braucht — und an die man bei Traefik nur über Umwege kam.
Drei Welten, drei Shared-Gateways
Mein Edge ist nicht ein Gateway, sondern drei — je eines für eine Erreichbarkeits-Domäne, jeweils mit eigener GatewayClass, eigenem EnvoyProxy (zwei Replicas) und einer eigenen LoadBalancer-IP aus dem Cilium-BGP-Pool:
| Gateway | Domäne | TLS-Issuer | LB-IP | Erreichbar von |
|---|---|---|---|---|
shared-gateway-external | *.this-is-fine.io, *.this-is-fine.social | Let’s Encrypt | …0.1 | Internet |
shared-gateway-internal | *.this-is-fine.internal | Vault-PKI | …0.2 | LAN |
shared-gateway-tailnet | *.tif.internal | Vault-PKI | …0.3 | Tailnet |
Jeder Listener terminiert TLS mit einem Zertifikat, das cert-manager passend zur Domäne ausstellt — öffentlich per ACME für das externe Gateway, aus der internen Vault-PKI für die beiden privaten. Ein angehängter Redirect-Filter zwingt jeden :80-Request per 301 auf HTTPS:
- name: "${EXTERNAL_DOMAIN//./-}-https"
protocol: HTTPS
port: 443
hostname: "*.${EXTERNAL_DOMAIN}"
tls:
certificateRefs:
- { kind: Secret, name: "wildcard-${EXTERNAL_DOMAIN//./-}-tls" }
Wichtig ist die Abgrenzung zum gleichnamigen Nachbarn: shared-gateway-tailnet transportiert HTTP(S)-Anwendungen über das Tailnet. Die Kubernetes- und Talos-API dagegen läuft über das tailnet-gateway — zwei verschiedene Pfade, die man nicht verwechseln sollte.
OIDC für Apps, die kein SSO können
Das für mich wertvollste Feature ist die SecurityPolicy: Sie schaltet einem HTTPRoute ein vollständiges OIDC-Login vor, bevor der Request überhaupt das Backend erreicht. Damit bekommt selbst ein Dienst, der von Haus aus keinerlei Authentifizierung kennt, ein sauberes Pocket-ID-Login vorgesetzt — die Auth-Logik wandert aus der Anwendung heraus in den Edge:
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
name: grafana-oidc
spec:
targetRefs:
- { group: gateway.networking.k8s.io, kind: HTTPRoute, name: grafana-internal }
oidc:
provider:
issuer: "https://auth.${EXTERNAL_DOMAIN}"
clientID: "${ENVOY_GATEWAY_OIDC_CLIENT_ID}"
clientSecret:
name: grafana-oidc-secret
redirectURL: "https://grafana.${INTERNAL_DOMAIN}/login/generic_oauth"
logoutPath: "/logout"
cookieDomain: "${INTERNAL_DOMAIN}"
Envoy übernimmt den kompletten Authorization-Code-Flow gegen Pocket-ID, setzt das Session-Cookie und reicht erst den authentifizierten Request weiter. Für ein Homelab voller kleiner Dienste, die SSO niemals selbst implementieren würden, ist das ein enormer Hebel: Ein einziges SecurityPolicy-Manifest, und der Dienst sitzt hinter zentralem SSO.
Feintuning per Policy
Zwei weitere Policy-Typen erlauben Eingriffe in die Datenebene, ohne den Proxy anzufassen. Eine ClientTrafficPolicy regelt das Verhalten zum Client hin — bei mir vor allem HTTP/2-Tuning und großzügige Idle-Timeouts, damit langlebige Verbindungen wie Server-Sent-Events oder Streaming nicht vorzeitig gekappt werden:
kind: ClientTrafficPolicy
spec:
targetRef: { kind: Gateway, name: shared-gateway-external }
timeout:
http:
idleTimeout: 3600s
streamIdleTimeout: 900s
http2:
maxConcurrentStreams: 100
Eine BackendTrafficPolicy wirkt umgekehrt zum Backend hin. Das schönste Beispiel ist meine Zot-Registry: Image-Layer können hunderte Megabyte groß sein und Pushes entsprechend lange dauern. Hier hebe ich gezielt das Request-Buffer-Limit und die Timeouts an, was ich für keinen anderen Backend tun möchte:
kind: BackendTrafficPolicy
metadata:
name: zot-timeouts
spec:
targetRefs:
- { kind: HTTPRoute, name: zot }
requestBuffer:
limit: 2Gi
timeout:
http:
requestTimeout: 600s
connectionIdleTimeout: 3600s
Diese chirurgische Präzision — eine spezielle Einstellung exakt an einem Backend, der Rest unberührt — ist genau das, was die alte annotationsbasierte Ingress-Welt so mühsam machte.
Fazit
Envoy Gateway ist bei mir weit mehr als ein Ingress-Ersatz: Es ist die zentrale Richtungsentscheidung am Edge. Drei sauber getrennte Erreichbarkeits-Domänen, zentrales OIDC für Dienste, die es selbst nie könnten, und backend-genaues Feintuning — alles in typisierten, GitOps-tauglichen Ressourcen statt in Annotation-Magie. Traefik hat mir den Weg in die Gateway-API geebnet; geblieben bin ich bei der API, nicht beim Controller.
