Automatické škálování v Kubernetes: HPA, VPA, KEDA a Karpenter
Jednou z hlavních výhod Kubernetes v produkci je možnost automatického škálování pracovní zátěže v reakci na poptávku. Přesto většina týmů používá pouze zlomek dostupných možností automatického škálování: nakonfigurují HPA se škálováním CPU a nechají to tak. Výsledek? Nedostatečně zajištěné pody, které se při zatížení zpomalují, nebo nadměrně zajištěné uzly, které bezdůvodně pálí rozpočty.
Kubernetes nabízí čtyři doplňkové úrovně automatického škálování: HPA měřítko horizontálně pody na CPU/paměť/vlastní metriky, VPA opravit automaticky požaduje zdroje, KEDA povolit škálování řízené událostmi jakýkoli zdroj (fronty, databáze, metriky Prometheus), např Karpenter Poskytuje uzly za méně než 30 sekund, o 40 % rychleji než tradiční Cluster Autoscaler podle benchmarků CNCF 2025. Tento článek ukazuje, jak je používat společně ve výrobě.
Co se naučíte
- Jak Horizontal Pod Autoscaler (HPA) pracuje s vlastními a externími metrikami
- Nakonfigurujte Vertical Pod Autoscaler (VPA) pro automatické přizpůsobení
- KEDA: Událostí řízené automatické škálování ve frontách SQS, Kafka, Redis a Prometheus
- Karpenter: Just-in-time zřizování uzlů s NodePool a NodeClass
- Kombinovaný vzor: Používejte HPA a KEDA společně bez konfliktů
- Odstraňování problémů: proč se vaše HPA neškáluje tak, jak očekáváte
- Doporučené postupy, jak se vyhnout smyčkám klapek a studeným startům
Horizontal Pod Autoscaler (HPA)
HPA a komponenta Kubernetes, která škáluje počet replik Deployment, StatefulSet o ReplicaSet na základě pozorovaných metrik. Řadič HPA se dotazuje na metriky každý 15 sekund (konfigurovatelné) a vypočítejte požadovaný počet replik podle vzorce:
desiredReplicas = ceil(currentReplicas * (currentMetricValue / desiredMetricValue))
Aby se zabránilo chvění (nepřetržité zvyšování a snižování měřítka), má HPA stabilizační období: Ve výchozím nastavení 5 minut pro zmenšení a 0 sekund pro zvětšení.
HPA na CPU a paměti
Základní konfigurace s CPU a pamětí. Všimněte si, že pro škálování do paměti, aplikace musí uvolnit paměť, když se zatížení sníží, jinak k zmenšení nikdy nedojde:
# 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 s vlastními metrikami prostřednictvím adaptéru Prometheus
Chcete-li škálovat podle metrik aplikace (požadavky za sekundu, délka fronty atd.), potřebujete Adaptér Prometheus odhalující metriky Prometheus jako vlastní metriky 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 s externími metrikami
Externí metriky umožňují škálovat na zdroje mimo cluster, jako je délka fronty SQS nebo počet nespotřebovaných Kafkových zpráv:
# 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 monitoruje skutečné využití CPU a paměti vašich podů a automaticky se přizpůsobuje
i resources.requests e limits. A řešení problému
„odpadky dovnitř, odpadky ven“ požadavků na zdroje: pokud nevíte, kolik zdrojů potřebujete
Pod, VPA to zjistí za vás.
VPA a HPA: Pozor na konflikty
Nepoužívejte VPA v režimu Auto spolu s HPA škálováním na CPU nebo paměť:
dva ovladače budou v konfliktu. Správná kombinace je: VPA v režimu
Off o Initial pro požadavky na zdroje a HPA pro replikace
na vlastních metrikách. Nebo použijte KEDA místo HPA, abyste se vyhnuli problému.
Instalace a konfigurace 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 v automatickém režimu
# 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: Událostí řízené automatické škálování
KEDA (Kubernetes Event-Driven Autoscaling) je operátor CNCF, který rozšiřuje HPA o 60+ předpřipravených scalerů: AWS SQS, Azure Service Bus, Kafka, RabbitMQ, Redis, Prometheus, Datadog a mnoho dalších. KEDA může škálovat nasazení na 0 replik, když nejsou žádné události a nastavte ji zpět na 1, když dorazí první událost.
Instalace 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 pro Kafku
Pracovník spotřebovávající z tématu Kafka se škáluje na základě zpoždění skupiny spotřebitelů:
# 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 pro 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 na 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 je uzlový provizor nové generace vytvořený projektem AWS a nyní CNCF. Na rozdíl od Cluster Autoscaler, který pracuje s předdefinovanými skupinami uzlů, Karpenter zajišťovací uzly s přesnými charakteristikami požadovanými nevyřízenými pody: typ instance, oblast, kapacita na vyžádání nebo na místě, CPU/GPU. Výsledek: zajištění za 30–60 sekund oproti 3-5 minutám pro Cluster Autoscaler.
Karpenterská architektura
Karpenter zcela nahrazuje Cluster Autoscaler. Má dvě hlavní CRD:
- NodePool: definuje požadavky uzlů, které může Karpenter vytvořit (typy instancí, zóny, skvrny, štítky, limity)
- NodeClass (EC2NodeClass na AWS): Konfigurace specifická pro poskytovatele cloudu (AMI, podsíť, skupiny zabezpečení, uživatelská data)
Instalace Karpenter na 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 a EC2NodeClass pro produkci
# 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 pro pracovní zatížení 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
Konsolidace a optimalizace nákladů
# 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"]}'
Kombinujte HPA, KEDA a Karpenter
Ve vyspělém výrobním klastru fungují tyto tři složky synergicky:
- KEDA škálovat Pody od 0 do N na základě událostí (Kafkova prodleva, hloubka SQS, dotaz Prometheus)
- Karpenter detekuje čekající moduly a provizorní uzly s přesnými požadovanými charakteristikami za 30-60 sekund
- VPA (v režimu Vypnuto) poskytuje doporučení požadavků na zdroje, které aplikujete ručně nebo prostřednictvím kanálu CI/CD
Doporučený vzor pro výrobu
- Bezstavové serverové API: KEDA na Prometheus (latence P99) + univerzální NodePool Karpenter
- Pracovník fronty: KEDA na SQS/Kafka s minReplicas=0 + Karpenter s mixem na vyžádání/na místě
- Databáze/StatefulSet: VPA v automatickém režimu s minReplicas >= 2, bez HPA v paměti
- Dávkové úlohy: KEDA ScaledJob (nikoli ScaledObject) pro dokončování úloh K8s
- Nepoužívejte HPA na CPU společně s KEDA – vede ke konfliktům na targetMetrics
KEDA ScaledJob pro dávku
# 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
Odstraňování problémů s HPA a 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
Osvědčené postupy a anti-vzorce
Nejlepší postupy pro automatické škálování
- Vždy nastavte minReplicas >= 2 pro kritické služby: škálování od 0 vyžaduje studený start; pro produkční rozhraní API udržujte alespoň 2 minimální repliky
- Použijte PodDisruptionBudget: Zabraňte tomu, aby Karpenter/HPA během konsolidace vypustil příliš mnoho lusků
- Nakonfigurujte přesné požadavky na zdroje: HPA vypočítá procentuální využití na resource.requests; pokud jsou příliš nízké, nikdy se neškáluje
- Přísná sonda připravenosti: Kubernetes před odesláním provozu čeká, až bude modul připravený; Bez sond připravenosti přijímají nově zmenšené moduly provoz dříve, než jsou připraveny
- Monitorovací klapky: pokud se HPA každých několik minut zvyšuje a snižuje, zvyšte hodnotu
stabilizationWindowSecondszmenšení - Použijte omezení rozprostření topologie s Karpenterem: distribuujte Pody napříč zónami pro vysokou dostupnost i během zřizování
Anti-vzory, kterým je třeba se vyhnout
- HPA bez definovaných požadavků na zdroje: HPA nemůže vypočítat procento využití bez požadavků ve specifikaci kontejneru
- VPA Auto + HPA na CPU/paměti: dva řadiče soutěží o zdroje a způsobují nekonzistentní škálování; Pokud chcete obojí, použijte KEDA pro vlastní metriky
- maxReplicas je příliš nízký: pokud váš špičkový provoz vyžaduje 100 modulů, ale max. replik a 20, automatické škálování nestačí a služba se zhoršuje
- Karpenter bez přerušení rozpočtu: bez
disruption.budgets, Karpenter může vypustit 100 % uzlů během noční konsolidace - Interval dotazování je na KEDA příliš krátký: un
pollingInterval5 sekund na externích zdrojích (SQS, externí API) generuje příliš mnoho volání API a možné omezení
Závěry a další kroky
Efektivní automatické škálování v Kubernetes není jediné řešení, ale více strategií úrovně: KEDA pro škálování podů řízené událostmi, HPA pro škálování podle použití zdrojů, VPA pro optimalizaci požadavků na zdroje a Karpenter pro zajišťování rychle z uzlů. Při společném použití mohou tyto nástroje snížit náklady o 30–50 % ve srovnání se staticky zřízenými clustery, zachování vysokých SLA.
Klíčem k úspěchu je přesná konfigurace požadavků na zdroje (zde pomáhá VPA), výběr správných metrik pro škálování (ne vždy CPU a odezva) a konfigurace chování škálování (doby stabilizace, omezení rychlosti) abyste se vyhnuli oscilacím, které mohou výkon zhoršit, místo aby jej zlepšily.







