Von NGINX Ingress zu Envoy Gateway: Ein Praxisbericht

20 Apr. 2026

7 Minuten Lesezeit

Ein Bild zur Veranschaulichung der Migration von NGINX Ingress zu Envoy Gateway.

Die Kubernetes Gateway API ist der offizielle Nachfolger der Ingress API. Neben vieler technischer Neuerungen und Vorteile hat uns allerdings das Abkündigen der Weiterentwicklung des NGINX Ingress Controller zum Handeln gefordert.

Genau dieses Handeln soll Inhalt dieses Beitrags sein und unsere Migration einer produktiven Multi-Environment-Plattform vom NGINX Ingress Controller zur GatewayAPI mittels Envoy Gateway dokumentieren.

Ausgangslage

Unsere Plattform läuft auf provisionierten Kubernetes Clustern auf OVH mit mehreren Umgebungen. Die Infrastruktur wird mittels Pulumi verwaltet und alle Ressourcen auf den Clustern werden deklarativ via Helm und ArgoCD bereitgestellt. Vor der Migration war unser Setup typisch aufgeteilt, mit einem zentralen NGINX Ingress Controller und entsprechender zentraler Konfiguration, so wie die dezentralen Ingresse, welche für die verschiedenen Anwendungen als Einstiegspunkte agierten und jeweils nochmal eigene Konfiguration mitbrachten.

NGINX Ingress hat uns bis dahin gut gedient, hatte aber ein paar Mankos: Annotations sind untypisiert, Security-Features wie IP-Whitelisting und WAF erfordern Annotation-Bloat und das Routing-Modell kennt keine Trennung zwischen Infrastruktur- und Applikationsverantwortung.

Die Architektur der Gateway API

Das zentrale Konzept der Gateway API ist die Rollentrennung:

Rollenverteilung der Gateway API.

Diese Trennung haben wir 1:1 umgesetzt: Die Gateway-Infrastruktur liegt im eigenen Namespace und wird zentral verwaltet, während die HTTPRoutes in den jeweiligen App-Namespaces durch die Entwickler:innen definiert werden.

Schritt 1: Envoy Gateway installieren

Wir deployen Envoy Gateway über zwei Helm-Charts. Eines für die CRDs, eines für den Controller:

# Chart.yaml (CRDs)
dependencies:
  - name: gateway-crds-helm
    version: v1.7.1
    repository: oci://docker.io/envoyproxy
# Chart.yaml (Controller)
dependencies:
  - name: gateway-helm
    version: v1.7.1
    repository: oci://docker.io/envoyproxy

Die Trennung von CRDs und Controller ist wichtig: CRDs müssen vor dem Controller existieren. Mittels ArgoCD Sync-Waves kann man die Reihenfolge festlegen, so dass der Ablauf gewährleistet ist.

Schritt 2: GatewayClass und EnvoyProxy konfigurieren

Die GatewayClass verbindet Kubernetes mit der Envoy-Gateway-Implementierung und referenziert eine EnvoyProxy-Ressource für infrastrukturspezifische Einstellungen:

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: eg
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller
  parametersRef:
    group: gateway.envoyproxy.io
    kind: EnvoyProxy
    name: proxy-config
    namespace: envoy-gateway

Die EnvoyProxy-Ressource steuert, wie der Data-Plane-Service provisioniert wird. In unserem Fall als OpenStack LoadBalancer von OVH mit Proxy Protocol:

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
  name: proxy-config
spec:
  provider:
    type: Kubernetes
    kubernetes:
      envoyService:
        type: LoadBalancer
        externalTrafficPolicy: Local
        annotations:
          loadbalancer.openstack.org/proxy-protocol: "v2"

Diese Einstellungen sind notwendig, um die originale IP eines Requests zu übermitteln, um z.B. IP Whitelisting und Rate-Limiting zu realisieren.

Schritt 3: Das Gateway definieren

Das Gateway ist der zentrale Einstiegspunkt, dort definieren wir Listener und zugehörige Einstellungen. 

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: gateway
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-cert"
    external-dns.alpha.kubernetes.io/hostname: "*.my.domain"
spec:
  gatewayClassName: eg
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: All
    - name: https
      protocol: HTTPS
      port: 443
      hostname: "*.my.domain"
      allowedRoutes:
        namespaces:
          from: All
      tls:
        mode: Terminate
        certificateRefs:
          - name: letsencrypt-cert

Insbesondere zwei Aspekte fallen auf:

  1. allowedRoutes.namespaces.from: All – HTTPRoutes aus allen Namespaces dürfen dieses Gateway nutzen. Das ermöglicht App-Teams, ihre Routen selbst zu verwalten.

  2. TLS-Terminierung geschieht am Gateway. Die Cert-Manager-Integration via Annotation funktioniert identisch wie bei Ingress.

Schritt 4: HTTP-zu-HTTPS-Redirect

