Kubernetes の自動スケーリング: HPA、VPA、KEDA、Karpenter
実稼働環境における Kubernetes の主な利点の 1 つは、自動的にスケーリングできることです。 需要に応じたワークロード。しかし、ほとんどのチームは端数しか使用していません 利用可能な自動スケーリング機能の一部: CPU スケーリングを使用して HPA を構成し、そのままにしておきます。 結果?負荷がかかると速度が低下するプロビジョニング不足のポッド、または過剰にプロビジョニングされたノード 彼らは理由もなく予算を使い果たします。
Kubernetes は、次の 4 つの補完的なレベルの自動スケーリングを提供します。 HPA スケール 水平方向には CPU/メモリ/カスタム メトリクス上のポッド、 VPA 修正する リソースは自動的にリクエストされ、 ケダ イベント駆動型のスケーリングを有効にする 任意のソース (キュー、データベース、Prometheus メトリクス)、e カーペンター ノードのプロビジョニングは 30 秒未満で、従来のクラスター オートスケーラーよりも 40% 高速です CNCF 2025 ベンチマークによると。この記事では、実稼働環境でこれらを組み合わせて使用する方法を説明します。
何を学ぶか
- 水平ポッド オートスケーラー (HPA) がカスタム メトリックおよび外部メトリックとどのように連携するか
- 自動サイズ調整用に垂直ポッド オートスケーラー (VPA) を構成する
- KEDA: SQS、Kafka、Redis、Prometheus キューでのイベント駆動型の自動スケーリング
- Karpenter: NodePool と NodeClass を使用したジャストインタイムのノード プロビジョニング
- 組み合わせパターン: HPA と KEDA を競合せずに併用する
- トラブルシューティング: HPA が期待どおりに拡張できない理由
- フラップ ループとコールド スタートを回避するためのベスト プラクティス
水平ポッドオートスケーラー (HPA)
HPA と、デプロイメントのレプリカの数を調整する Kubernetes コンポーネント、StatefulSet o 観察されたメトリクスに基づく ReplicaSet。 HPA コントローラーはメトリクスをクエリします。 15 秒 (構成可能) を実行し、次の式を使用して必要なレプリカの数を計算します。
desiredReplicas = ceil(currentReplicas * (currentMetricValue / desiredMetricValue))
フラッピング (連続的な上下スケーリング) を回避するために、HPA には安定化期間があります。 デフォルトでは、スケールダウンの場合は 5 分、スケールアップの場合は 0 秒です。
CPU とメモリの HPA
CPUとメモリを搭載した基本構成。メモリに合わせてスケールするには、アプリケーションが 負荷が減少したときにメモリを解放する必要があります。そうしないと、スケールダウンは行われません。
# 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
Prometheus アダプター経由のカスタム メトリックを使用した HPA
アプリケーションのメトリクス (1 秒あたりのリクエスト数、キューの長さなど) をスケールするには、 Prometheus メトリクスをカスタム メトリクス Kubernetes API として公開する Prometheus アダプタ:
# 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
外部メトリックを使用すると、長さなどクラスターの外部のソースに合わせてスケールできます。 SQS キューまたは未消費の Kafka メッセージの数:
# 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
垂直ポッド オートスケーラー (VPA)
VPA はポッドの実際の CPU とメモリ使用量を監視し、自動的に調整します。
私は resources.requests e limits。そして、その問題の解決策は、
リソースリクエストの「ガベージイン、ガベージアウト」: 必要なリソースの数がわからない場合
ポッド、VPA があなたのために見つけてくれます。
VPA と HPA: 競合に注意してください
モードでは VPA を使用しないでください Auto CPU またはメモリに応じた HPA スケーリングとともに:
2 つのコントローラーが競合します。正しい組み合わせは次のとおりです: VPA モード
Off o Initial リソース要求の場合は HPA、レプリケーションの場合は HPA
カスタムメトリクスについて。または、問題を回避するには、HPA の代わりに KEDA を使用してください。
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
# 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: イベント駆動型の自動スケーリング
KEDA (Kubernetes Event-Driven Autoscaling) は、HPA を拡張する CNCF オペレーターです。 60 以上の事前構築済みスケーラー: AWS SQS、Azure Service Bus、Kafka、RabbitMQ、Redis、 プロメテウス、Datadog、その他多数。 KEDA は、次の場合にデプロイメントを 0 レプリカまで拡張できます。 イベントがない場合は、最初のイベントが到着したときに 1 に戻します。
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
Kafka の ScaledObject
Kafka トピックから消費するワーカーは、コンシューマ グループ ラグに基づいてスケーリングします。
# 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
AWS SQS の ScaledObject
# 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
Prometheus の ScaledObject
# 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: ジャストインタイムのノード プロビジョニング
Karpenter は、AWS によって作成され、現在は CNCF プロジェクトとなっている次世代ノード プロビジョナーです。 事前定義されたノード グループで動作するクラスター オートスケーラーとは異なり、Karpenter 保留中のポッドに必要な正確な特性を備えたノードをプロビジョニングします: インスタンス タイプ、 エリア、オンデマンドまたはスポット容量、CPU/GPU。結果: 30 ~ 60 秒でプロビジョニングが完了します。 クラスター オートスケーラーの場合は 3 ~ 5 分です。
カーペンター建築
Karpenter は、Cluster Autoscaler を完全に置き換えます。これには 2 つの主要な CRD があります。
- ノードプール: Karpenter が作成できるノードの要件 (インスタンス タイプ、ゾーン、テイント、ラベル、制限) を定義します。
- NodeClass (AWS 上の EC2NodeClass): クラウドプロバイダー固有の構成 (AMI、サブネット、セキュリティグループ、ユーザーデータ)
EKS への Karpenter のインストール
# 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 と EC2NodeClass
# 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
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
統合とコストの最適化
# 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"]}'
HPA、KEDA、Karpenter を組み合わせる
成熟した製造クラスターでは、次の 3 つのコンポーネントが相乗的に機能します。
- ケダ イベント (Kafka ラグ、SQS 深度、Prometheus クエリ) に基づいて Pod を 0 から N にスケールします。
- カーペンター 保留中のポッドを検出し、必要な正確な特性を備えたノードを 30 ~ 60 秒でプロビジョニングします
- VPA (オフ モード) 手動または CI/CD パイプライン経由で適用するリソース リクエストの推奨事項を提供します
制作推奨パターン
- ステートレス サーバー API: KEDA on Prometheus (P99 レイテンシー) + Karpenter 汎用 NodePool
- キュー ワーカー: minReplicas=0 の SQS/Kafka 上の KEDA + オンデマンド/スポット ミックスの Karpenter
- データベース/ステートフルセット: minReplicas >= 2 の自動モードの VPA、メモリ上に HPA なし
- バッチ ジョブ: K8s ジョブの終了用の KEDA ScaledJob (ScaledObject ではありません)
- CPU で HPA を KEDA と一緒に使用しないでください - targetMetrics で競合が発生します
バッチ用の KEDA スケールジョブ
# 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 および 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
ベストプラクティスとアンチパターン
自動スケーリングのベスト プラクティス
- 重要なサービスには常に minReplicas >= 2 を設定します。 0 からスケーリングするにはコールド スタートが必要です。本番環境の API の場合は、少なくとも 2 つのレプリカを維持します
- PodDisruptionBudget を使用します。 Karpenter/HPA が統合中にあまりにも多くの Pod を排出しないようにします
- 正確なリソース要求を構成します。 HPA は、resource.requests の使用率を計算します。低すぎる場合は拡張できません
- 厳密な準備状況プローブ: Kubernetes は、トラフィックを送信する前にポッドの準備が完了するまで待機します。 Readiness Probe がないと、新しくスケーリングされた Pod は準備が整う前にトラフィックを受信します
- モニターフラップ: HPA が数分ごとにスケールアップおよびスケールダウンする場合は、
stabilizationWindowSecondsスケールダウンの - Karpenter でトポロジ スプレッド制約を使用します。 プロビジョニング中であっても高可用性を実現するために、ゾーン間でポッドを分散します。
避けるべきアンチパターン
- リソース要求が定義されていない HPA: HPA は、コンテナー仕様のリクエストがなければ使用率を計算できません
- CPU/メモリ上の VPA Auto + HPA: 2 つのコントローラーがリソースをめぐって競合し、スケーリングの一貫性が失われます。両方が必要な場合は、カスタム メトリクスで KEDA を使用します
- maxReplicas が低すぎる: ピーク時のトラフィックに 100 ポッドが必要だが、maxReplicas と 20 が必要な場合、自動スケーリングでは十分ではなく、サービスが低下します。
- 予算を中断せずに Karpenter: それなし
disruption.budgets、Karpenter は夜間の統合中にノードの 100% を排出できます。 - KEDA でのポーリング間隔が短すぎます: un
pollingInterval外部ソース (SQS、外部 API) で 5 秒間使用すると、API 呼び出しが多すぎてスロットルが発生する可能性があります
結論と次のステップ
Kubernetes での効果的な自動スケーリングは単一のソリューションではなく、複数の戦略です レベル: ポッドのイベント駆動型スケーリング用の KEDA、使用量ベースのスケーリング用の HPA リソース、VPA によるリソース要求の最適化、Karpenter によるプロビジョニング 結び目が早い。これらのツールを併用すると、コストを 30 ~ 50% 削減できます。 静的にプロビジョニングされたクラスターと比較して、高い SLA を維持します。
成功の鍵は、リソース リクエストの正確な構成です (ここでは VPA が役に立ちます)。 スケールする適切なメトリクスを選択すること (必ずしも CPU とレスポンスであるとは限りません)、 スケーリング動作の設定 (安定化期間、レート制限) パフォーマンスを向上させるどころか悪化させる可能性のある振動を回避するためです。







