Aller au contenu
English
Cloud medium

Ingress ou Service LoadBalancer : un répartiteur pour tous, ou un par service

Mesuré live le ·fr-par·scw 2.62.0

75 min de lecture

logo Scaleway

Chez Scaleway, chaque Service de type LoadBalancer fait naître un vrai répartiteur facturé 0,023 €/h, qu'aucun de vos manifestes ne nomme. Cette leçon installe un contrôleur Ingress sur Kapsule et compte les Load Balancers réellement créés à chaque étape, au lieu de répéter l'argument d'architecture habituel. Vous verrez qu'un Ingress à deux routes n'en consomme qu'un seul là où deux Services en consomment deux de plus, et vous prouverez le routage par chemin avec un contrôle négatif, parce que deux tentatives naïves ne prouvaient rien.

  • Installer un contrôleur Ingress et compter ce qu'il fait naître côté Scaleway.
  • Chiffrer l'écart entre un Ingress et N Services de type LoadBalancer.
  • Prouver le routage par chemin, contrôle négatif à l'appui.
  • Supprimer dans l'ordre qui ne laisse aucun répartiteur orphelin.
  • Un cluster Kapsule avec au moins deux nœuds : voir créer un cluster Kapsule.
  • kubectl configuré sur ce cluster, jq et la CLI scw 2.62.0.
  • De quoi émettre des requêtes HTTP, curl suffit.

Ingress ou Service LoadBalancer : lequel pour quel besoin ?

Section intitulée « Ingress ou Service LoadBalancer : lequel pour quel besoin ? »

Le critère n'est pas technique, il est budgétaire et opérationnel. Les deux approches exposent un service ; elles ne facturent pas la même chose et ne s'administrent pas au même endroit.

CritèreService type=LoadBalancerIngress
Répartiteurs facturésun par Serviceun pour tous
Niveau de routagepar service et portpar chemin, nom d'hôte et en-tête
Terminaison TLSpossible au Load Balancer, par les annotations scw-loadbalancer-protocol-http et scw-loadbalancer-certificate-idscentralisée au contrôleur, un seul point pour toutes les applications
Quand le choisirun protocole non HTTP, ou un service uniqueplusieurs services HTTP derrière une même entrée

L'Ingress n'est pas toujours le bon choix. Un service qui parle PostgreSQL, SMTP, ou tout protocole TCP ou UDP qui n'est pas du HTTP n'a rien à faire derrière un contrôleur HTTP : le Service de type LoadBalancer est alors la réponse juste, et son coût est le prix de la fonction. gRPC n'entre pas dans cette catégorie : il repose sur HTTP/2 et se route très bien par un contrôleur Ingress qui le prend en charge.

Et la terminaison TLS n'est pas un argument en faveur de l'Ingress. Le cloud controller de Scaleway sait faire terminer TLS par le Load Balancer lui-même, au moyen des annotations scw-loadbalancer-protocol-http, scw-loadbalancer-certificate-ids et scw-loadbalancer-http-backend-tls. Le vrai argument de l'Ingress est la centralisation : un seul point de terminaison pour N applications, au lieu de N certificats à renouveler indépendamment. Inversement, dès que deux applications web cohabitent, chaque Service supplémentaire ajoute un répartiteur, une adresse à documenter et un certificat à gérer.

Combien de Load Balancers chaque approche fait-elle naître ?

Section intitulée « Combien de Load Balancers chaque approche fait-elle naître ? »

Comptez-les à chaque étape plutôt que de raisonner sur le principe. Une seule commande donne la vérité facturée :

