Autoscaling în Kubernetes: HPA, VPA, KEDA și Karpenter
Unul dintre principalele avantaje ale Kubernetes în producție este capacitatea de a scala automat sarcini de lucru ca răspuns la cerere. Cu toate acestea, majoritatea echipelor folosesc doar o fracțiune a capabilităților de autoscaling disponibile: configurează un HPA cu scalare CPU și o lasă așa. Rezultatul? Pod-uri sub-provizionate care încetinesc sub sarcină sau noduri supra-provizionate care ard bugete fără motiv.
Kubernetes oferă patru niveluri complementare de autoscaling: HPA scara pe orizontală, podurile pe CPU/memorie/metrice personalizate, VPA repara solicitări de resurse automat, KEDA activați scalarea bazată pe evenimente orice sursă (cozi, baze de date, metrici Prometheus), e Karpenter Furnizează noduri în mai puțin de 30 de secunde, cu 40% mai rapid decât Cluster Autoscaler tradițional conform standardelor CNCF 2025. Acest articol arată cum să le folosiți împreună în producție.
Ce vei învăța
- Cum funcționează Horizontal Pod Autoscaler (HPA) cu valori personalizate și externe
- Configurați Vertical Pod Autoscaler (VPA) pentru dimensionarea automată a dreptului
- KEDA: Autoscaling bazat pe evenimente pe cozile SQS, Kafka, Redis și Prometheus
- Karpenter: furnizarea de noduri la timp cu NodePool și NodeClass
- Model de combinație: Folosiți HPA și KEDA împreună fără conflicte
- Depanare: de ce HPA nu se scalează așa cum vă așteptați
- Cele mai bune practici pentru a evita buclele clapetelor și pornirile la rece
Autoscaler cu pod orizontal (HPA)
HPA și componenta Kubernetes care scalează numărul de replici ale unui Deployment, StatefulSet o ReplicaSet bazat pe valorile observate. Controlerul HPA interogează valorile fiecare 15 secunde (configurabil) și calculați numărul dorit de replici cu formula:
desiredReplicas = ceil(currentReplicas * (currentMetricValue / desiredMetricValue))
Pentru a evita fluturarea (scalare continuă în sus și în jos), HPA are o perioadă de stabilizare: 5 minute pentru reducere și 0 secunde pentru extindere în mod implicit.
HPA pe CPU și memorie
Configurația de bază cu CPU și memorie. Rețineți că pentru a scala la memorie, aplicația trebuie să elibereze memorie atunci când sarcina scade, altfel reducerea nu are loc niciodată:
# 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 cu metrici personalizate prin adaptorul Prometheus
Pentru a scala în funcție de valorile aplicației (cereri pe secundă, lungimea cozii etc.), aveți nevoie de Adaptorul Prometheus care expune valorile Prometheus ca API Kubernetes Custom Metrics:
# 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 cu valori externe
Valorile externe vă permit să scalați la surse din afara clusterului, cum ar fi lungimea a unei cozi SQS sau a numărului de mesaje Kafka neconsumate:
# 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
Vertical Pod Autoscaler (VPA)
VPA monitorizează utilizarea reală a CPU și a memoriei Pod-urilor și se ajustează automat
i resources.requests e limits. Și soluția la problema de
„gunoaie in, garbage out” din cererile de resurse: dacă nu știi de câte resurse are nevoie ta
Pod, VPA-ul află pentru tine.
VPA și HPA: Atenție la conflicte
Nu utilizați VPA în modul Auto împreună cu scalarea HPA la CPU sau memorie:
cei doi controlori vor intra în conflict. Combinația corectă este: VPA în modul
Off o Initial pentru cererile de resurse și HPA pentru replicări
privind valorile personalizate. Sau utilizați KEDA în loc de HPA pentru a evita problema.
Instalare și configurare VPA
# 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 în modul automat
# 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: Autoscaling bazat pe evenimente
KEDA (Kubernetes Event-Driven Autoscaling) este un operator CNCF care extinde HPA cu Peste 60 de scalatoare pre-construite: AWS SQS, Azure Service Bus, Kafka, RabbitMQ, Redis, Prometheus, Datadog și multe altele. KEDA poate scala o implementare la 0 replici atunci când nu există evenimente și setați-l înapoi la 1 când sosește primul eveniment.
Instalare KEDA
# 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 pentru Kafka
Un lucrător care consumă dintr-un subiect Kafka se bazează pe decalajul grupului de consumatori:
# 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 pentru 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 pe 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: Aprovizionare cu noduri Just-in-Time
Karpenter este următoarea generație de furnizori de noduri creat de AWS și acum proiectul CNCF. Spre deosebire de Cluster Autoscaler, care funcționează cu grupuri de noduri predefinite, Karpenter noduri de furnizare cu caracteristicile exacte cerute de podurile în așteptare: tip de instanță, suprafață, capacitate la cerere sau spot, CPU/GPU. Rezultatul: aprovizionare în 30-60 de secunde față de 3-5 minute pentru Cluster Autoscaler.
Arhitectura Karpenter
Karpenter înlocuiește complet Cluster Autoscaler. Are două CRD-uri principale:
- NodePool: definește cerințele nodurilor pe care le poate crea Karpenter (tipuri de instanțe, zone, mate, etichete, limite)
- NodeClass (EC2NodeClass pe AWS): configurație specifică furnizorului de cloud (AMI, subrețea, grupuri de securitate, date utilizator)
Instalare Karpenter pe 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 și EC2NodeClass pentru producție
# 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 pentru sarcinile de lucru GPU
# 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
Consolidare și optimizare a costurilor
# 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"]}'
Combinați HPA, KEDA și Karpenter
Într-un cluster de producție matur, aceste trei componente funcționează sinergic:
- KEDA scalați podurile de la 0 la N în funcție de evenimente (lag Kafka, adâncime SQS, interogare Prometheus)
- Karpenter detectează Pod-uri în așteptare și noduri de provizii cu caracteristicile exacte necesare în 30-60 de secunde
- VPA (în modul Off) oferă recomandări de solicitare de resurse pe care le aplicați manual sau prin conducta CI/CD
Model recomandat pentru producție
- API de server fără stat: KEDA pe Prometheus (latență P99) + NodePool de uz general Karpenter
- Lucrător la coadă: KEDA pe SQS/Kafka cu minReplicas=0 + Karpenter cu mix la cerere/la fața locului
- Bază de date/StatefulSet: VPA în modul automat cu minReplicas >= 2, fără HPA în memorie
- Locuri de muncă: KEDA ScaledJob (nu ScaledObject) pentru finalizarea lucrărilor K8s
- Nu utilizați HPA pe CPU împreună cu KEDA - duce la conflicte pe targetMetrics
KEDA ScaledJob pentru lot
# 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
Depanare HPA și KEDA
# 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
Cele mai bune practici și anti-modele
Cele mai bune practici pentru autoscaling
- Setați întotdeauna minReplicas >= 2 pentru serviciile critice: scalarea de la 0 necesită o pornire la rece; pentru API-uri în producție, mențineți cel puțin 2 replici minime
- Utilizați PodDisruptionBudget: preveniți Karpenter/HPA să scurgă prea multe Pod-uri în timpul consolidării
- Configurați cereri precise de resurse: HPA calculează procentul de utilizare pe resource.requests; dacă sunt prea scăzute, nu se scalează niciodată
- Sondă strictă de pregătire: Kubernetes așteaptă ca Podul să fie gata înainte de a trimite trafic; Fără probe de pregătire, podurile nou scalate primesc trafic înainte de a fi gata
- Clapele monitorului: dacă HPA crește și scade la fiecare câteva minute, creșteți
stabilizationWindowSecondsde reducere - Utilizați constrângerile de răspândire a topologiei cu Karpenter: distribuiți poduri în zone pentru o disponibilitate ridicată chiar și în timpul aprovizionării
Anti-modele de evitat
- HPA fără solicitări de resurse definite: HPA nu poate calcula utilizarea procentuală fără solicitări în specificațiile containerului
- VPA Auto + HPA pe CPU/Memorie: cei doi controlori concurează pentru resurse și provoacă o scalare inconsecventă; utilizați KEDA pentru valori personalizate dacă doriți ambele
- maxReplicas prea scăzut: dacă traficul dvs. de vârf necesită 100 de poduri, dar maxReplicas și 20, autoscaling nu este suficient și serviciul se degradează
- Bugetul Karpenter fără întrerupere: fără
disruption.budgets, Karpenter poate drena 100% din noduri în timpul consolidării peste noapte - Interval de sondare prea mic pe KEDA: un
pollingIntervalde 5 secunde pe surse externe (SQS, API-uri externe) generează prea multe apeluri API și posibilă limitare
Concluzii și pașii următori
Scalare automată eficientă în Kubernetes nu este o singură soluție, ci o strategie multiplă niveluri: KEDA pentru scalarea bazată pe evenimente a podurilor, HPA pentru scalarea bazată pe utilizare resurse, VPA pentru optimizarea cererilor de resurse și Karpenter pentru furnizare iute de noduri. Folosite împreună, aceste instrumente pot reduce costurile cu 30-50% comparativ cu clusterele furnizate static, menținând SLA-uri ridicate.
Cheia succesului este configurarea precisă a solicitărilor de resurse (VPA ajută aici), alegerea valorilor potrivite pentru scalare (nu întotdeauna CPU și răspuns) și configurația comportamentului de scalare (perioade de stabilizare, limitare a ratei) pentru a evita oscilațiile care pot înrăutăți performanța în loc să o îmbunătățească.







