Automatisch schalen in Kubernetes: HPA, VPA, KEDA en Karpenter
Een van de belangrijkste voordelen van Kubernetes in de productie is de mogelijkheid om automatisch te schalen werklasten als reactie op de vraag. Toch gebruiken de meeste teams slechts een fractie van de beschikbare automatische schalingsmogelijkheden: ze configureren een HPA met CPU-schaling en laten het daarbij. Het resultaat? Te weinig ingerichte peulen die langzamer worden onder belasting, of te veel ingerichte knooppunten ze verbranden budgetten zonder enige reden.
Kubernetes biedt vier complementaire niveaus van automatisch schalen: HPA schaal horizontaal de Pods op CPU/geheugen/aangepaste statistieken, VPA repareren automatisch aanvragen van bronnen, KEDA schakel gebeurtenisgestuurde schaling in elke bron (wachtrijen, databases, Prometheus-statistieken), e Karpenter Richt knooppunten in minder dan 30 seconden in, 40% sneller dan traditionele Cluster Autoscaler volgens CNCF 2025-benchmarks. Dit artikel laat zien hoe u ze samen in de productie kunt gebruiken.
Wat je gaat leren
- Hoe de Horizontal Pod Autoscaler (HPA) werkt met aangepaste en externe statistieken
- Configureer de Vertical Pod Autoscaler (VPA) voor automatische rechten
- KEDA: gebeurtenisgestuurde automatische schaling op SQS-, Kafka-, Redis- en Prometheus-wachtrijen
- Karpenter: Just-in-time node-provisioning met NodePool en NodeClass
- Combinatiepatroon: Gebruik HPA en KEDA samen zonder conflicten
- Problemen oplossen: waarom uw HPA niet schaalt zoals u verwacht
- Beste praktijken om flaplussen en koude starts te voorkomen
Horizontale Pod Autoscaler (HPA)
De HPA- en Kubernetes-component die het aantal replica's van een implementatie schaalt, StatefulSet o ReplicaSet gebaseerd op waargenomen statistieken. De HPA-controller vraagt elke keer de statistieken op 15 seconden (configureerbaar) en bereken het gewenste aantal replica's met de formule:
desiredReplicas = ceil(currentReplicas * (currentMetricValue / desiredMetricValue))
Om klapperen (continu op- en afschalen) te voorkomen, kent de HPA een stabilisatieperiode: Standaard 5 minuten voor schalen en 0 seconden voor schalen.
HPA op CPU en geheugen
De basisconfiguratie met CPU en geheugen. Houd er rekening mee dat om naar het geheugen te schalen, de applicatie moet geheugen vrijmaken wanneer de belasting afneemt, anders vindt het terugschalen nooit plaats:
# hpa-basic.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60 # scala quando CPU media > 60%
- type: Resource
resource:
name: memory
target:
type: AverageValue
averageValue: "512Mi" # scala quando memoria media > 512Mi
behavior:
scaleUp:
stabilizationWindowSeconds: 0 # scala su immediatamente
policies:
- type: Percent
value: 100
periodSeconds: 60 # max raddoppio delle repliche al minuto
- type: Pods
value: 4
periodSeconds: 60 # o max 4 pod al minuto
selectPolicy: Max # usa la policy piu aggressiva
scaleDown:
stabilizationWindowSeconds: 300 # 5 minuti prima di scalare giu
policies:
- type: Percent
value: 25
periodSeconds: 60 # riduce max 25% delle repliche al minuto
selectPolicy: Min
HPA met aangepaste statistieken via Prometheus-adapter
Om te schalen op applicatiestatistieken (verzoeken per seconde, wachtrijlengte, enz.), hebt u de Prometheus Adapter stelt Prometheus-statistieken beschikbaar als Custom Metrics Kubernetes API:
# Installa Prometheus Adapter
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install prometheus-adapter prometheus-community/prometheus-adapter \
--namespace monitoring \
--set prometheus.url=http://kube-prometheus-stack-prometheus.monitoring.svc \
--set prometheus.port=9090
# prometheus-adapter-config.yaml - regola di mapping metrica
rules:
custom:
- seriesQuery: 'http_requests_total{namespace!="",pod!=""}'
resources:
overrides:
namespace: {resource: "namespace"}
pod: {resource: "pod"}
name:
matches: "^(.*)_total$"
as: "${1}_per_second"
metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)'
---
# hpa-custom-metric.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server-rps-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 2
maxReplicas: 50
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "1000" # 1000 req/s per Pod
HPA met externe statistieken
Met externe statistieken kunt u schalen naar bronnen buiten het cluster, zoals lengte van een SQS-wachtrij of het aantal niet-geconsumeerde Kafka-berichten:
# hpa-external-metric.yaml
# Scala in base alla lunghezza di una coda SQS
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: worker-queue-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: queue-worker
minReplicas: 1
maxReplicas: 100
metrics:
- type: External
external:
metric:
name: sqs_approximate_number_of_messages_visible
selector:
matchLabels:
queue: "job-queue-prod"
target:
type: AverageValue
averageValue: "10" # 10 messaggi per worker
# Verifica stato HPA
kubectl get hpa -n production -w
kubectl describe hpa api-server-hpa -n production
Verticale Pod Autoscaler (VPA)
VPA bewaakt het daadwerkelijke CPU- en geheugengebruik van uw Pods en past dit automatisch aan
ik resources.requests e limits. En de oplossing voor het probleem van
"garbage in, garbage out" van resourceverzoeken: als u niet weet hoeveel resources de uwe nodig hebben
Pod, de VPA zoekt het voor je uit.
VPA en HPA: pas op voor conflicten
Gebruik VPA niet in de modus Auto samen met HPA-schaling naar CPU of geheugen:
de twee controllers zullen conflicteren. De juiste combinatie is: VPA in modus
Off o Initial voor resourceaanvragen en HPA voor replicaties
op aangepaste statistieken. Of gebruik KEDA in plaats van HPA om het probleem te voorkomen.
VPA-installatie en configuratie
# Installa VPA
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler
./hack/vpa-install.sh
# oppure con Helm
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm install vpa fairwinds-stable/vpa --namespace vpa --create-namespace
---
# vpa-recommendation.yaml - modalita Off (solo raccomandazioni)
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-server-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
updatePolicy:
updateMode: "Off" # Off|Initial|Recreate|Auto
resourcePolicy:
containerPolicies:
- containerName: api-server
minAllowed:
cpu: "100m"
memory: "128Mi"
maxAllowed:
cpu: "4"
memory: "4Gi"
controlledResources: ["cpu", "memory"]
controlledValues: RequestsAndLimits
# Leggi le raccomandazioni VPA
kubectl describe vpa api-server-vpa -n production
# Output tipico:
# Recommendation:
# Container Recommendations:
# Container Name: api-server
# Lower Bound: cpu: 100m, memory: 256Mi
# Target: cpu: 450m, memory: 512Mi
# Uncapped Target: cpu: 450m, memory: 512Mi
# Upper Bound: cpu: 2000m, memory: 2Gi
VPA in automatische modus
# vpa-auto.yaml - aggiorna automaticamente i resource (riavvia i Pod)
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: background-worker-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: background-worker
updatePolicy:
updateMode: "Auto" # Riavvia i Pod con i nuovi resource
minReplicas: 2 # Non aggiornare se le repliche sono meno di 2
resourcePolicy:
containerPolicies:
- containerName: worker
minAllowed:
cpu: "200m"
memory: "256Mi"
maxAllowed:
cpu: "2"
memory: "2Gi"
KEDA: gebeurtenisgestuurde automatische schaling
KEDA (Kubernetes Event-Driven Autoscaling) is een CNCF-operator die HPA uitbreidt 60+ vooraf gebouwde scalers: AWS SQS, Azure Service Bus, Kafka, RabbitMQ, Redis, Prometheus, Datadog en nog veel meer. KEDA kan een implementatie schalen naar 0 replica's wanneer er zijn geen gebeurtenissen en zet deze terug op 1 wanneer de eerste gebeurtenis arriveert.
KEDA-installatie
# Installa KEDA via Helm
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda \
--namespace keda \
--create-namespace \
--version 2.14.0
# Verifica
kubectl get pods -n keda
ScaledObject voor Kafka
Een werknemer die consumeert vanuit een Kafka-onderwerp schaalt op basis van de vertraging van de consumentengroep:
# keda-kafka-scaledobject.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: kafka-consumer-scaler
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: kafka-consumer
pollingInterval: 15 # controlla ogni 15 secondi
cooldownPeriod: 30 # aspetta 30s prima di scalare a 0
minReplicaCount: 0 # scala a zero se non ci sono messaggi
maxReplicaCount: 50
advanced:
restoreToOriginalReplicaCount: true
horizontalPodAutoscalerConfig:
behavior:
scaleDown:
stabilizationWindowSeconds: 30
triggers:
- type: kafka
metadata:
bootstrapServers: kafka-broker.kafka.svc:9092
consumerGroup: my-consumer-group
topic: orders-topic
lagThreshold: "100" # 100 messaggi per replica
offsetResetPolicy: latest
allowIdleConsumers: "false"
scaleToZeroOnInvalidOffset: "false"
authenticationRef:
name: kafka-auth # TriggerAuthentication con credenziali Kafka
ScaledObject voor AWS SQS
# keda-sqs-scaledobject.yaml
apiVersion: v1
kind: Secret
metadata:
name: aws-credentials
namespace: production
data:
AWS_ACCESS_KEY_ID: BASE64_KEY
AWS_SECRET_ACCESS_KEY: BASE64_SECRET
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: aws-trigger-auth
namespace: production
spec:
secretTargetRef:
- parameter: awsAccessKeyID
name: aws-credentials
key: AWS_ACCESS_KEY_ID
- parameter: awsSecretAccessKey
name: aws-credentials
key: AWS_SECRET_ACCESS_KEY
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: sqs-worker-scaler
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: sqs-worker
minReplicaCount: 0
maxReplicaCount: 100
triggers:
- type: aws-sqs-queue
authenticationRef:
name: aws-trigger-auth
metadata:
queueURL: https://sqs.eu-west-1.amazonaws.com/123456789/job-queue
queueLength: "5" # 5 messaggi per replica
awsRegion: eu-west-1
identityOwner: pod # usa IRSA se disponibile
ScaledObject op Prometheus
# keda-prometheus-scaledobject.yaml
# Scala in base a una query Prometheus personalizzata
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: api-latency-scaler
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicaCount: 2
maxReplicaCount: 30
triggers:
- type: prometheus
metadata:
serverAddress: http://kube-prometheus-stack-prometheus.monitoring.svc:9090
metricName: http_request_duration_p99
threshold: "0.5" # scala se P99 latency > 500ms
query: histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{job="api-server"}[2m])) by (le))
# Verifica stato KEDA
kubectl get scaledobject -n production
kubectl describe scaledobject kafka-consumer-scaler -n production
Karpenter: Just-in-Time-node-provisioning
Karpenter is de knooppuntprovisioner van de volgende generatie, gemaakt door AWS en nu het CNCF-project. In tegenstelling tot de Cluster Autoscaler, die werkt met vooraf gedefinieerde knooppuntgroepen, kan Karpenter knooppunten voorzien van de exacte kenmerken die vereist zijn voor in behandeling zijnde pods: instantietype, gebied, on-demand of spotcapaciteit, CPU/GPU. Het resultaat: provisioning in 30-60 seconden versus 3-5 minuten voor de Cluster Autoscaler.
Karpenter-architectuur
Karpenter vervangt de Cluster Autoscaler volledig. Het heeft twee belangrijke CRD's:
- Knooppuntpool: definieert de vereisten van de knooppunten die Karpenter kan maken (instantietypes, zones, taints, labels, limieten)
- NodeClass (EC2NodeClass op AWS): cloudproviderspecifieke configuratie (AMI, subnet, beveiligingsgroepen, gebruikersgegevens)
Karpenterinstallatie op EKS
# Prerequisiti: IRSA configurata per Karpenter
export CLUSTER_NAME="my-production-cluster"
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export AWS_REGION=eu-west-1
# Installa Karpenter con Helm
helm repo add karpenter https://charts.karpenter.sh/
helm repo update
helm upgrade --install karpenter karpenter/karpenter \
--namespace karpenter \
--create-namespace \
--version 1.0.0 \
--set serviceAccount.annotations."eks.amazonaws.com/role-arn"=arn:aws:iam::${AWS_ACCOUNT_ID}:role/KarpenterControllerRole \
--set settings.clusterName=${CLUSTER_NAME} \
--set settings.interruptionQueue=${CLUSTER_NAME} \
--set controller.resources.requests.cpu=1 \
--set controller.resources.requests.memory=1Gi
NodePool en EC2NodeClass voor productie
# karpenter-nodepool.yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: general-purpose
spec:
template:
metadata:
labels:
node-type: general-purpose
spec:
nodeClassRef:
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
name: default
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand", "spot"] # preferisce spot, fallback on-demand
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"] # compute, memory, general
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["2"] # solo istanze di generazione 3+
- key: karpenter.k8s.aws/instance-cpu
operator: In
values: ["4", "8", "16", "32"]
taints: []
expireAfter: 720h # ricicla nodi ogni 30 giorni
terminationGracePeriod: 48h
limits:
cpu: "500" # max 500 vCPU in questo NodePool
memory: 2000Gi
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 1m # consolida nodi vuoti dopo 1 minuto
budgets:
- nodes: "20%" # non drainare piu del 20% dei nodi alla volta
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
amiFamily: AL2023
amiSelectorTerms:
- alias: al2023@latest # usa sempre la AMI piu recente
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: "my-production-cluster"
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: "my-production-cluster"
instanceProfile: KarpenterNodeInstanceProfile
blockDeviceMappings:
- deviceName: /dev/xvda
ebs:
volumeSize: 100Gi
volumeType: gp3
iops: 3000
encrypted: true
metadataOptions:
httpEndpoint: enabled
httpProtocolIPv6: disabled
httpPutResponseHopLimit: 1 # sicurezza: blocca access IMDSv1 da container
httpTokens: required # richiede IMDSv2
NodePool voor GPU-workloads
# karpenter-gpu-nodepool.yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: gpu-nodes
spec:
template:
metadata:
labels:
node-type: gpu
spec:
nodeClassRef:
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
name: gpu-nodeclass
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand"] # GPU spot non disponibile in tutte le zone
- key: karpenter.k8s.aws/instance-family
operator: In
values: ["g5", "p3", "p4d"] # GPU instance families
taints:
- key: nvidia.com/gpu
effect: NoSchedule # solo Pod che tollerano questo taint
limits:
cpu: "128"
memory: 1024Gi
nvidia.com/gpu: "32" # max 32 GPU in questo NodePool
Consolidatie en kostenoptimalisatie
# Forza la consolidazione immediata (utile per test)
kubectl annotate node karpenter.sh/do-not-disrupt-
# Vedi i nodi creati da Karpenter
kubectl get nodes -l karpenter.sh/nodepool=general-purpose -o wide
# Vedi le decisioni di Karpenter in tempo reale
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter -f | grep -E "launched|terminated|consolidated"
# Vedi quanto sta costando ogni nodo (con Kubecost)
kubectl get nodeclaims -o json | jq '.items[] | {name: .metadata.name, type: .status.providerID, price: .metadata.annotations["karpenter.sh/nodepool"]}'
Combineer HPA, KEDA en Karpenter
In een volwassen productiecluster werken deze drie componenten synergetisch:
- KEDA schaal Pods van 0 tot N op basis van gebeurtenissen (Kafka-vertraging, SQS-diepte, Prometheus-query)
- Karpenter detecteert in 30-60 seconden in behandeling zijnde Pods en voorzieningenknooppunten met de exacte vereiste kenmerken
- VPA (in de Uit-modus) biedt aanbevelingen voor resourceaanvragen die u handmatig of via de CI/CD-pijplijn toepast
Aanbevolen patroon voor productie
- Stateless server API: KEDA op Prometheus (P99-latentie) + Karpenter NodePool voor algemeen gebruik
- Wachtrijmedewerker: KEDA op SQS/Kafka met minReplicas=0 + Karpenter met on-demand/spot mix
- Database/StatefulSet: VPA in automatische modus met minReplicas >= 2, geen HPA in geheugen
- Batchtaken: KEDA ScaledJob (niet ScaledObject) voor het voltooien van K8s-taken
- Gebruik HPA niet op CPU samen met KEDA - leidt tot conflicten op targetMetrics
KEDA ScaledJob voor batch
# keda-scaledjob.yaml - per batch job che terminano
apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
name: ml-training-job
namespace: production
spec:
jobTargetRef:
template:
spec:
containers:
- name: trainer
image: my-registry/ml-trainer:latest
resources:
requests:
cpu: "2"
memory: "4Gi"
nvidia.com/gpu: "1"
limits:
nvidia.com/gpu: "1"
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
restartPolicy: Never
pollingInterval: 30
maxReplicaCount: 20
successfulJobsHistoryLimit: 5
failedJobsHistoryLimit: 3
triggers:
- type: aws-sqs-queue
authenticationRef:
name: aws-trigger-auth
metadata:
queueURL: https://sqs.eu-west-1.amazonaws.com/123456789/ml-jobs
queueLength: "1" # 1 job per task
awsRegion: eu-west-1
HPA- en KEDA-probleemoplossing
# HPA non scala? Controlla lo stato
kubectl describe hpa api-server-hpa -n production
# Cerca: "AbleToScale", "ScalingActive", "DesiredReplicas"
# Errore comune: "failed to get cpu utilization" = metrics-server non installato
# Verifica che metrics-server funzioni
kubectl top pods -n production
kubectl top nodes
# KEDA non scala a zero? Controlla il cooldownPeriod
kubectl get scaledobject kafka-consumer-scaler -n production -o yaml | grep -A5 "conditions"
# Karpenter non provisiona?
kubectl get pods --field-selector=status.phase=Pending -A
kubectl describe pod | grep "Events" -A20
# Cerca: "0/N nodes are available" + il motivo del pending
# Vedi log Karpenter
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter --tail=50 | grep -i "error\|warning\|launched"
# Simula provisioning senza applicare
kubectl annotate pods karpenter.sh/do-not-disrupt=true
Beste praktijken en antipatronen
Best practices voor automatisch schalen
- Stel altijd minReplicas >= 2 in voor kritieke services: schalen vanaf 0 vereist een koude start; voor API's in productie moet u minimaal twee replica's onderhouden
- Gebruik PodDisruptionBudget: voorkomen dat Karpenter/HPA te veel Pods leegmaakt tijdens consolidatie
- Configureer nauwkeurige resourceverzoeken: HPA berekent het gebruikspercentage op resource.requests; als ze te laag zijn, schaalt het nooit
- Strikte gereedheidssonde: Kubernetes wacht tot de Pod gereed is voordat verkeer wordt verzonden; Zonder gereedheidstests ontvangen nieuw geschaalde Pods verkeer voordat ze gereed zijn
- Monitorkleppen: Als de HPA om de paar minuten op en neer schaalt, verhoog dan de waarde
stabilizationWindowSecondsvan schaalverkleining - Gebruik topologiespreidingsbeperkingen met Karpenter: distribueer Pods over zones voor hoge beschikbaarheid, zelfs tijdens de inrichting
Antipatronen die u moet vermijden
- HPA zonder gedefinieerde resourceverzoeken: HPA kan het gebruikspercentage niet berekenen zonder verzoeken in de containerspecificatie
- VPA Auto + HPA op CPU/geheugen: de twee controllers strijden om hulpbronnen en veroorzaken inconsistente schaalvergroting; gebruik KEDA voor aangepaste statistieken als u beide wilt
- maxReplica's te laag: als uw piekverkeer 100 pods vereist, maar maxReplicas en 20, is automatisch schalen niet voldoende en gaat de service achteruit
- Karpenter zonder verstoringsbudget: zonder
disruption.budgets, kan Karpenter 100% van de knooppunten leegmaken tijdens nachtelijke consolidatie - Pollinginterval te laag op KEDA: un
pollingIntervalvan 5 seconden op externe bronnen (SQS, externe API's) genereert te veel API-aanroepen en mogelijk throttling
Conclusies en volgende stappen
Effectieve autoscaling in Kubernetes is niet één oplossing, maar een meervoudige strategie niveaus: KEDA voor gebeurtenisgestuurde schaling van pods, HPA voor op gebruik gebaseerde schaling van resources, VPA om resourceverzoeken te optimaliseren, en Karpenter voor provisioning snel van de knopen. Als deze tools samen worden gebruikt, kunnen de kosten met 30-50% worden verlaagd vergeleken met statisch ingerichte clusters, waarbij hoge SLA's worden gehandhaafd.
De sleutel tot succes is de nauwkeurige configuratie van resourceverzoeken (VPA helpt hierbij), het kiezen van de juiste statistieken om op te schalen (niet altijd CPU en respons), en de configuratie van schaalgedrag (stabilisatieperioden, snelheidsbeperking) om trillingen te voorkomen die de prestaties kunnen verslechteren in plaats van verbeteren.