Fenêtre de terminal
scw lb lb list zone=fr-par-1 -o json | jq -r 'length'
  1. Deux applications exposées en ClusterIP, le point de départ.

    Fenêtre de terminal
    for app in boutique blog; do
    kubectl create deployment "${app}" \
    --image=nginx:1.29.1@sha256:8adbdcb969e2676478ee2c7ad333956f0c8e0e4c5a7463f4611d7a2e7a7ff5dc
    kubectl expose deployment "${app}" --port=80 --target-port=80 --type=ClusterIP
    done
    scw lb lb list zone=fr-par-1 -o json | jq -r 'length'

    La sortie doit afficher 0. ClusterIP ne facture rien : le service n'existe qu'à l'intérieur du cluster.

  2. Installer le contrôleur Ingress, avec l'annotation qui fera remonter l'adresse du client.

    Fenêtre de terminal
    helm repo add traefik https://traefik.github.io/charts && helm repo update
    helm install traefik traefik/traefik --version 41.5.0 \
    --namespace traefik --create-namespace \
    --set-string 'service.annotations.service\.beta\.kubernetes\.io/scw-loadbalancer-proxy-protocol-v2=true' \
    --set-string 'service.annotations.service\.beta\.kubernetes\.io/scw-loadbalancer-use-hostname=true' \
    --set 'ports.web.proxyProtocol.trustedIPs={0.0.0.0/0}' \
    --set 'ports.web.forwardedHeaders.trustedIPs={0.0.0.0/0}' \
    --wait --timeout 6m

    La sortie doit finir par Install complete. Mesuré le 2026-09-12 : 23 secondes, pour Traefik 3.7.13.

  3. Compter de nouveau.

    Fenêtre de terminal
    scw lb lb list zone=fr-par-1 -o json | jq -r '.[] | "\(.name[0:36]) type \(.type)"'

    La sortie doit afficher une seule ligne, de type lb-s. Le contrôleur expose son propre Service de type LoadBalancer : c'est lui, et lui seul, qui crée un répartiteur.

Un Ingress à plusieurs routes coûte-t-il plus cher ?

Section intitulée « Un Ingress à plusieurs routes coûte-t-il plus cher ? »

Non, et c'est tout l'intérêt. Ajoutez un Ingress qui route vers les deux applications, puis recomptez.

Fenêtre de terminal
cat <<'YAML' | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vitrine
spec:
ingressClassName: traefik
rules:
- http:
paths:
- path: /boutique
pathType: Prefix
backend:
service:
name: boutique
port:
number: 80
- path: /blog
pathType: Prefix
backend:
service:
name: blog
port:
number: 80
YAML
scw lb lb list zone=fr-par-1 -o json | jq -r 'length'

La sortie doit toujours afficher 1. L'Ingress réutilise le répartiteur du contrôleur : ajouter des routes ne crée aucune ressource facturée.

Comparez maintenant avec l'autre architecture :

Fenêtre de terminal
kubectl patch svc boutique -p '{"spec":{"type":"LoadBalancer"}}'
kubectl patch svc blog -p '{"spec":{"type":"LoadBalancer"}}'
sleep 20
scw lb lb list zone=fr-par-1 -o json | jq -r 'length'

La sortie doit afficher 3. Mesuré le 2026-09-12 : les deux répartiteurs supplémentaires sont provisionnés en 5 secondes.

ArchitectureLoad Balancers facturésCoût horaire
2 Services ClusterIP00 €
Contrôleur Ingress seul10,023 €/h
Contrôleur + Ingress à 2 routes10,023 €/h
2 Services type=LoadBalancer en plus30,069 €/h

Remettez les deux Services en ClusterIP pour arrêter les frais :

Fenêtre de terminal
kubectl patch svc boutique -p '{"spec":{"type":"ClusterIP"}}'
kubectl patch svc blog -p '{"spec":{"type":"ClusterIP"}}'

L'écart de trois à un ne se lit pas seulement sur la facture, et c'est ce qui rend l'argument solide au-delà du prix. Chaque Service de type LoadBalancer apporte sa propre adresse publique, donc son propre enregistrement DNS à créer, son propre certificat TLS à obtenir et à renouveler, et sa propre règle de pare-feu à maintenir. Dix services web exposés ainsi, ce sont dix adresses que quelqu'un doit documenter et dix renouvellements de certificat qui peuvent échouer indépendamment. Derrière un Ingress, il y a une adresse, un point de terminaison TLS et un endroit où lire la configuration de routage de toutes les applications. Le gain d'exploitation dépasse largement les 0,046 €/h économisés dans cet exemple, et il croît avec le nombre de services, là où le gain financier, lui, reste linéaire.

L'Ingress route-t-il vraiment vers la bonne application ?

Section intitulée « L'Ingress route-t-il vraiment vers la bonne application ? »

Il faut le prouver, et deux méthodes naïves n'y parviennent pas. Je les donne toutes les deux, parce que l'erreur est instructive.