Was bei NGINX Ingress eine Annotation war (nginx.ingress.kubernetes.io/ssl-redirect: "true") wird bei der Gateway API eine eigene HTTPRoute:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: http-to-https-redirect
spec:
  parentRefs:
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: gateway
      sectionName: http
  rules:
    - filters:
        - type: RequestRedirect
          requestRedirect:
            scheme: https
            statusCode: 301

Über sectionName: http wird der Redirect gezielt nur an den HTTP-Listener gebunden. Expliziter, nachvollziehbarer und im Gegensatz zur Annotation, auch testbar.

Schritt 5: Proxy Protocol und Client-IP

Wer hinter einem LoadBalancer mit Proxy Protocol arbeitet, muss die ClientTrafficPolicy konfigurieren:

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: ClientTrafficPolicy
metadata:
  name: enable-proxy-protocol
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: gateway
  enableProxyProtocol: true
  path:
    escapedSlashesAction: KeepUnchanged

Bei NGINX war das use-proxy-protocol: "true" in der ConfigMap. Envoy Gateway macht daraus eine eigene Ressource, die gezielt auf ein Gateway zeigt. Vorteil: Unterschiedliche Gateways können unterschiedliche Policies haben.

Schritt 6: HTTPRoutes für Applikationen

Hier zeigt sich die Stärke der Gateway API im Alltag. Eine HTTPRoute für eine Anwendung könnte wie folgt aussehen:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  annotations:
    external-dns.alpha.kubernetes.io/hostname: "thisis.my.domain"
  name: http-route
spec:
  parentRefs:
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: gateway
      namespace: envoy-gateway
  hostnames:
    - "thisis.my.domain"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      filters:
        - type: ResponseHeaderModifier
          responseHeaderModifier:
            set:
              - name: Content-Security-Policy
                value: "default-src 'self' ..."
      backendRefs:
        - name: service
          port: 80

Drei Dinge, die wir bei der Migration gelernt haben:

  1. Cross-Namespace-Routing: Die HTTPRoute lebt im App-Namespace, referenziert aber das Gateway aus dem zentral gemanagten Namespace via parentRefs.
    D.h. HTTPRouten und Gateway können an unterschiedlichen Orten leben und mittels ReferenceGrants lässt sich z.B. Multi-Tenancy nativ umsetzen.

  2. Response Header nativ: CSP-Header werden direkt als ResponseHeaderModifier-Filter definiert. Bei NGINX wäre die Umsetzung mittels Annotation erfolgt.

  3. External-DNS-Annotation: Bleibt auf der HTTPRoute – external-dns unterstützt die Gateway API nativ.

Schritt 7: Traffic-Management mit BackendTrafficPolicy

Envoy Gateway bietet granulares Traffic-Management als eigene Ressource:

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
  name: selector-policy
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      name: http-route
  timeout:
    tcp:
      connectTimeout: 10s
    http:
      connectionIdleTimeout: 10s
      requestTimeout: 10s
  rateLimit:
    type: Local
    local:
      rules:
        - limit:
            requests: 100
            unit: Second
  circuitBreaker:
    maxConnections: 5000
  requestBuffer:
    limit: "10Mi"

Was bei NGINX Ingress über ein Dutzend verschiedene Annotations verteilt war, wird hier in einer einzigen, typisierten Ressource gebündelt:

Traffic-Management mit BackendTrafficPolicy.

Schritt 8: Security Policies – IP-Whitelisting

IP-basierte Zugriffskontrolle war bei NGINX ebenfalls eine Annotation:

nginx.ingress.kubernetes.io/whitelist-source-range: "123.123.123.123/32,456.456.456.456/32"

Bei Envoy Gateway wird dies mittels SecurityPolicy umgesetzt mit einem expliziten Deny-by-Default-Modell:

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
  name: portal-security-policy
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      name: http-route
  authorization:
    defaultAction: Deny
    rules:
      - name: allow-internal
        action: Allow
        principal:
          clientCIDRs:
            - "123.123.123.123/32"
            - "456.456.456.456/32"

Das ist ausdrucksstärker: Man definiert explizite Allow-Regeln mit benannten Principals statt einer flachen Komma-separierten IP-Liste. In Kombination mit Helm-Templating lassen sich so elegant mehrere Umgebungen steuern:

Schritt 9: Web Application Firewall mit Coraza

Zusätzlich brauchten wir eine WAF, welche die Funktionalität von ModSecurity übernimmt. Envoy Gateway integriert Coraza als Wasm-Plugin über die EnvoyExtensionPolicy:

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyExtensionPolicy
metadata:
  name: http-route-waf
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      name: http-route
  wasm:
    - name: coraza-waf
      rootID: "coraza"
      code:
        type: Image
        image:
          url: "ghcr.io/corazawaf/coraza-proxy-wasm:0.6.0"
      config:
        directives_map:
          default:
            - "Include @recommended-conf"
            - "SecRuleEngine On"
            - "SecRequestBodyAccess On"
            - "SecAuditEngine On"
            - "SecAuditLogFormat JSON"
            - "SecAuditLog /dev/stdout"
            - "Include @crs-setup-conf"
            - "Include @owasp_crs/*.conf"
        default_directives: default

