Routage HTTP avec Envoy Gateway en pratique : host, path, header et répartition du trafic

7 min read

Les deux premiers articles vous ont donné la base "qu'est-ce que c'est". Celui-ci entre dans ce que vous modifierez réellement au quotidien : HTTPRoute.

Voyez HTTPRoute comme un script de routage pour le trafic entrant, sauf qu'au lieu d'écrire des règles Nginx ou de mémoriser des annotations de contrôleur, vous utilisez des ressources natives Kubernetes.

Une chose à clarifier tout de suite : cet article ne couvre que HTTPRoute, parce qu'il gère la sémantique HTTP/HTTPS. Pour gRPC, vous vous orienterez vers GRPCRoute ; pour du TCP brut, TCPRoute ; pour le TLS passthrough, TLSRoute. Ne forcez pas tout dans HTTPRoute : le YAML commence alors à dégager une forte impression de bricolage.

Le routage le plus courant : host et path

Voici l'exemple le plus typique :

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app-route
spec:
  parentRefs:
    - name: eg
  hostnames:
    - "app.example.com"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: api-service
          port: 8080

Ce que cela signifie :

  • Seul Host: app.example.com correspondra à cette route
  • Le chemin doit commencer par /api
  • Le trafic est envoyé vers api-service:8080

Si vous retirez hostnames, la route devient "basée uniquement sur le chemin, quel que soit le domaine".

Ce que matches peut faire correspondre

matches est l'un des concepts les plus centraux dans HTTPRoute. Pensez-y comme à "quelles conditions déclenchent cette règle".

Conditions de matching courantes :

  • path
  • headers
  • queryParams
  • method

Exemple :

matches:
  - method: GET
    path:
      type: PathPrefix
      value: /api
    headers:
      - name: version
        value: v2

Cela signifie :

  • Seulement les requêtes GET
  • Le chemin commence par /api
  • La requête doit inclure le header version: v2

Ce pattern est idéal pour tester une nouvelle version d'API ou pour déployer progressivement auprès d'utilisateurs ciblés.

Une route, plusieurs règles

Un même HTTPRoute peut router différents chemins vers différents services :

rules:
  - matches:
      - path:
          type: PathPrefix
          value: /api
    backendRefs:
      - name: api-service
        port: 8080
  - matches:
      - path:
          type: PathPrefix
          value: /admin
    backendRefs:
      - name: admin-service
        port: 8080

C'est parfait pour découper plusieurs sous-chemins sous un même domaine, par exemple :

  • /api va vers le service API
  • /admin va vers le backend d'administration

La logique de routage devient immédiatement visible. Contrairement à certains vieux fichiers de configuration où, arrivé à la moitié, vous commencez à douter de votre propre existence.

Header, méthode et query comme conditions

HTTPRoute ne se limite pas au matching de chemin, il peut aussi faire du matching sur les headers, les méthodes et les query params.

Par exemple, envoyer uniquement les requêtes avec version: v2 vers le nouveau service :

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: versioned-route
spec:
  parentRefs:
    - name: eg
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
          headers:
            - name: version
              value: v2
      backendRefs:
        - name: api-v2
          port: 8080
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: api-v1
          port: 8080

Ce pattern fonctionne bien pour :

  • Les déploiements canary
  • Permettre à certains clients d'essayer plus tôt les nouvelles versions
  • La validation manuelle de nouvelles fonctionnalités

Comparé au basculement vers un tout nouveau domaine, le routage par header est plus fin et plus simple à expérimenter.

filters : traiter les requêtes avant le forwarding

HTTPRoute ne fait pas seulement "matcher puis transférer" : il peut aussi appliquer des transformations avant ou après le forwarding. C'est à cela que servent les filters.

Un cas d'usage courant consiste à ajouter des headers :

rules:
  - filters:
      - type: RequestHeaderModifier
        requestHeaderModifier:
          add:
            - name: x-env
              value: prod
    backendRefs:
      - name: api-service
        port: 8080

