$ kubectl get hpa traitement-documents
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
traitement-documents Deploy/... 12%/70% 2 20 2
Douze pour cent de processeur, deux réplicas, tout va bien. Pendant ce temps, la file d'attente contient huit mille messages et le plus ancien date de quarante minutes.
L'autoscaler natif de Kubernetes fait exactement ce qu'on lui a demandé : il regarde le processeur et la mémoire des pods. Or un consommateur de file passe l'essentiel de son temps à attendre des entrées et sorties, base de données, stockage, appel réseau. Il est lent sans être occupé. La charge réelle n'est pas dans le pod, elle est dans la file, et Kubernetes ne sait pas la voir.
Ce que KEDA ajoute, et ce qu'il ne remplace pas
KEDA est un composant de mise à l'échelle événementielle pour Kubernetes, sous licence Apache 2.0, aujourd'hui projet gradué de la CNCF.
Le malentendu fréquent est de croire qu'il remplace l'autoscaler horizontal natif. Il ne le remplace pas, il l'alimente. KEDA interroge une source externe, une file de messages, une base, une requête de métrique, et publie le résultat comme une métrique que l'autoscaler natif consomme. La mise à l'échelle reste faite par Kubernetes ; KEDA lui donne simplement un chiffre qu'il n'aurait pas su aller chercher.
Deux conséquences pratiques. La première : tout ce que vous savez du comportement de l'autoscaler natif reste vrai, y compris ses délais de stabilisation. La seconde : KEDA fait une chose que l'autoscaler natif ne sait pas faire, descendre à zéro réplica. C'est même sa fonction la plus intéressante, et la plus piégeuse.
Le déclencheur, en pratique
La configuration prend la forme d'un objet qui désigne une charge de travail et une ou plusieurs sources à surveiller.
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: traitement-documents
spec:
scaleTargetRef:
name: traitement-documents
minReplicaCount: 0
maxReplicaCount: 20
pollingInterval: 15
cooldownPeriod: 300
triggers:
- type: rabbitmq
metadata:
queueName: documents
mode: QueueLength
value: '50'
authenticationRef:
name: rabbitmq-auth
La lecture est directe : un réplica pour cinquante messages en attente, jusqu'à vingt réplicas, et retour à zéro après cinq minutes de file vide. Le catalogue de sources couvre les courtiers de messages habituels, les bases, les stockages objet et les requêtes Prometheus, ce dernier cas permettant de déclencher sur n'importe quelle métrique déjà collectée.
L'authentification aux sources se déclare séparément, ce qui évite de recopier des identifiants dans chaque objet et permet de les tirer d'un coffre externe. C'est un détail d'ergonomie qui devient important dès qu'on dépasse une dizaine de charges.
La remise à zéro, et ce qu'elle coûte vraiment
Descendre à zéro réplica est séduisant : un traitement qui ne tourne que deux heures par jour ne consomme rien les vingt-deux autres. Sur un parc de vingt traitements de ce genre, le gain est réel.
Il a un prix, le démarrage à froid, et ce prix se mesure. Le temps réel entre l'arrivée du premier message et son traitement additionne quatre choses : le délai de scrutation de la source, quinze secondes dans l'exemple ci-dessus ; l'ordonnancement du pod ; le téléchargement de l'image si elle n'est pas déjà présente sur le noeud ; et le démarrage de l'application elle-même.
Pour un service applicatif compact avec une image en cache locale, on parle de quelques secondes. Pour une application sur machine virtuelle Java, avec une image d'un gigaoctet à télécharger sur un noeud qui vient d'être créé, on parle de plusieurs minutes. Un traitement par lots s'en accommode parfaitement. Un service qui répond à un utilisateur derrière un navigateur, jamais.
La règle qui évite les mauvaises surprises : la remise à zéro pour les traitements asynchrones, un minimum d'un réplica pour tout ce qui a un humain au bout. Et quand la remise à zéro est retenue, l'image doit être légère et préchargée, faute de quoi le gain financier se paie en latence perçue.
L'oscillation, le défaut classique
Une mise à l'échelle mal réglée passe son temps à créer et détruire des pods. Le symptôme se lit dans les événements du namespace, où les créations et suppressions se succèdent toutes les deux minutes.
Trois causes se combinent généralement. Un seuil trop bas, qui fait réagir à la moindre variation. Une période de refroidissement trop courte, qui autorise la descente avant que la file suivante n'arrive. Et un traitement long, où les pods sont supprimés alors qu'ils travaillent encore.
Ce dernier point mérite une attention particulière, parce qu'il ne relève pas de KEDA mais de l'application. Un pod qui reçoit l'ordre de s'arrêter doit finir le message en cours, refuser d'en prendre un nouveau, puis sortir. Cela suppose que le message ne soit acquitté qu'après traitement complet, et que le délai de grâce à l'arrêt soit supérieur à la durée du traitement le plus long. Sans ces deux conditions, chaque descente en charge perd des messages, silencieusement, et le problème sera attribué à l'outil de mise à l'échelle alors qu'il vient du code.
Ce qu'il faut superviser
Trois signaux suffisent, et ils ne sont pas ceux qu'on surveille d'habitude.
La profondeur de la file et l'âge du plus ancien message, qui disent si le système tient. Une file qui reste stable à huit mille messages avec vingt réplicas signifie que le plafond est atteint et que le débit unitaire est le vrai problème.
Le nombre de réplicas dans le temps, qui révèle l'oscillation d'un coup d'oeil sur un graphique.
Et l'état de l'objet de mise à l'échelle lui-même, parce qu'un déclencheur qui n'arrive plus à interroger sa source, identifiants expirés ou courtier injoignable, cesse simplement de produire des métriques. Le comportement dans ce cas est le pire possible : rien ne se passe, rien ne remonte en erreur, et la file grossit. La version 2.20.2 a d'ailleurs introduit une condition dédiée sur l'objet, qui reflète l'état de l'autoscaler sous-jacent et évite qu'une interruption passagère de métrique ne fasse basculer l'état général. C'est exactement le genre de signal à remonter dans une chaîne d'alerte, et il vaut mieux l'alerter sur l'absence de données que sur un seuil.
Où ça se place, et quand s'en passer
KEDA trouve sa place dès qu'une charge asynchrone existe : traitement de documents, encodage, génération de rapports, indexation, appels à un service d'inférence dont la demande arrive par rafales. Sur ce dernier cas, il complète directement un serveur de modèles, dont la charge est typiquement irrégulière.
Il ne sert à rien sur une charge régulière et prévisible, où deux réplicas fixes coûtent moins cher en complexité qu'un mécanisme de plus à comprendre lors d'un incident. Il ne sert à rien non plus si le goulot d'étranglement est ailleurs : multiplier par dix les consommateurs d'une file quand la limite réelle est le nombre de connexions à la base ne fait que déplacer la panne, et l'aggrave en saturant le répartiteur de connexions.
La question à se poser avant d'installer quoi que ce soit reste donc la même : où est réellement la contention ? Si la réponse est « dans la file et dans les consommateurs », KEDA est l'outil juste. Si la réponse est ailleurs, il ne fera qu'ajouter une pièce mobile à un système qui en a déjà assez.
Sources
- Dépôt GitHub kedacore/keda, licence Apache 2.0, mise à l'échelle événementielle pour Kubernetes, 10 540 étoiles au 21 septembre 2026
- Notes de version KEDA v2.20.2, publiée le 31 juillet 2026, condition HPAActive et corrections de stabilité
- Documentation KEDA, ScaledObject, champs de configuration, intervalle de scrutation et période de refroidissement
- Documentation Kubernetes, autoscaler horizontal, mécanisme sous-jacent alimenté par KEDA


