Vos pods restent bloqués en Pending parce que le cluster manque de capacité.
Karpenter détecte ces pods en attente et déclenche le provisionnement en
quelques secondes, mais le temps jusqu'à un nœud Ready dépend du provider
cloud, du bootstrap (OS, runtime, CNI) et des pulls d'images. En pratique,
comptez 1 à 4 minutes selon votre environnement.
Les nœuds sont dimensionnés exactement pour vos pods et disparaissent automatiquement quand ils ne servent plus. Ce guide vous donne le modèle mental pour comprendre comment Karpenter fonctionne, son architecture interne, et pourquoi il surpasse les autoscalers traditionnels.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Comprendre les ressources clés : NodePool, NodeClass, NodeClaim, NodeOverlay
- Maîtriser la Disruption : consolidation, drift, expiration, budgets
- Comprendre le Scheduling : comment Karpenter simule et sélectionne les instances
- Visualiser l'architecture interne de Karpenter et le rôle de chaque contrôleur
- Distinguer ce que fait Karpenter vs ce que fait kube-scheduler
- Comparer Karpenter avec Cluster Autoscaler pour choisir le bon outil
Le problème que Karpenter résout
Section intitulée « Le problème que Karpenter résout »Dans un cluster Kubernetes traditionnel, l'autoscaling des nœuds repose sur le Cluster Autoscaler. Ce dernier ajuste le nombre de nœuds au sein de groupes de nœuds (Node Groups ou Auto Scaling Groups). Cette approche présente plusieurs limitations :
| Limitation | Impact |
|---|---|
| Rigidité | Vous devez créer et maintenir de nombreux groupes de nœuds pour différents types d'instances |
| Lenteur | L'ajout d'un nœud peut prendre plusieurs minutes (négociation avec ASG, attente de scaling) |
| Inefficacité | La sélection du type d'instance est limitée au groupe, pas optimisée pour le pod |
| Complexité | Gérer des centaines de groupes devient vite ingérable |
Karpenter élimine ces contraintes en provisionnant directement les instances sans passer par des groupes intermédiaires. Il analyse les pods en attente, détermine le type d'instance optimal parmi tous ceux disponibles, et crée immédiatement la capacité nécessaire.
Quand utiliser Karpenter
Section intitulée « Quand utiliser Karpenter »Le critère décisif est la variabilité de vos besoins en capacité. Sur un cluster dont les workloads sont homogènes et la charge prévisible, un Cluster Autoscaler avec deux ou trois groupes de nœuds fait le travail sans ajouter de CRD ni de contrôleur à maintenir. Karpenter prend l'avantage dès que le nombre de combinaisons pertinentes (architecture, taille, spot ou on-demand, zone) rend la maintenance des groupes déraisonnable.
- Vos workloads ont des besoins variés (architectures, types d'instances, spot/on-demand)
- Vous voulez une consolidation automatique des nœuds sous-utilisés
- La réactivité compte (pods schedulés en secondes, pas en minutes)
- Vous déployez sur un cloud avec un provider Karpenter disponible
Providers disponibles
Section intitulée « Providers disponibles »Karpenter sépare un cœur générique, maintenu par le projet Kubernetes SIG, et un provider
par cloud qui implémente l'interface CloudProvider. Cette architecture explique la colonne
NodeClass du tableau : chaque provider apporte sa propre CRD de configuration cloud, alors que
NodePool et NodeClaim restent identiques partout. La colonne Maturité mérite votre attention avant
tout choix : un provider en Preview peut voir son API changer sans préavis, ce qui n'est pas
anodin sur un composant qui crée et détruit des machines.
| Provider | Maintenu par | Maturité | NodeClass | Repo |
|---|---|---|---|---|
| AWS | AWS | Production | EC2NodeClass | aws/karpenter-provider-aws |
| Azure/AKS | Microsoft | Production | AKSNodeClass | Azure/karpenter-provider-azure |
| GCP/GKE | CloudPilot AI | Preview | GCPNodeClass | cloudpilot-ai/karpenter-provider-gcp |
| Alibaba Cloud | CloudPilot AI | Preview | ECSNodeClass | cloudpilot-ai/karpenter-provider-alibabacloud |
Les trois ressources fondamentales
Section intitulée « Les trois ressources fondamentales »Karpenter utilise trois Custom Resource Definitions (CRDs) qui travaillent ensemble. Comprendre leur rôle est essentiel pour configurer et dépanner Karpenter.
NodeClass : la configuration cloud
Section intitulée « NodeClass : la configuration cloud »La NodeClass répond à la question : "Comment créer un nœud chez mon provider ?"
Chaque provider cloud a sa propre NodeClass (EC2NodeClass pour AWS,
AKSNodeClass pour Azure). Elle contient :
- Image machine : AMI sur AWS, OMI sur Outscale
- Réseau : subnets, security groups
- IAM/Credentials : rôle pour le nœud
- User-data : script d'initialisation
apiVersion: karpenter.k8s.aws/v1kind: EC2NodeClassmetadata: name: defaultspec: role: "KarpenterNodeRole-my-cluster" amiSelectorTerms: - alias: al2023@latest subnetSelectorTerms: - tags: karpenter.sh/discovery: "my-cluster" securityGroupSelectorTerms: - tags: karpenter.sh/discovery: "my-cluster"Un point de vigilance sur alias: al2023@latest : la documentation du provider AWS déconseille
explicitement latest en production. Chaque publication d'une nouvelle AMI rend vos nœuds
existants « driftés », et Karpenter les remplace en cascade. En production, épinglez une version
d'AMI précise, par exemple al2023@v20240625, et faites du remplacement une opération volontaire.
NodePool : les contraintes de provisionnement
Section intitulée « NodePool : les contraintes de provisionnement »Le NodePool répond à la question : "Quels types de nœuds Karpenter peut-il créer ?"
Il définit :
- Requirements : architectures autorisées, types d'instances, zones de disponibilité
- Taints : restrictions sur les pods pouvant être schedulés
- Limites : capacité maximale du pool (CPU, mémoire)
- Disruption : comment et quand Karpenter peut supprimer des nœuds
apiVersion: karpenter.sh/v1kind: NodePoolmetadata: name: defaultspec: template: spec: nodeClassRef: group: karpenter.k8s.aws # ou karpenter.outscale.com, etc. kind: EC2NodeClass name: default requirements: - key: kubernetes.io/arch operator: In values: ["amd64"] - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand"] limits: cpu: 100 memory: 200Gi disruption: consolidationPolicy: WhenEmptyOrUnderutilized consolidateAfter: 5mNodeClaim : la décision de provisionnement
Section intitulée « NodeClaim : la décision de provisionnement »Le NodeClaim est une ressource interne créée automatiquement par Karpenter. Il représente une "demande de nœud" avec des spécifications précises. Vous ne créez jamais de NodeClaim manuellement.
Un NodeClaim contient :
- Le type d'instance choisi
- La zone de disponibilité
- Le providerID (identifiant cloud de l'instance)
- Les conditions de progression (Launched, Registered, Initialized, Ready)
# Exemple de NodeClaim créé automatiquementapiVersion: karpenter.sh/v1kind: NodeClaimmetadata: name: default-abc123 labels: karpenter.sh/nodepool: defaultspec: requirements: - key: topology.kubernetes.io/zone operator: In values: ["eu-west-2a"] - key: node.kubernetes.io/instance-type operator: In values: ["m5.large"]status: providerID: aws:///eu-west-2a/i-0123456789abcdef0 conditions: - type: Ready status: "True"Architecture interne de Karpenter
Section intitulée « Architecture interne de Karpenter »Karpenter se compose de plusieurs contrôleurs qui travaillent ensemble pour gérer le cycle de vie complet des nœuds.
Provisioner Controller
Section intitulée « Provisioner Controller »Le Provisioner Controller est le cœur du provisionnement. Il surveille en
permanence les pods marqués comme Unschedulable par kube-scheduler et décide
quels nœuds créer.
Flux de travail :
- Observation : écoute les événements pods via l'API Kubernetes
- Batching : regroupe les pods pending pendant ~10 secondes max
(configurable via
--batch-max-duration) - Simulation : simule le scheduling pour déterminer les NodeClaims nécessaires
- Création : crée les ressources NodeClaim correspondantes
Le batching est crucial : au lieu de créer un nœud par pod, Karpenter attend jusqu'à 10 secondes (1s idle par défaut) pour regrouper plusieurs pods sur le même nœud si possible.
Disruption Controller
Section intitulée « Disruption Controller »Le Disruption Controller optimise le cluster en identifiant les nœuds à supprimer ou remplacer. Il opère selon un ordre de priorité :
- Drift : nœuds dont la configuration ne correspond plus au NodePool
- Consolidation : nœuds vides ou sous-utilisés
- Expiration : nœuds ayant dépassé leur durée de vie (
expireAfter)
Types de consolidation :
| Type | Description |
|---|---|
| Empty | Supprime les nœuds sans pods applicatifs (en parallèle) |
| Multi-Node | Migre les pods de plusieurs nœuds vers moins de nœuds |
| Single-Node | Remplace un nœud par un type d'instance moins cher |
Garbage Collection Controller
Section intitulée « Garbage Collection Controller »Le Garbage Collection Controller vérifie périodiquement (~30 secondes) que
chaque NodeClaim correspond à une VM réelle dans le cloud en appelant
CloudProvider.List().
Fonctionnement :
- Appelle
List()qui retourne toutes les VMs gérées par Karpenter - Compare cette liste avec les NodeClaims existants
- Si un NodeClaim n'a pas de VM correspondante → suppression immédiate
Termination Controller
Section intitulée « Termination Controller »Quand un nœud doit être supprimé, le Termination Controller gère la séquence de shutdown gracieux :
- Taint : ajoute
karpenter.sh/disrupted:NoSchedulepour empêcher de nouveaux pods - Drain : évince les pods via l'API Eviction (respecte les PDBs)
- Cleanup : attend que les VolumeAttachments soient supprimés
- Delete : appelle le cloud provider pour terminer l'instance
- Finalize : retire le finalizer pour permettre la suppression de l'objet Node
Qui fait quoi : Karpenter vs kube-scheduler
Section intitulée « Qui fait quoi : Karpenter vs kube-scheduler »Point critique : Karpenter provisionne des nœuds, mais c'est kube-scheduler qui schedule les pods dessus.
-
Un pod est créé → kube-scheduler le marque
Unschedulable(pas de nœud disponible) -
Karpenter observe ce pod Pending et analyse ses contraintes
-
Karpenter crée un NodeClaim → l'API cloud provisionne l'instance
-
Le kubelet s'enregistre → le node rejoint le cluster
-
kube-scheduler (pas Karpenter) schedule le pod sur le nouveau nœud
Cycle de vie d'un NodeClaim
Section intitulée « Cycle de vie d'un NodeClaim »Comprendre les phases d'un NodeClaim est essentiel pour le debugging. Ces conditions sont
cumulatives et se lisent dans kubectl describe nodeclaim <nom> : la condition qui reste à
Unknown désigne l'étape où le nœud s'est arrêté, et donc la couche à interroger. Un blocage sur
Launched renvoie vers l'API du cloud (quota, capacité insuffisante dans la zone), un blocage sur
Registered renvoie vers le bootstrap du kubelet (user-data, réseau, credentials), un blocage sur
Initialized renvoie vers le CNI et les DaemonSets.
| Phase | Condition | Que se passe-t-il ? |
|---|---|---|
| Created | - | Karpenter a détecté des pods pending et créé un objet NodeClaim |
| Launched | Launched=True | Le provider cloud a créé la VM (instance ID connu) |
| Registered | Registered=True | Le kubelet s'est connecté et Karpenter a lié le Node au NodeClaim |
| Initialized | Initialized=True | Le nœud est Ready au sens Kubernetes (CNI, runtime, DaemonSets critiques) |
| Ready | Ready=True | Les taints d'initialisation sont retirées, les pods applicatifs peuvent être schedulés |
Disruption : optimiser et maintenir le cluster
Section intitulée « Disruption : optimiser et maintenir le cluster »La Disruption est le mécanisme par lequel Karpenter supprime ou remplace des nœuds. Elle se divise en deux catégories : les méthodes graceful (respectent les budgets) et les méthodes forceful (drainent immédiatement).
Méthodes graceful (rate-limitées par budgets)
Section intitulée « Méthodes graceful (rate-limitées par budgets) »Ces deux méthodes sont déclenchées par une décision de Karpenter, pas par un événement subi. Comme rien ne presse, elles passent par les disruption budgets : Karpenter n'agit que sur la proportion de nœuds que vous l'autorisez à perturber en même temps. C'est ce qui les rend sûres à laisser actives en production.
| Méthode | Déclencheur | Description |
|---|---|---|
| Drift | NodePool/NodeClass modifiés | Remplace les nœuds dont la config ne correspond plus |
| Consolidation | Sous-utilisation | Réduit les coûts en fusionnant les workloads |
Consolidation
Section intitulée « Consolidation »La consolidation s'exécute selon trois stratégies (dans cet ordre) :
- Empty Node, Supprime les nœuds vides en parallèle
- Multi-Node, Migre les pods de N nœuds vers moins de nœuds
- Single-Node, Remplace un nœud par un type moins cher
apiVersion: karpenter.sh/v1kind: NodePoolspec: disruption: consolidationPolicy: WhenEmptyOrUnderutilized # ou WhenEmpty consolidateAfter: 5m # attendre 5 min avant de considérer le nœudLe drift est le mécanisme qui applique un changement de configuration aux nœuds déjà en service. Sans lui, modifier un NodePool n'affecterait que les nœuds créés ensuite, et votre cluster accumulerait des générations de nœuds divergentes. Karpenter détecte automatiquement si un NodeClaim ne correspond plus à son NodePool ou à sa NodeClass :
- NodePool : changements dans
spec.template.spec.requirements - NodeClass : changements dans
amiSelectorTerms,subnetSelectorTerms,securityGroupSelectorTerms
Les nœuds driftés sont remplacés progressivement selon les budgets. C'est précisément ce qui rend
alias: al2023@latest dangereux en production : une nouvelle AMI publiée par AWS marque d'un coup
tous vos nœuds comme driftés.
Méthodes forceful (non rate-limitées)
Section intitulée « Méthodes forceful (non rate-limitées) »À l'inverse, ces trois méthodes répondent à une contrainte extérieure : le cloud vous reprend l'instance Spot dans deux minutes, ou le nœud a dépassé la durée de vie que vous avez fixée. Attendre un budget n'aurait pas de sens, Karpenter draine donc immédiatement. Conséquence pratique à anticiper : vos PodDisruptionBudgets ne suffisent pas à protéger un service critique de ces événements, seule une répartition sur plusieurs zones et types de capacité le fait.
| Méthode | Déclencheur | Description |
|---|---|---|
| Expiration | expireAfter atteint | Force le remplacement après une durée max |
| Interruption | Spot interruption, maintenance AWS | Évacuation préventive avant interruption cloud |
| Node Auto Repair | Node unhealthy > 30 min | Remplace les nœuds défectueux (alpha) |
apiVersion: karpenter.sh/v1kind: NodePoolspec: template: spec: expireAfter: 720h # 30 jours max terminationGracePeriod: 1h # temps max pour drainerDisruption Budgets
Section intitulée « Disruption Budgets »Un budget limite le nombre de nœuds perturbés simultanément. La valeur peut s'écrire en
pourcentage ou en nombre absolu, et plusieurs budgets coexistent : Karpenter applique
toujours le plus restrictif de ceux qui s'appliquent à l'instant considéré. Le champ reasons
cible une cause précise, ce qui permet d'être tolérant sur la consolidation tout en freinant le
drift, sachant que les seules valeurs acceptées sont Underutilized, Empty et Drifted. Le
troisième budget de l'exemple illustre le cas d'usage le plus courant : nodes: "0" combiné à
schedule et duration gèle toute disruption pendant une fenêtre de maintenance. Si vous ne
déclarez aucun budget, Karpenter en applique un par défaut à 10 %.
apiVersion: karpenter.sh/v1kind: NodePoolspec: disruption: consolidationPolicy: WhenEmptyOrUnderutilized budgets: - nodes: "20%" # max 20% des nœuds en disruption reasons: ["Drifted", "Empty"] - nodes: "5" # plafond absolu - nodes: "0" # bloquer pendant maintenance schedule: "@daily" duration: 30m reasons: ["Underutilized"]Contrôles au niveau pod/nœud
Section intitulée « Contrôles au niveau pod/nœud »Les budgets raisonnent à l'échelle du NodePool. Pour protéger un cas individuel, par exemple un job de migration de base qui ne supporte pas d'être interrompu, l'annotation ci-dessous agit au niveau du pod ou du nœud. Posée sur un pod, elle rend le nœud qui l'héberge non perturbable tant que ce pod tourne, ce qui est la forme la plus utile : la protection disparaît d'elle-même quand le travail est terminé.
| Annotation | Effet |
|---|---|
karpenter.sh/do-not-disrupt: "true" sur pod | Bloque la disruption volontaire du nœud |
karpenter.sh/do-not-disrupt: "true" sur node | Bloque toute disruption volontaire |
Scheduling : sélection des instances
Section intitulée « Scheduling : sélection des instances »Quand Karpenter doit créer un nœud, il simule le scheduling pour choisir le type d'instance optimal.
Processus de sélection
Section intitulée « Processus de sélection »Ce qui distingue Karpenter du Cluster Autoscaler tient dans l'étape 2 : au lieu de choisir un groupe de nœuds préexistant, Karpenter rejoue le scheduling en mémoire sur chaque type d'instance candidat pour savoir combien de pods y tiendraient réellement, en tenant compte des DaemonSets et de la réservation du kubelet. Le scoring de l'étape 3 arbitre ensuite sur le coût horaire. C'est pour cela qu'un NodePool trop restrictif dégrade la qualité des décisions : moins il reste de candidats après filtrage, moins la simulation a de marge.
- Filtrage, Élimine les types incompatibles avec les requirements
- Simulation, Calcule combien de pods pending tiennent sur chaque type
- Scoring, Choisit le type le moins cher qui satisfait les contraintes
- Batching, Regroupe les pods pendant ~10s max (configurable)
Requirements et Well-Known Labels
Section intitulée « Requirements et Well-Known Labels »Les requirements dans le NodePool filtrent les types d'instances disponibles. Ces clés sont des
labels standard de Kubernetes, à l'exception de karpenter.sh/capacity-type qui est propre à
Karpenter : vos pods peuvent donc utiliser les mêmes clés dans leur nodeSelector ou leur
nodeAffinity, et Karpenter en tiendra compte lors de la simulation. Un piège classique consiste à
figer node.kubernetes.io/instance-type sur une seule valeur : vous retrouvez alors la rigidité
d'un groupe de nœuds, sans les bénéfices de Karpenter.
| Label | Description | Exemple |
|---|---|---|
kubernetes.io/arch | Architecture CPU | amd64, arm64 |
kubernetes.io/os | Système d'exploitation | linux, windows |
node.kubernetes.io/instance-type | Type exact | m5.large, c6i.xlarge |
topology.kubernetes.io/zone | Zone de disponibilité | eu-west-1a |
karpenter.sh/capacity-type | Spot ou On-Demand | spot, on-demand |
Priorité des pools (weight)
Section intitulée « Priorité des pools (weight) »Quand plusieurs NodePools peuvent satisfaire un pod, Karpenter utilise le weight : il essaie
d'abord le pool au poids le plus élevé, et bascule sur le suivant si le provisionnement échoue.
L'exemple ci-dessous met en place le schéma le plus répandu, une bascule spot vers on-demand :
tant que le cloud a de la capacité Spot, elle est utilisée ; en cas de refus, le pool
on-demand-fallback prend le relais. Sans ce second pool, vos pods resteraient Pending pendant
une pénurie de capacité Spot.
apiVersion: karpenter.sh/v1kind: NodePoolmetadata: name: spot-poolspec: weight: 100 # Priorité haute → essayé en premier template: spec: requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot"]---apiVersion: karpenter.sh/v1kind: NodePoolmetadata: name: on-demand-fallbackspec: weight: 10 # Priorité basse → fallback template: spec: requirements: - key: karpenter.sh/capacity-type operator: In values: ["on-demand"]NodeOverlay : ajuster prix et capacités (alpha)
Section intitulée « NodeOverlay : ajuster prix et capacités (alpha) »Les décisions de Karpenter valent ce que valent les données d'entrée de sa simulation : le catalogue de types d'instances, leurs capacités et leurs prix, tels que le provider les remonte. Le NodeOverlay est le point d'injection qui permet de corriger ce catalogue quand il ne correspond pas à votre réalité, sans modifier le provider lui-même.
Les NodeOverlays permettent de modifier les informations d'instances que Karpenter utilise pour ses décisions :
- Ajuster les prix pour refléter vos Savings Plans ou contrats
- Ajouter des extended resources non détectées automatiquement
Ajustement des prix
Section intitulée « Ajustement des prix »Karpenter choisit ses instances sur les prix publics à la demande publiés par le provider. Si
vous bénéficiez d'un Savings Plan ou d'un contrat négocié, ces prix sont faux et le scoring
privilégie les mauvais types. Le NodeOverlay corrige la donnée d'entrée de la simulation, il ne
change évidemment rien à votre facture. Deux champs mutuellement exclusifs existent :
priceAdjustment applique un pourcentage ou un montant signé, price remplace la valeur par un
prix absolu. Déclarer les deux fait rejeter la ressource.
apiVersion: karpenter.sh/v1alpha1kind: NodeOverlaymetadata: name: savings-plan-discountspec: requirements: - key: node.kubernetes.io/instance-type operator: In values: ["m5.large", "m5.xlarge", "m5.2xlarge"] priceAdjustment: "-30%" # Réduction de 30% (Savings Plan)Ajout de capacités
Section intitulée « Ajout de capacités »Certaines ressources ne sont pas visibles par Karpenter au moment de la simulation, parce qu'elles
n'apparaissent sur le nœud qu'après le démarrage d'un device plugin. Sans NodeOverlay, un pod qui
demande smarter-devices/fuse reste Pending : Karpenter ne trouve aucun type d'instance capable
de le satisfaire, puisqu'aucun n'annonce cette ressource. Le champ capacity ajoute uniquement
des ressources étendues ; il ne permet pas de mentir sur cpu, memory, ephemeral-storage ou
pods, qui sont explicitement refusés par la validation.
apiVersion: karpenter.sh/v1alpha1kind: NodeOverlaymetadata: name: custom-devicesspec: requirements: - key: node.kubernetes.io/instance-type operator: In values: ["g4dn.xlarge"] capacity: smarter-devices/fuse: "1" custom-hardware/inference-unit: "4"Résolution des conflits
Section intitulée « Résolution des conflits »Le prix étant une valeur unique, deux overlays qui prétendent le modifier ne peuvent pas coexister : il faut départager. La capacité, elle, est une liste de ressources étendues, donc les contributions s'additionnent sans arbitrage. Retenez surtout la règle de départage à poids égal, contre-intuitive : c'est le nom qui vient le plus tard dans l'alphabet qui l'emporte. Nommez donc vos overlays avec un préfixe numérique si vous voulez un ordre lisible.
- weight le plus élevé gagne (valeur entre 1 et 10000, absent équivaut à 0)
- À weight égal, le nom le plus tard dans l'ordre alphabétique gagne
- Les capacity sont fusionnées (pas de conflit)
| Champ | Comportement |
|---|---|
price / priceAdjustment | L'overlay avec le plus haut weight gagne |
capacity | Fusionné entre tous les overlays |
Ce qu'il faut retenir
Section intitulée « Ce qu'il faut retenir »- Karpenter provisionne, kube-scheduler schedule, ne confondez pas les rôles
- NodePool = contraintes (types, arch, limits) ; NodeClass = config cloud (AMI, réseau, IAM)
nodeClassRefcomplet :group,kind,name, les trois sont obligatoires- Toujours définir des
limits, sinon Karpenter peut créer des centaines de nœuds - 4 contrôleurs : Provisioner, Disruption, Termination, Garbage Collection
- Le GC Controller est critique, une erreur dans
List()supprime tous vos nœuds - Disruption graceful (consolidation, drift) respecte les budgets ; forceful (expiration, interruption) non
- NodeOverlay (alpha) permet d'ajuster prix et capacités pour la simulation
D'autres ressources
Section intitulée « D'autres ressources »Ressources
Section intitulée « Ressources »- Documentation officielle Karpenter, Référence complète
- Getting Started AWS, Guide d'installation AWS
- Concepts NodePools, Configuration détaillée
- Disruption, Consolidation, drift, expiration
- FAQ / Compatibility, Matrice de compatibilité Kubernetes
- CloudProvider Interface, API Go v1.9.0