C'est utile pour :

  • Injecter des métadonnées de traçage avant le forwarding vers le backend
  • Ajouter des headers fixes au niveau de l'entrée
  • Faire des transformations de requête/réponse

Cela dit, les filters sont puissants, mais ils ne vous donnent pas carte blanche pour transformer la couche d'entrée en énorme blob de middleware. Trop de logique empilée sur les routes et le débogage finit par donner l'impression d'avoir besoin de sa propre séance de thérapie.

Répartition pondérée du trafic : le pattern canary le plus courant

Pour envoyer 90 % du trafic vers l'ancienne version et 10 % vers la nouvelle, utilisez weight :

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: canary-route
spec:
  parentRefs:
    - name: eg
  hostnames:
    - "app.example.com"
  rules:
    - backendRefs:
        - name: api-v1
          port: 8080
          weight: 90
        - name: api-v2
          port: 8080
          weight: 10

Il s'agit d'un canary basique / d'une répartition de trafic.

Vous n'avez pas besoin d'un service mesh complet juste pour mener une expérience de trafic. Pour beaucoup de services API, cela suffit largement, et c'est bien plus propre à configurer.

💡 weight est relatif, il n'a pas besoin de totaliser 100. 9 et 1 expriment le même ratio que 90 et 10.

timeouts : ne laissez pas les requêtes pendre pour toujours

HTTPRoute prend en charge la configuration de timeout au niveau de la règle. C'est important : si la couche d'entrée n'impose aucune limite, les mauvaises requêtes restent là indéfiniment comme un client impoli qui refuse de partir.

rules:
  - matches:
      - path:
          type: PathPrefix
          value: /reports
    timeouts:
      request: 10s
      backendRequest: 2s
    backendRefs:
      - name: report-service
        port: 8080

Comprendre les deux champs :

  • request : temps d'attente total maximal pour la requête du client
  • backendRequest : temps d'attente maximal pour une requête unique vers le backend

Notez que backendRequest ne devrait pas dépasser request : la logique reviendrait à dire "le trajet entier dure 10 minutes, mais un seul segment de bus peut attendre 20 minutes". L'univers se mettrait à buguer.

Deux nuances courantes

1. parentRefs peut cibler un listener précis

Si un Gateway possède plusieurs listeners, vous pouvez vous lier à un seul :

parentRefs:
  - name: eg
    sectionName: http

Cela signifie que la route ne s'attache qu'au listener http. Dans les configurations multi-listeners, c'est bien plus prévisible que de s'attacher à tout.

2. Pas de matches = match sur tout

Si une règle n'a pas de matches, elle matche par défaut sur / — en pratique, c'est un catch-all.

C'est pratique, mais cela peut avaler silencieusement ce que vous pensiez être des règles plus spécifiques. Ne l'omettez pas par paresse pour ensuite passer une heure à vous demander pourquoi vos règles précises ne se déclenchent pas.

Vérifier que le routage fonctionne

Après avoir configuré les routes, validez avec curl :

curl -H "Host: app.example.com" http://$GATEWAY_HOST/api/users
curl -H "Host: app.example.com" -H "version: v2" http://$GATEWAY_HOST/api/users

Pour vérifier le status du HTTPRoute :

kubectl get httproute app-route -o yaml

Portez particulièrement attention à :

  • status.parents
  • La condition Accepted

Parfois, le YAML semble correct, mais le Gateway l'a rejeté. C'est comme envoyer une candidature et supposer que vous êtes embauché parce que personne ne vous a répondu.

Résumé en une ligne

Le coeur de HTTPRoute, c'est "on fait d'abord le matching, puis on transfère". Une fois que vous maîtrisez host, path, header et weight, vous couvrez environ 80 % des besoins HTTP d'entrée au quotidien.

Étape suivante

Le prochain article aborde quelque chose que vous rencontrerez dans chaque environnement de production : HTTPS, certificats, terminaison TLS et sécurité : 👉 TLS et sécurité