La première compare les codes HTTP. Sur ce lab, /boutique, /blog et /inconnu renvoyaient tous un 404. Impossible de conclure : soit l'Ingress a routé et c'est le nginx applicatif qui répond 404 faute de fichier à ce chemin, soit il n'a pas routé et c'est son backend par défaut qui répond.

La seconde compare le corps de la réponse, en supposant que le backend par défaut ne ressemblerait pas à un nginx. C'est faux : le backend par défaut d'un contrôleur peut ressembler à celui de vos applications. Avec ingress-nginx, dont le backend par défaut est lui aussi un nginx, la page d'erreur portait exactement la même signature. Le test déclarait le routage correct y compris sur /inconnu, ce qui était absurde.

La méthode qui tranche donne à chaque application un contenu unique, servi exactement au chemin routé :

Fenêtre de terminal
for app in boutique blog; do
pod=$(kubectl get pods -l app="${app}" -o jsonpath='{.items[0].metadata.name}')
kubectl exec "${pod}" -- sh -c \
"mkdir -p /usr/share/nginx/html/${app} && echo 'JE-SUIS-${app}' > /usr/share/nginx/html/${app}/index.html"
done
ADRESSE=$(kubectl get svc -n traefik traefik \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}{.status.loadBalancer.ingress[0].hostname}')
for chemin in boutique/ blog/ inconnu/; do
printf '%-12s %s\n' "/${chemin}" "$(curl -s --max-time 10 "http://${ADRESSE}/${chemin}" | head -1)"
done

La sortie doit afficher trois lignes distinctes :

/boutique/ JE-SUIS-boutique
/blog/ JE-SUIS-blog
/inconnu/ 404 page not found

Deux corps différents sur deux chemins prouvent le routage ; le troisième est le contrôle négatif. Sans lui, un test qui réussit partout ne démontrerait rien du tout.

Comment obtenir un nom plutôt qu'une adresse IP ?

Section intitulée « Comment obtenir un nom plutôt qu'une adresse IP ? »

Par une annotation du cloud controller, sinon vous recevez une IP brute. Le comportement par défaut renseigne status.loadBalancer.ingress[0].ip.

metadata:
annotations:
service.beta.kubernetes.io/scw-loadbalancer-use-hostname: "true"

Préférez le nom. Une adresse IP de Load Balancer est rendue au fournisseur puis réattribuée : un enregistrement DNS qui la cite en dur finit par pointer vers la machine de quelqu'un d'autre. C'est aussi la raison pour laquelle aucune adresse publique réelle ne figure dans les guides de ce site.

Quelle adresse IP votre application voit-elle vraiment ?

Section intitulée « Quelle adresse IP votre application voit-elle vraiment ? »

Elle voit le contrôleur Ingress, jamais votre visiteur, et cela reste vrai même avec le PROXY protocol. C'est le défaut classique d'une chaîne à plusieurs sauts, et il casse silencieusement tout ce qui repose sur l'adresse du client : limitation de débit, filtrage géographique, journaux d'audit, détection d'abus.

Le trafic traverse trois sauts, et chacun ne connaît directement que le précédent :

visiteur -> Load Balancer -> contrôleur Traefik -> application

Le PROXY protocol agit sur le premier saut, en annonçant au contrôleur l'adresse d'origine que le répartiteur connaît. L'en-tête X-Forwarded-For agit sur le dernier, en transmettant cette origine à l'application. Il faut donc les deux, et chacun règle un problème différent.

Trois adresses circulent ainsi, et les confondre est l'erreur courante :

AdresseQui la porteCe que l'application en voit
Adresse du visiteurle client sur Internetrien, sauf réglage explicite
Adresse privée du Load Balancerle répartiteur Scalewayce que le contrôleur voit par défaut
Adresse du pod du contrôleurTraefik dans le clusterce que l'application voit dans remote_addr

Le PROXY protocol résout le premier saut. C'est un court en-tête que le Load Balancer place devant la connexion TCP pour annoncer l'adresse d'origine. Il se demande par une annotation, et le contrôleur doit être configuré pour lui faire confiance, sans quoi il rejette la connexion. Les deux réglages figurent dans la commande d'installation de cette leçon.

Une mesure seule ne prouverait qu'une corrélation. Comparez donc les deux états, en lisant les journaux de l'application :