Die OWASP Core Rule Set (CRS) wird direkt eingebunden. Die WAF lässt sich pro Route aktivieren und entsprechend individuell konfigurieren. Bei NGINX Ingress war ModSecurity ebenfalls wieder über die Annotation der Ingresse konfigurierbar, was leider zu sehr viel Annotation-Bloat führte und schwer testbar war.

Wir sind doch nun fertig, oder?

Wer im Verlauf des Artikels genau gelesen hat, wird sehen, dass der Cert-Manager eingesetzt wird. Damit sind wir aber noch nicht vollständig funktionsfähig, denn wir verwenden zusätzlich noch ExternalDNS, um dynamisch Änderungen an DNS-Einträgen vorzunehmen. Beide Tools sind nativ bereits in der Lage, die GatewayAPI zu bedienen, müssen aber noch entsprechend konfiguriert werden, damit das auch geschieht.

Dazu aktiviert man die Verwaltung von Zertifikaten bei Ressourcen der GatewayAPI wie folgt:

cert-manager:
  installCRDs: true
  config:
    apiVersion: controller.config.cert-manager.io/v1alpha1
    kind: ControllerConfiguration
    enableGatewayAPI: true

Damit die zugehörigen DNS Einträge auch automatisiert angelegt werden, muss ExternalDNS wissen, welche Ressourcen dafür in Betracht kommen, d.h. man ergänzt gateway-httproute.

external-dns:
  sources:
    - gateway-httproute
    - ingress
    - service

Damit sind wir nun in der Lage, automatisiert Zertifikate und DNS Einträge erstellen zu lassen, wenn Entwickler:innen neue Routen anlegen, die mittels Wildcard (*.my.domain) als gültig erachtet werden.

Lessons Learned

Was gut lief:

  • Die Rollentrennung (Gateway vs. HTTPRoute) passt perfekt zu unserem GitOps-Modell: Zentrale Verwaltung der Infrastruktur, also GatewayClass und Gateway und die Entwickler:innen legen selbstständig ihre benötigten Routen an.

  • Die typisierten Policies (BackendTrafficPolicy, SecurityPolicy) eliminieren die Notwendigkeit, aufgeblähte Annotations zu verwenden und lassen sich testen.

  • Cross-Namespace-Routing vereinfacht das Multi-Tenant-Setup erheblich.

  • Die WAF-Integration via Wasm ist robust, performant und läuft nahezu identisch zu ModSecurity.

Worauf man achten sollte:

  • CRD-Reihenfolge: Envoy Gateway CRDs müssen vor dem Controller installiert werden. Bei ArgoCD löst man das über separate Applications mit Sync-Waves.

  • Proxy Protocol: Wenn der LoadBalancer Proxy Protocol v2 nutzt, muss die ClientTrafficPolicy konfiguriert werden – sonst sind alle Client-IPs die des LoadBalancers und wir können kein RateLimiting oder IP Whitelisting umsetzen.

Fazit

Die Migration von NGINX Ingress zu Envoy Gateway war kein Selbstzweck. Getrieben aus der Not heraus, gewann unsere Konfiguration an Ausdrucksstärke, Sicherheit und Wartbarkeit. Die GatewayAPI ist der Kubernetes-Standard der Zukunft und bietet schon jetzt viele Vorteile, jedoch kann ich nicht uneingeschränkt sagen, dass alles von vorherein funktionierte. Gerade die Notwendigkeit, auf die Implementierung eines Anbieters zurückzugreifen, um bestimmte Dinge wie z.B. SecurityPolicies umzusetzen, hat mich ein wenig überrascht, denn genau eine solche Funktionalität erhoffe ich mir in Zukunft als First-Class-Citizen der GatewayAPI und den entsprechenden Implementierungen und nicht als provider-spezifische Ressource.

Zusätzlich gibt es leider nach wie vor bei allen Anbietern noch mehr als genug Kinderkrankheiten (siehe diese Benchmark), auch wenn wir bisher von diesen verschont sind, zeigt sich eindeutig die Notwendigkeit hier auf dem aktuellsten Stand zu bleiben, um einerseits Performance weiter zu ermöglichen, aber auch um Sicherheit und Stabilität zu gewährleisten.

Die vollständige Migration inklusive WAF hat bei uns über alle Umgebungen hinweg ca. eine gute Woche gedauert und bisher sind keinerlei Ausfälle oder Kinderkrankheiten zu beklagen.

Wenn Ihnen dieser Artikel gefallen hat, lesen Sie auch gerne weitere Artikel zum Thema Datensouveränität auf unserem Blog:

Oder informieren Sie sich auf unserer Leistungsseite zu Digitaler Souveränität und melden Sie sich dort zu unserem Themennewsletter an.

Wenn Sie auf der Suche nach Unterstützung für Ihre Internal Developer Platform (IDP) sind, sprechen Sie uns gerne an – wir unterstützen Sie bei der Auswahl und Umsetzung geeigneter Lösungen.

Nächster Artikel

BMAD: Struktur für KI‑gestützte Softwareentwicklung

Zusammenarbeit von Mensch und KI.