Envoy Gateway : 1 seul LoadBalancer avec MergeGateways

Éviter la prolifération de LoadBalancers avec Envoy Gateway : la feature mergeGateways pour partager un seul Service LB entre plusieurs Gateways d'un cluster Kubernetes.

RV
Rémi Verchère
Platform & Cloud Native
5 min de lecture

Dernier article sur Envoy Gateway ! Aujourd'hui, comment éviter d'avoir un Service LoadBalancer par Gateway déployée. La réponse d'Envoy Gateway : mergeGateways.

Lorsque j'ai migré depuis Ingress NGINX, j'ai été un peu dérouté avec la notion de HTTPRoutes, Gateways, etc. Et j'ai remarqué que pour chaque Gateway définie dans le cluster, Envoy Gateway génère son propre déploiement Envoy et son propre Service de type LoadBalancer. Si sur le principe c'est cohérent avec la spec, j'aimerais un moyen de mutualiser les LoadBalancers entre les Gateways du cluster, pour notamment économiser des coûts de cloud (et les IPs publiques).

La problématique : un LoadBalancer par Gateway

En théorie, les équipes devs n'ont pas à gérer les Gateways, seulement les Routes, c'est aux ops de gérer cette partie. Mais parfois on peut donner de l'autonomie aux équipes, et qu'elles gèrent leurs propres Gateways avec leur policies. Sans mergeGateways, le schéma est le suivant : chaque équipe qui déploie une Gateway pour son application se retrouve avec son propre LB provisionné côté cloud, sa propre IP.

Gateway "eg" (envoy-gateway-system)      → Service LB → IP 203.0.113.10
Gateway "app1-gw" Service LB IP 203.0.113.11
Gateway "app2-gw" Service LB IP 203.0.113.12
Gateway "monitoring-gw" (monitoring)     → Service LB → IP 203.0.113.13
[...]

Ce comportement est différent d'avant avec les ingress, où j'avais 1 seul LB pour toutes mes ressources. On va voir comment retrouver cette façon de faire.

La solution : mergeGateways

Envoy Gateway propose via la ressource EnvoyProxy un paramètre mergeGateways. Quand il est à true, toutes les Gateway utilisant la même GatewayClass partagent un seul déploiement Envoy et un seul Service LoadBalancer.

La ressource EnvoyProxy

C'est ici qu'on active la fusion des Gateways, et qu'on configure le Service LB partagé :

# envoyproxy-custom-proxy-config.yaml
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
  name: custom-proxy-config
  namespace: envoy-gateway-system
spec:
  mergeGateways: true # <-- là
  provider:
    type: Kubernetes
    kubernetes:
      envoyService:
        type: LoadBalancer
      envoyDeployment:
        [...]

Le GatewayClass qui référence l'EnvoyProxy

La GatewayClass fait le lien entre les Gateway et notre configuration EnvoyProxy, via parametersRef :

# gatewayclass-eg.yaml
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: custom-proxy-config # <-- mergeGateways: true
    namespace: envoy-gateway-system

Toute Gateway déclarée avec gatewayClassName: eg bénéficiera automatiquement du merge.

La Gateway principale

La gateway "centrale" reste dans envoy-gateway-system, avec ses listeners HTTP/HTTPS habituels :

# gateway-eg.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: eg
  namespace: envoy-gateway-system
spec:
  gatewayClassName: eg
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: All
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - kind: Secret
            name: ip-cert-tls # Voir article précédent
      allowedRoutes:
        namespaces:
          from: All

Exemple : App1 avec sa propre Gateway

Avec mergeGateways, chaque application peut avoir sa propre Gateway dans son propre namespace.

La Gateway applicative

App1 déclare sa Gateway dans son namespace app1, avec son listener HTTPS et son certificat Let's Encrypt géré par cert-manager via l'annotation :

# app1-gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: app1-gateway
  namespace: app1
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod-eg
spec:
  gatewayClassName: eg
  listeners:
    - name: https
      protocol: HTTPS
      hostname: app1.gravitek.io
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - kind: Secret
            name: app1.gravitek.io-tls
      allowedRoutes:
        namespaces:
          from: Same

La HTTPRoute

# app1-httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app1
  namespace: app1
spec:
  parentRefs:
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: app1-gateway
      namespace: app1
      sectionName: https
  hostnames:
    - app1.gravitek.io
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - kind: Service
          name: app1
          port: 8080
          weight: 1

Résultat : App1 est accessible via la même IP publique que toutes les autres applications, sans LB supplémentaire, avec son propre certificat géré de façon autonome dans son namespace.

Remarques

  • Si besoin particulier, chaque application peut maintenant gérer des règles différentes sur les ClientTrafficPolicy, BackendTrafficPolicy & co.

  • ⚠️ Pour la gestion TLS, dans mon cas d'usage toutes les applis ont des routes HTTPS, pas HTTP. La redirection HTTP vers HTTPS est gérée par la Gateway par défaut, avec un redirect permanent. Cela m'a posé soucis avec cert-manager, et cette configuration marche bien chez moi.

Alternative : ListenerSets

Gateway API v1.3 propose une approche alternative avec les ListenerSets : une Gateway principale expose des listeners de base, et des ListenerSet permettent d'en ajouter depuis d'autres namespaces.

C'est séduisant car c'est natif Gateway API (pas une extension Envoy), avec un modèle de permissions plus granulaire sur les listeners. Mais il y a des limites importantes à date :

  • Seule HTTPRoute supportée : si vous utilisez des GRPCRoute, TCPRoute, elles ne peuvent pas référencer des listeners issus d'un ListenerSet.
  • Encore expérimental : la feature est en alpha dans Gateway API, le support varie selon les implémentations.

Pour l'instant, mergeGateways reste la solution que j'ai choisie, à vous de voir laquelle vous préférez.

Conclusion

Avec mergeGateways, on passe d'un modèle "un LB par Gateway" à "un LB pour tout le cluster", retrouvant à peu près la configuration que j'avais avec Ingress NGINX :

  • 💰 Un seul LoadBalancer partagé entre toutes les Gateways
  • 🌐 Une seule IP publique → gestion DNS simplifiée
  • 🧩 Autonomie conservée : chaque application gère sa propre Gateway dans son namespace, avec ses propres certificats et listeners

Bien sûr, rien ne vous empêche d'avoir plusieurs Gateways quand même, avec ou sans mergeGateways, cela dépend de votre besoin et architecture !

Ressources

© Gravitek. Tous droits réservés.

logo

Gravitek est une Société à taille humaine, guidée par la qualité de service et la construction d'une relation durable avec ses clients.