Fenêtre de terminal
POD=$(kubectl get pods -l app=boutique -o jsonpath='{.items[0].metadata.name}')
curl -s -o /dev/null "http://${ADRESSE}/boutique/"
sleep 3
kubectl logs "${POD}" --tail=1

La sortie doit afficher une ligne de journal se terminant par l'adresse transmise dans X-Forwarded-For. Retirez ensuite l'annotation, attendez que le cloud controller reconfigure le répartiteur, et refaites la même lecture :

Fenêtre de terminal
kubectl annotate svc -n traefik traefik \
'service.beta.kubernetes.io/scw-loadbalancer-proxy-protocol-v2-'
sleep 60
curl -s -o /dev/null "http://${ADRESSE}/boutique/"
sleep 3
kubectl logs "${POD}" --tail=1

Mesuré le 2026-09-12, adresses remplacées par les plages de documentation de la RFC 5737 :

remote_addr vu par l'applicationX-Forwarded-For
Avec l'annotation198.51.100.10, le pod Traefik203.0.113.42, le visiteur
Sans l'annotation198.51.100.10, le pod Traefik198.51.100.20, le Load Balancer

Deux enseignements, et le second surprend plus que le premier.

Le PROXY protocol est bien la cause de ce qui remonte : privé de l'annotation, Traefik ne connaît que le répartiteur et propage cette adresse-là, en toute bonne foi. Rien n'échoue, rien n'alerte, et une limitation de débit posée sur cette valeur limiterait votre propre Load Balancer.

Et surtout, remote_addr ne change pas. Dans les deux cas, l'application voit l'adresse du pod Traefik. La vraie adresse du visiteur arrive par un en-tête, jamais par la connexion : votre application doit être configurée pour lire X-Forwarded-For, et pour ne faire confiance à cet en-tête que s'il vient d'un intermédiaire de confiance.

L'ordre est impératif : ce qui crée un répartiteur part avant le cluster. Supprimer le cluster d'abord laisse les Load Balancers orphelins, sans personne pour les réclamer, et leur facturation continue.

  1. Supprimer l'Ingress et les charges.

    Fenêtre de terminal
    kubectl delete ingress vitrine --ignore-not-found=true
    kubectl delete deployment boutique blog --ignore-not-found=true
    kubectl delete svc boutique blog --ignore-not-found=true
  2. Supprimer les Services de type LoadBalancer, et eux seuls.

    Fenêtre de terminal
    kubectl get svc --all-namespaces -o json \
    | jq -r '.items[] | select(.spec.type == "LoadBalancer")
    | "\(.metadata.namespace) \(.metadata.name)"' \
    | while read -r ns nom; do
    echo "suppression de ${ns}/${nom}"
    kubectl delete svc -n "${ns}" "${nom}"
    done

    Sélectionnez sur le type, jamais sur l'emplacement. Une commande qui supprimerait « tous les Services sauf ceux de default » emporterait aussi kube-system/coredns, et casserait la résolution DNS de tout le cluster. Seul un Service de type LoadBalancer provisionne une ressource facturée : c'est lui qu'un nettoyage doit viser.

  3. Attendre la libération effective des répartiteurs.

    Fenêtre de terminal
    until [ "$(scw lb lb list zone=fr-par-1 -o json | jq -r 'length')" = "0" ]; do
    printf '.'; sleep 10
    done
    echo " tous les Load Balancers sont libérés"

    La sortie doit finir par la phrase de confirmation. Ne passez pas à la suite avant ce zéro : la suppression est asynchrone, et « j'ai demandé » n'est pas « c'est fait ».

  4. Supprimer le namespace du contrôleur.

    Fenêtre de terminal
    helm uninstall traefik --namespace traefik
    kubectl delete namespace traefik --ignore-not-found=true
    scw lb lb list zone=fr-par-1 -o json | jq -r 'length'

    La dernière sortie doit afficher 0.

PlafondValeurSource
Prix d'un Load Balancer lb-s0,023 €/hgrille réseau officielle, 2026-09-12
Bande passante d'un lb-s200 Mbit/sgrille réseau officielle, 2026-09-12
Zones sans Load Balancerfr-par-3aide de scw lb lb create, 2026-09-12
Type créé par le cloud controllerlb-s par défautscw lb lb list, mesuré le 2026-09-12
Nom des Load Balancers crééspréfixé par l'identifiant du clusterscw lb lb list, mesuré le 2026-09-12

