ADR 017 — Load balancer d'exposition : NLB plutôt que Classic ELB (2026-06-14)¶
Statut¶
Accepté (2026-06-14). Issue #70. Validé live le 2026-06-16 (console AWS : 1 seul load balancer, de type Network, internet-facing, aucun Classic ELB).
Contexte¶
Le Service LoadBalancer provisionné par Envoy Gateway pour le Gateway
(point d'entrée public, ADR 010) crée par défaut un Classic ELB (CLB) sur AWS.
Le CLB est déprécié par AWS : plus de nouvelles fonctionnalités, pas de
preserve client IP natif, pas de mode IP target, latence supérieure à un NLB.
Décision 1 — NLB plutôt que CLB¶
On provisionne un Network Load Balancer (NLB, L4) à la place du CLB, via une
ressource EnvoyProxy (gateway.envoyproxy.io/v1alpha1) qui annote le Service
Envoy et est référencée par le GatewayClass (parametersRef) :
spec:
provider:
kubernetes:
envoyService:
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
Le GatewayClass ne se personnalise pas directement : Envoy Gateway délègue la
config d'infrastructure à un EnvoyProxy (cluster-level via parametersRef),
qui doit vivre dans le namespace du contrôleur (envoy-gateway-system).
Décision 2 — NLB, pas ALB¶
L'ALB (L7) serait redondant avec Envoy Gateway, qui fait déjà tout le routing L7 (HTTPRoute, terminaison TLS via cert-manager, hostnames). Un ALB en amont ajouterait une couche L7 inutile et du lock-in AWS. Le NLB (L4, TCP pass-through) est la bonne brique : il transporte le trafic jusqu'à Envoy, qui gère le L7 en aval. Cohérent avec ADR 010 (exposition portable, pas de dépendance à un Ingress controller AWS).
Décision 3 — Annotation in-tree, pas AWS Load Balancer Controller¶
Deux façons d'obtenir un NLB :
| Voie | Annotation | Prérequis |
|---|---|---|
| In-tree (retenue) | aws-load-balancer-type: "nlb" |
Aucun (cloud provider AWS intégré) |
| AWS LB Controller | aws-load-balancer-type: "external" + nlb-target-type: "ip" |
Installer l'AWS LB Controller |
On retient l'in-tree : le projet n'installe pas l'AWS Load Balancer
Controller (Envoy assure le L7, on ne veut pas d'un second contrôleur LB). Le
NLB en mode instance couvre le besoin. Tradeoff assumé : pas de mode IP target
(qui éviterait le double saut via NodePort). Migration possible vers l'AWS LB
Controller + target-type: ip si le besoin de preserve client IP / IP target
apparaît (évolution).
Pièges / Conséquences¶
- Cluster éphémère = pas de migration in-place : le NLB est créé from-scratch
à chaque
aws-startavec le Gateway. On évite le piège du changement de type d'ELB sur un Service existant (AWS recrée le LB, l'ancien peut rester orphelin). L'ancien CLB n'existe que le temps d'une session ; sur un cluster neuf, seul le NLB est créé. - Teardown inchangé (anti-INC-016) : le NLB est supprimé quand le Gateway est
supprimé (Envoy Gateway supprime son Service), exactement comme le CLB
aujourd'hui. L'
EnvoyProxyn'est que de la configuration, il n'ajoute aucune ressource AWS à nettoyer. - ExternalDNS : le CNAME
api.devopsyouss.comsuit le hostname du nouveau LB automatiquement (source gateway-httproute), aucune action manuelle.
Décision 4 — Le listener :80 ne sert qu'à rediriger (#147, 2026-07-26)¶
Le défaut corrigé. Le Gateway expose deux listeners, http:80 et https:443,
tous deux en allowedRoutes: from: All. Or une HTTPRoute sans sectionName
s'attache à tous les listeners que le Gateway lui ouvre — et aucune des 4 routes
du repo n'en portait. Les quatre services publics (api, app, grafana,
argocd) répondaient donc aussi bien en clair qu'en TLS. Constaté en live le
2026-07-26 : curl http://api.devopsyouss.com/healthz/ready renvoyait 200.
Ce n'est pas un détail de configuration. POST /login transporte identifiants et
JWT (app/routers/auth.py), et la CLI argocd transporte un token de session :
sur http://, les deux sont lisibles par un observateur du réseau (CWE-319).
Le TLS fonctionnait, rien n'obligeait simplement à l'emprunter.
La décision, en deux pièces qui ne jouent pas le même rôle :
sectionName: httpssur les 4 routes applicatives. C'est ceci qui ferme le clair : les routes quittent le listener:80.- Une HTTPRoute de redirection sur
sectionName: http(k8s/platform/httproute-https-redirect.yaml), sanshostnamesdonc attrape-tout, avec un filtreRequestRedirecten 301. Elle ne ferme rien, elle offre une sortie propre plutôt qu'un 404 sec, et le 301 étant mis en cache par le navigateur, les visites suivantes partent directement en HTTPS.
La redirection n'a ni hostname ni backend : un RequestRedirect répond au
niveau du Gateway, la requête n'atteint jamais un pod. Elle peut donc vivre dans le
namespace plateforme fastapi sans qu'aucun Service n'y tourne.
Ownership. Elle est posée par le bootstrap Ansible avec le Gateway, et non en GitOps comme les routes applicatives (ADR 010) : elle décrit le comportement d'un listener, elle appartient à la plomberie du Gateway, pas à une application.
Le point de vigilance. Le :80 ne doit plus servir à rien d'autre. Les
certificats passent par DNS-01 (wildcard oblige), donc aucun challenge HTTP-01 à
préserver aujourd'hui. Si un ClusterIssuer HTTP-01 était ajouté un jour, cette
route attrape-tout l'aveuglerait : il faudrait exclure
/.well-known/acme-challenge/ avant de le mettre en service.
Validation¶
- Hors infra : YAML conforme à l'API Envoy Gateway (EnvoyProxy + parametersRef).
- Live (2026-06-16, critères de l'issue #70) : console AWS = 1 NLB de type
Network (pas de CLB),
curl https://api.devopsyouss.com/healthz/ready→ 200, aucun CLB orphelin. ✅
Date : 2026-06-14 Sprint : 4 Issue : #70