El pasado 24 de Octubre dí la charla "Karpenter en el mundo cloud native" en KubeFest #3, un evento organizado por la comunidad Platform Engineering Perú. Me gustó mucho el ambiente: habían personas que operan Kubernetes en su trabajo y al final salieron preguntas muy buenas.
Quería dejar por escrito lo que conté, para quien no pudo ir o para quien quiera revisar los ejemplos con más calma.
Todo empieza con un pod en Pending
¿A quién no le ha pasado?. Haces el deploy, parece que todo está bien, y un pod se queda en Pending. Revisas el clúster, hay nodos, hay recursos libres... y el pod sigue sin arrancar.
Pending quiere decir que el scheduler todavía no encuentra un nodo donde ponerlo. Lo que confunde es que el scheduler no mira el espacio libre total del clúster, mira nodo por nodo. Si un pod pide 2 CPU y tienes tres nodos con 1 CPU libre cada uno, en total hay 3 CPU libres, pero el pod no entra en ninguno. Y a eso súmale que puede necesitar una zona específica, una arquitectura o un tipo de nodo en particular.
$ kubectl describe pod web-6f9c
Events:
Type Reason Message
Warning FailedScheduling 0/3 nodes are available: 3 Insufficient cpu.
Algo super importante: si realmente falta capacidad, un autoscaler puede crear nodos. Pero si el problema está en la configuración del pod, por más nodos que agregues va a seguir esperando.
¿Y dónde entra Karpenter?
Karpenter vive al costado del clúster. Por un lado está pendiente de la API de Kubernetes para detectar pods que no se pueden programar. Por el otro, le pide máquinas nuevas a Amazon EC2.
Algo que quise recalcar durante la charla: Karpenter no reemplaza al scheduler. Él consigue las máquinas, pero quien decide en qué nodo va cada pod sigue siendo Kubernetes.
Antes: Cluster Autoscaler y sus grupos
Antes de Karpenter, lo más común en AWS era usar Cluster Autoscaler. Funciona con grupos de nodos que defines de antemano (en EKS son Auto Scaling Groups), cada uno con su tipo de instancia y su mínimo y máximo. Por ejemplo, un grupo general con m5.large, otro de memoria con r5.xlarge, otro con GPU, y así.
Cuando ve pods pendientes, se pregunta si agregando un nodo a alguno de esos grupos el pod entraría. Si la respuesta es sí, escala ese grupo.
Cluster Autoscaler funciona bien y es muy usado. El tema es cuando tienes muchas aplicaciones con necesidades distintas: cada combinación de tamaño, arquitectura o GPU termina siendo un grupo más, y cada grupo hay que configurarlo y mantenerlo. Ahí surge la pregunta que da pie a nuestro tema central: ¿y si en vez de preparar grupos, la capacidad se eligiera según lo que piden los pods?
Lo que propone Karpenter
Karpenter le da la vuelta. La aplicación dice lo que necesita (memoria, arquitectura, si puede correr en Spot) y nosotros definimos en un NodePool qué tipos de instancias permitimos y con qué límites. Con eso, más lo que AWS tenga disponible en ese momento, Karpenter lanza el nodo que hace falta.
Ahora, tampoco es magia. No siempre va a elegir la máquina más barata y no puede garantizar que una instancia esté disponible, tambien depende mucho de tu configuración definida en el NodePool. Y no creo que una herramienta sea mejor que la otra en todos los casos: depende de tu infraestructura, y conviene medir antes y después, porque el ahorro no viene solo.
Un poco de contexto: Karpenter nació en AWS en 2021 como proyecto open source. En 2023 sus APIs pasaron a beta y la parte neutral del proyecto se donó a la CNCF, dentro de Kubernetes SIG Autoscaling. La versión 1.0, que estabiliza las APIs principales, salió en agosto de 2024.
Por dentro
El flujo es más o menos así: hay un pod sin asignar, Karpenter mira sus requests y restricciones, crea un NodeClaim, se lanza la instancia en EC2, el nodo se registra en el clúster y recién ahí el scheduler le asigna el pod, que pasa a Running.
Tambien hay que considerar que esto toma su tiempo, que la instancia ya exista no significa que esté lista, todavía tiene que arrancar y unirse al clúster.
Cómo elige la máquina
Karpenter no lanza un nodo por cada pod que llega. Primero espera alrededor de un segundo sin pods nuevos (hasta diez segundos como máximo) para juntarlos y decidir en lote. Después suma lo que piden en CPU, memoria y GPU, sin olvidarse de los DaemonSets ni de lo que reserva el sistema en cada nodo.
Con eso arma una lista de hasta 60 tipos de instancia que sirven, desde el más chico donde entra todo hasta otros más grandes, y se la pasa a EC2 Fleet para que lance una. En On-Demand se prioriza el precio más bajo y en Spot se combina el precio con el riesgo de interrupción.
Si EC2 no tiene capacidad de algún tipo en una zona, Karpenter lo descarta por un rato y sigue con los demás. Por eso, mientras más tipos de instancia permitas, más alternativas tiene. Limitarlo a una o dos familias puede parecer más seguro, pero en la práctica lo vuelve más frágil, sobre todo con Spot.
NodePool, EC2NodeClass y NodeClaim
Estos tres son los que más se confunden, así que les dediqué un buen rato en la charla.
El NodePool son las reglas: qué nodos puede crear Karpenter, con qué arquitecturas, categorías, si son Spot u On-Demand, los límites y cómo maneja la disrupción. La EC2NodeClass es la parte de AWS: la imagen, las subredes, los security groups y el rol de la instancia. Y el NodeClaim es la solicitud concreta de una máquina. Ese no lo escribes tú, lo crea Karpenter, y es lo primero que yo reviso cuando algo no funciona.
Un NodePool sencillo se ve así:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: general
spec:
template:
spec:
nodeClassRef: # 1. enlaza con la EC2NodeClass
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
requirements:
- key: kubernetes.io/arch # 2. arquitectura permitida
operator: In
values: ["amd64"]
- key: karpenter.sh/capacity-type # 3. solo On-Demand
operator: In
values: ["on-demand"]
- key: karpenter.k8s.aws/instance-category # 4. cómputo, general, memoria
operator: In
values: ["c", "m", "r"]
limits:
cpu: "100" # 5. tope de capacidad del NodePool
Hay que encontrar un equilibrio: si restringes demasiado, Karpenter se queda sin opciones, y si abres mucho, toca validar bien qué tipos van a aparecer. Y el NodePool solo no alcanza, también necesitas la EC2NodeClass, los permisos y la red bien configurados.
Leer un NodeClaim
$ kubectl get nodeclaims
NAME TYPE CAPACITY ZONE NODE READY
general-x7k2p m6i.large on-demand us-east-1a ip-10-0-1-23 True
general-b4m9t c6i.xlarge spot us-east-1b ip-10-0-2-87 True
Cada NodeClaim pasa por cuatro condiciones: Launched cuando EC2 aceptó la solicitud, Registered cuando el nodo se unió al clúster, Initialized cuando terminó de arrancar y quitó sus taints de inicio, y Ready cuando ya se le pueden asignar pods. Si un pod sigue en Pending, fíjate cuál de esas condiciones no llegó a True. Y si ni siquiera aparece un NodeClaim, el problema está antes: en el NodePool o en los requests del pod.
Si lo quieres instalar en EKS
Esto es lo que necesitas tener listo:
- Los permisos de IAM: el rol del controlador (con EKS Pod Identity o IRSA) y el rol de los nodos.
- La etiqueta
karpenter.sh/discoveryen las subredes y security groups que va a usar. - Una cola de interrupciones con SQS y EventBridge, para que reciba los avisos de Spot y de mantenimiento.
- Un lugar donde correr el controlador que no sean los nodos que él mismo administra, por ejemplo un managed node group pequeño o Fargate.
- Al menos un NodePool y una EC2NodeClass. Sin eso no crea ningún nodo.
helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter \
--version "${KARPENTER_VERSION}" \
--namespace kube-system \
--set settings.clusterName=${CLUSTER_NAME} \
--set settings.interruptionQueue=${CLUSTER_NAME}
Mi consejo es seguir la guía oficial de getting started para la versión exacta que vas a instalar, porque los detalles cambian entre versiones. De hecho es la misma guía que seguí para la Demo mostrada en la charla.
Los requests importan más de lo que parece
Karpenter decide qué máquina lanzar sumando los requests de los pods. Y ojo que request, uso real y límite no son lo mismo: el request es lo que reserva el scheduler, el uso real es lo que la app consume en ese momento y el límite es el tope.
Si pones requests muy altos, vas a terminar con nodos más grandes o más nodos de los que necesitas. Si los pones muy bajos, se te llenan los nodos de pods que después compiten por recursos cuando sube la carga. Karpenter trabaja con lo que declaras, no puede adivinar lo que tu aplicación necesita de verdad, así que ajustar los requests con métricas reales sigue siendo trabajo nuestro.
Consolidación
Karpenter no solo agrega capacidad, también la saca cuando sobra.
Si los pods de varios nodos con poco uso entran en menos nodos, los reacomoda y elimina los que quedan sobrando. Esto se configura en el NodePool:
spec:
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 5m
Con WhenEmptyOrUnderutilized retira los nodos vacíos y también los que están poco usados, siempre que sus pods entren en otro lado o en un nodo más barato. Ahorras más, pero se mueven más pods. Con WhenEmpty solo retira nodos que no tienen pods de aplicación (los DaemonSets no cuentan), así que ahorras menos pero no desaloja nada. Y consolidateAfter es cuánto espera desde que el último pod entró o salió del nodo antes de considerarlo, para no reaccionar a cada pico.
Antes de sacar un nodo, Karpenter verifica que los pods entren en lo que queda, respetando sus restricciones y sus PodDisruptionBudgets. Si recién estás empezando, yo arrancaría con WhenEmpty o con un consolidateAfter más largo, y vería cuántos pods se mueven antes de ponerlo más agresivo.
¿Spot u On-Demand?
On-Demand se paga a tarifa normal y es más predecible, aunque tampoco es infalible. Spot puede salir mucho más barato, pero no siempre hay disponibilidad y AWS puede recuperar la instancia avisándote con unos dos minutos de anticipación.
Antes de meter Spot, revisa que tengas varias réplicas, reintentos, el estado y los datos protegidos, muchos tipos de instancia permitidos, y que hayas probado una interrupción de verdad. Yo lo decidiría según qué tan crítica es la aplicación y cuánto riesgo estás dispuesto a aceptar, no solo por el precio por hora.
Cuando Karpenter retira nodos
Un nodo puede salir por consolidación, por drift (cuando ya no coincide con la configuración, por ejemplo porque hay una AMI nueva), porque llegó a su expireAfter o por una interrupción de Spot. Para protegerte hay algunas herramientas, aunque cada una tiene su alcance.
El PodDisruptionBudget va en tu aplicación:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: web
Los disruption budgets van en el NodePool y controlan cuántos nodos se pueden retirar a la vez y en qué horario:
spec:
disruption:
budgets:
- nodes: "10%" # como máximo 10 % de los nodos a la vez
- nodes: "0" # ninguno en este horario...
schedule: "0 9 * * mon-fri" # ...de lunes a viernes desde las 9:00
duration: 8h # durante 8 horas
Y si hay un pod (o un nodo) que no quieres que Karpenter toque, puedes usar esta anotación:
metadata:
annotations:
karpenter.sh/do-not-disrupt: "true"
Cuidado con el PDB: si lo haces muy estricto, por ejemplo con minAvailable igual al número de réplicas, puede bloquear la consolidación indefinidamente. Y ninguna de estas protecciones te salva de una interrupción de Spot o de una falla, así que la aplicación igual tiene que aguantar perder un nodo.
La pregunta que le dejé al público fue: ¿qué impacto tiene esa acción en mi aplicación, y cómo compruebo que se recupera?
Para cerrar
Si me quedo con algo de esta charla es que Karpenter consigue capacidad según lo que realmente piden tus pods y la devuelve cuando ya no hace falta, que entender bien la diferencia entre NodePool, EC2NodeClass y NodeClaim te ahorra mucho tiempo al depurar, y que bajar costos nunca debería ir en contra de la disponibilidad.
¿Vale la pena usarlo? Si tus cargas cambian bastante, si necesitas muchos tipos de instancia o si mantener grupos de nodos ya se volvió un dolor de cabeza, probablemente sí. Pero úsalo porque resuelve un problema que puedes medir, no porque esté de moda.
Gracias a Platform Engineering Perú por la invitación y por organizar KubeFest #3, y a todos los que fueron y se quedaron a preguntar.
Aquí les dejo los slides de la presentación: https://speakerdeck.com/bnktch13/karpenter-en-el-mundo-cloud-native






Top comments (0)