Le préfixe du nom n'est pas un détail cosmétique : c'est lui qui permet à scw k8s cluster delete with-additional-resources=true de reconnaître les répartiteurs à emporter avec le cluster.

SymptômeCauseSolution
status.loadBalancer reste videle cloud controller n'a pas fini de provisionnerattendre ; vérifier kubectl describe svc pour les événements du CCM
L'adresse répond alors qu'aucune route ne correspondc'est le backend par défaut du contrôleur qui répond 404ajouter un contrôle négatif à vos tests pour le distinguer
Un Load Balancer subsiste après la suppression du clusterle Service n'a pas été supprimé avant le clusterle supprimer à la main avec scw lb lb delete
kubectl get svc affiche une IP, pas un nomcomportement par défaut du cloud controllerannoter avec service.beta.kubernetes.io/scw-loadbalancer-use-hostname

Ces quatre erreurs partagent une racine : elles traitent le répartiteur comme un objet Kubernetes, alors que c'est une ressource facturée chez l'hébergeur.

AntipatternConséquenceDiscipline
Un Service de type LoadBalancer par application webautant de répartiteurs, d'adresses et de certificats que d'applicationsun contrôleur Ingress, une adresse, un point de terminaison TLS
Supprimer le cluster avant les Servicedes répartiteurs orphelins continuent d'être facturéssupprimer les Service, attendre le zéro, puis le cluster
Nettoyer en ciblant un namespace plutôt qu'un typekube-system/coredns disparaît, le DNS du cluster tombesélectionner sur spec.type == "LoadBalancer"
Coder en dur l'adresse IP du répartiteurl'adresse est recyclée et pointe un jour ailleursutiliser le nom, via l'annotation du cloud controller

Combien de répartiteurs payez-vous en ce moment, et pourquoi ? La réponse ne se lit pas dans vos manifestes, puisque le cloud controller les crée sans qu'aucun ne les nomme.

Discipline : compter régulièrement avec scw lb lb list, confronter ce nombre à la liste des Service de type LoadBalancer, et rapatrier derrière un Ingress tout ce qui parle HTTP.

Que devient votre trafic si le contrôleur tombe ? Mutualiser une entrée, c'est aussi mutualiser une panne : un contrôleur unique est un point de défaillance pour toutes les applications qu'il sert.

Discipline : exécuter plusieurs répliques du contrôleur, réparties sur des nœuds distincts, et leur poser un PodDisruptionBudget. L'économie d'un répartiteur ne doit pas se payer d'une indisponibilité générale.

Vos tests d'exposition distinguent-ils une route correcte d'une réponse d'erreur générique ? Sur ce lab, deux méthodes de vérification concluaient juste pour de mauvaises raisons.

Discipline : donner à chaque destination une réponse identifiable, inclure un contrôle négatif sur un chemin non routé, et se demander avant chaque assertion ce qui différerait si la chose testée ne fonctionnait pas.

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

  • Un Service de type LoadBalancer crée un répartiteur facturé 0,023 €/h, absent de tous vos manifestes.
  • ClusterIP ne facture rien : c'est le point de départ de toute comparaison.
  • Un contrôleur Ingress crée un seul répartiteur, quel que soit le nombre de routes. Mesuré le 2026-09-12 : 0 avec deux ClusterIP, 1 avec le contrôleur, 1 encore avec un Ingress à deux routes, 3 dès que deux Services passent en LoadBalancer.
  • Le cloud controller crée des lb-s, nommés avec le préfixe de l'identifiant du cluster.
  • Traefik 3.7.13 est prêt en 23 secondes ; ingress-nginx est archivé depuis le 2026-03-24 et ne doit plus être enseigné.
  • Une annotation passée par Helm exige --set-string : avec --set, true devient un booléen et la pose échoue.
  • scw-loadbalancer-proxy-protocol-v2 fait remonter la vraie adresse du client dans X-Forwarded-For ; sans elle, c'est l'adresse privée du Load Balancer qui arrive.
  • Prouvez le routage avec un contenu unique par destination et un contrôle négatif sur un chemin non routé.
  • Nettoyez en sélectionnant sur spec.type, jamais sur le namespace, sous peine de supprimer coredns.
  • Les Service partent avant le cluster, sinon les répartiteurs restent orphelins et facturés.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn