- はじめに
- HPA v2の動作原理とアルゴリズム
- VPAのモード別の違いと制限事項
- KEDA: イベントドリブンなオートスケーリング
- HPA・VPA・KEDAの比較
- 混合戦略: HPA + VPA + KEDA
- トラブルシューティング: 失敗事例と復旧
- 運用チェックリスト
- おわりに
- 参考資料

はじめに
Kubernetesのワークロードを運用していると、もっとも厄介な問題のひとつが リソースのスケーリング です。トラフィックが急増するとPodが不足して障害が発生し、トラフィックが減ると過剰プロビジョニングでコストが無駄になります。Kubernetesはこの問題を解決するために、3つの中核的なオートスケーラーを提供しています。
- HPA (Horizontal Pod Autoscaler): Podの数を水平方向に調整
- VPA (Vertical Pod Autoscaler): 個々のPodのCPU/メモリ要求量を垂直方向に調整
- KEDA (Kubernetes Event-Driven Autoscaling): 外部イベントソースにもとづく拡張されたオートスケーリング
本記事では、各オートスケーラーの動作原理、設定方法、そして実践で遭遇するトラブルシューティング事例まで総合的に扱います。
HPA v2の動作原理とアルゴリズム
スケーリングアルゴリズム
HPAは autoscaling/v2 APIを使用し、次の計算式で必要なレプリカ数を算出します:
desiredReplicas = ceil(currentReplicas × (currentMetricValue / desiredMetricValue))
例えば、現在4個のPodの平均CPU使用率が80%で目標が50%であれば、ceil(4 × (80/50)) = ceil(6.4) = 7個のPodへスケールアウトされます。
HPAコントローラーはデフォルトで 15秒周期 でメトリクスを収集し、--horizontal-pod-autoscaler-sync-period フラグで調整できます。
基本的なHPA設定 - CPU/メモリベース
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 3
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 75
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
- type: Pods
value: 4
periodSeconds: 15
selectPolicy: Max
この設定で核心となるのは behavior ブロックです。スケールアップは即時(stabilizationWindowSeconds: 0)に実行されますが、スケールダウンは5分間の安定化期間を経て、60秒ごとに最大10%ずつしか減らしません。これによって、急激な縮小によるサービスへの影響を防ぎます。
カスタムメトリクスベースのHPA
実際の運用では、CPU/メモリよりも アプリケーションメトリクス(RPS、キューの深さ、アクティブコネクションなど)のほうが正確なスケーリング指標になります。Prometheus Adapterを活用したカスタムメトリクスHPAを設定してみます。
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-service-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-service
minReplicas: 2
maxReplicas: 30
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: '100'
- type: Object
object:
metric:
name: rabbitmq_queue_messages
describedObject:
apiVersion: v1
kind: Service
name: rabbitmq
target:
type: Value
value: '500'
この設定は2種類のカスタムメトリクスを使用します。Pod当たりの秒間HTTPリクエストが100を超えるか、RabbitMQのキューで待機中のメッセージが500個を超えるとスケールアウトします。複数のメトリクスが設定されている場合、HPAは各メトリクスごとに必要なレプリカ数を計算したうえで 最大値 を選択します。
Prometheus Adapterの設定は次のとおりです:
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-adapter-config
namespace: monitoring
data:
config.yaml: |
rules:
- seriesQuery: 'http_requests_total{namespace!="",pod!=""}'
resources:
overrides:
namespace: {resource: "namespace"}
pod: {resource: "pod"}
name:
matches: "^(.*)_total$"
as: "${1}_per_second"
metricsQuery: 'rate(<<.Series>>{<<.LabelMatchers>>}[2m])'
- seriesQuery: 'rabbitmq_queue_messages{namespace!=""}'
resources:
overrides:
namespace: {resource: "namespace"}
name:
matches: "^(.*)$"
as: "$1"
metricsQuery: '<<.Series>>{<<.LabelMatchers>>}'
VPAのモード別の違いと制限事項
VPAのアーキテクチャ
VPAは3つのコンポーネントで構成されます:
- Recommender: 現在および過去のリソース使用量を分析し、最適な要求量を推奨
- Updater: 誤ったリソース要求を持つPodを退去(Evict)
- Admission Controller: 新しく作成されるPodに正しいリソース要求を注入
VPAのモード
| モード | 動作 | Podの再起動 | 利用シナリオ |
|---|---|---|---|
| Off | 推奨の提供のみ、自動適用なし | なし | リソース分析、導入初期の段階 |
| Initial | Pod作成時にのみ推奨値を適用 | なし (新規Podのみ) | 安定性を優先するワークロード |
| Auto | 推奨値を既存のPodにも適用 (再起動) | あり | ステートレスなワークロード、開発環境 |
| Recreate | Autoと同じだが再起動を保証 | あり | 明示的な再起動が必要なとき |
Kubernetes 1.32から InPlaceOrRecreate モードが追加され、可能な場合はPodを再起動せずにリソースを変更します。この機能は InPlacePodVerticalScaling フィーチャーゲートが有効になっている必要があります。
VPAの設定例
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: payment-service-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: payment-service
updatePolicy:
updateMode: 'Initial'
minReplicas: 2
resourcePolicy:
containerPolicies:
- containerName: payment-app
minAllowed:
cpu: '100m'
memory: '128Mi'
maxAllowed:
cpu: '4'
memory: '8Gi'
controlledResources: ['cpu', 'memory']
controlledValues: RequestsOnly
- containerName: sidecar-proxy
mode: 'Off'
この設定で注目すべき点は次のとおりです:
updateMode: Initialに設定し、既存のPodを再起動せず新しいPodにのみ適用minReplicas: 2で最低2個のPodが常に実行中であることを保証- サイドカーコンテナ(
sidecar-proxy)はVPAの対象から除外(mode: Off) controlledValues: RequestsOnlyでlimitには手を触れず、requestのみを調整
VPAの主な制限事項
- HPAとの同時利用の制限: VPAを同じメトリクス(CPU/メモリ)でHPAと併用すると衝突が発生します。HPAがCPU使用率をもとにPod数を増やすと同時にVPAがCPU要求量を増やすと、予測できない動作になります。
- 最大1,000 Podの制限: VPAが管理するPod数は、クラスタ当たり1,000個を超えないことが推奨されます。
- CronJob/Jobは非対応: VPAは長時間実行されるワークロード(Deployment、StatefulSet)でのみ動作します。
- JVMワークロード: JVMのヒープメモリは起動時に固定されるため、VPAがメモリを減らしても実際にJVMが使用するメモリは減りません。
KEDA: イベントドリブンなオートスケーリング
KEDAのアーキテクチャ
KEDAはHPAを置き換えるものではなく 拡張 します。KEDAは外部イベントソース(Kafka、Prometheus、Redis、AWS SQSなど)のメトリクスを取得してHPAへ注入する役割を担います。
中核となるCRDは次のとおりです:
- ScaledObject: Deployment/StatefulSetに対するスケーリングルールを定義
- ScaledJob: Jobベースのワークロードに対するスケーリングルールを定義
- TriggerAuthentication: 外部システムの認証情報を管理
- ClusterTriggerAuthentication: クラスタ範囲の認証情報
KafkaベースのKEDAスケーリング
apiVersion: v1
kind: Secret
metadata:
name: kafka-credentials
namespace: production
type: Opaque
data:
sasl_username: dXNlcm5hbWU=
sasl_password: cGFzc3dvcmQ=
ca: LS0tLS1CRUdJTi4uLg==
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: kafka-trigger-auth
namespace: production
spec:
secretTargetRef:
- parameter: sasl
name: kafka-credentials
key: sasl_username
- parameter: password
name: kafka-credentials
key: sasl_password
- parameter: ca
name: kafka-credentials
key: ca
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: order-consumer-scaler
namespace: production
spec:
scaleTargetRef:
name: order-consumer
pollingInterval: 15
cooldownPeriod: 300
idleReplicaCount: 0
minReplicaCount: 1
maxReplicaCount: 50
fallback:
failureThreshold: 3
replicas: 5
triggers:
- type: kafka
metadata:
bootstrapServers: 'kafka-0.kafka:9092,kafka-1.kafka:9092,kafka-2.kafka:9092'
consumerGroup: order-consumer-group
topic: orders
lagThreshold: '100'
activationLagThreshold: '10'
offsetResetPolicy: latest
authenticationRef:
name: kafka-trigger-auth
この設定の主要なパラメータを分析します:
idleReplicaCount: 0— イベントがなければPodを0に減らしてコストを削減(ゼロスケーリング)activationLagThreshold: 10— コンシューマーラグが10未満であれば活性化しないlagThreshold: 100— コンシューマーラグ100ごとにPodを1個追加fallback— KEDAがメトリクスを取得できない場合(3回失敗時)、5個のレプリカへフォールバック
PrometheusベースのKEDAスケーリング
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: web-frontend-scaler
namespace: production
spec:
scaleTargetRef:
name: web-frontend
pollingInterval: 30
cooldownPeriod: 120
minReplicaCount: 2
maxReplicaCount: 100
advanced:
restoreToOriginalReplicaCount: true
horizontalPodAutoscalerConfig:
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 25
periodSeconds: 60
triggers:
- type: prometheus
metadata:
serverAddress: 'http://prometheus.monitoring.svc:9090'
query: |
sum(rate(nginx_ingress_controller_requests{
namespace="production",
service="web-frontend"
}[2m]))
threshold: '500'
activationThreshold: '50'
- type: prometheus
metadata:
serverAddress: 'http://prometheus.monitoring.svc:9090'
query: |
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket{
namespace="production",
service="web-frontend"
}[5m])) by (le))
threshold: '0.5'
activationThreshold: '0.1'
この設定は2種類のPrometheusクエリを使用します。秒間リクエスト数が500を超えるか、P95レイテンシが500msを超えるとスケールアウトします。advanced.horizontalPodAutoscalerConfig を通じて、内部的に生成されるHPAのbehaviorも制御できます。
HPA・VPA・KEDAの比較
| 項目 | HPA | VPA | KEDA |
|---|---|---|---|
| スケーリング方向 | 水平 (Pod数) | 垂直 (リソースサイズ) | 水平 (Pod数) + ゼロスケール |
| 標準メトリクス | CPU、メモリ | CPU、メモリ | 60+の外部イベントソース |
| カスタムメトリクス | Adapterが必要 | 非対応 | ネイティブ対応 |
| ゼロスケーリング | 不可 (minReplicas >= 1) | 該当なし | 可能 (idleReplicaCount: 0) |
| Podの再起動 | なし | あり (Auto/Recreate) | なし |
| 設定の複雑さ | 低い | 中程度 | 中程度〜高い |
| Kubernetes組み込み | はい | 別途インストール | 別途インストール |
| CronJob対応 | 限定的 | 非対応 | ScaledJobで対応 |
| ステートフルなワークロード | 注意が必要 | 対応 | 注意が必要 |
| コミュニティ/エコシステム | 非常に活発 | 活発 | CNCF Graduated、非常に活発 |
混合戦略: HPA + VPA + KEDA
HPA + VPAの混合
HPAとVPAを併用するときは メトリクスの衝突 を必ず避ける必要があります。推奨されるパターンは次のとおりです:
- HPA: カスタムメトリクス(RPS、キューの深さなど)にもとづく水平スケーリング
- VPA: Offモードでリソースの推奨のみを行うか、CPU/メモリにもとづく垂直調整
# HPA: カスタムメトリクスのみ使用
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 3
maxReplicas: 20
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: '200'
---
# VPA: CPU/メモリのリソース最適化
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
updatePolicy:
updateMode: 'Auto'
resourcePolicy:
containerPolicies:
- containerName: api
controlledResources: ['cpu', 'memory']
controlledValues: RequestsOnly
HPA + KEDAの混合
KEDAは内部的にHPAを生成するため、同じDeploymentに別途HPAを作ると衝突します。代わりに、KEDAのScaledObjectに複数のtriggerを追加する方式で組み合わせます。
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: hybrid-scaler
namespace: production
spec:
scaleTargetRef:
name: worker-service
minReplicaCount: 2
maxReplicaCount: 100
triggers:
# CPUベース (HPAの役割を代替)
- type: cpu
metricType: Utilization
metadata:
value: '70'
# Kafkaイベントベース
- type: kafka
metadata:
bootstrapServers: 'kafka.default:9092'
consumerGroup: worker-group
topic: tasks
lagThreshold: '50'
# Cronベースの予約スケーリング
- type: cron
metadata:
timezone: Asia/Seoul
start: '0 9 * * 1-5'
end: '0 18 * * 1-5'
desiredReplicas: '10'
この設定は3つの戦略を組み合わせています: 平常時はCPUベース、Kafkaのメッセージが滞留すればイベントベース、平日の業務時間(09:00〜18:00)はCronベースで最低10個を維持します。KEDAはすべてのトリガーのうち 最大値 を使うため、安全に組み合わせられます。
トラブルシューティング: 失敗事例と復旧
事例1: HPAがスケールアウトしない
症状: CPU使用率が90%なのにHPAが反応しない
# HPAの状態確認
kubectl get hpa api-server-hpa -n production -o yaml
# イベントの確認
kubectl describe hpa api-server-hpa -n production
# metrics-serverの動作確認
kubectl top pods -n production
kubectl get apiservices | grep metrics
原因と解決:
- metrics-serverが未インストール:
kubectl get deployment metrics-server -n kube-systemで確認 - リソース要求が未設定: Podに
resources.requestsがないとUtilizationを計算できません。必ずrequestの設定が必要 - maxReplicasに到達:
kubectl get hpaでMAXPODSに到達していないか確認 - 未知のメトリクス:
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1"でカスタムメトリクスAPIを確認
事例2: VPAの推奨値が異常に高い
症状: VPAがメモリ要求を32Giと推奨するが、実際の使用量は2Gi
原因: JVMワークロードで -Xmx をメモリlimitに近い値に設定している場合、JVMが最大ヒープを確保するためVPAが高い値を推奨します。
解決: maxAllowed を適切に設定し、JVMワークロードはVPAからメモリを除外します。
resourcePolicy:
containerPolicies:
- containerName: java-app
controlledResources: ['cpu'] # メモリはVPAから除外
maxAllowed:
cpu: '4'
事例3: KEDAが0へスケールインした後の復帰が遅い
症状: Kafkaコンシューマーが0へスケールインした後、メッセージが届いてもPodの起動まで2〜3分かかる
解決策:
activationLagThresholdを低めに設定して、より早く活性化させるminReplicaCount: 1に設定して最低1個のPodを維持する- Podの
readinessProbeの時間を短縮して素早くサービスに登録する - コンテナイメージの事前プル(pre-pull)戦略を使う
事例4: HPAのFlapping(頻繁なスケールアップ/ダウン)
症状: Pod数が継続的に増加/減少を繰り返す
# HPAのイベント履歴を確認
kubectl get events --field-selector involvedObject.name=api-hpa -n production --sort-by='.lastTimestamp'
解決:
behavior:
scaleDown:
stabilizationWindowSeconds: 600 # 10分間の安定化
policies:
- type: Pods
value: 1
periodSeconds: 300 # 5分に1個ずつのみ縮小
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 50
periodSeconds: 60
運用チェックリスト
運用環境でオートスケーリングを適用する際に必ず確認すべき項目です:
デプロイ前のチェックリスト
- すべてのPodに
resources.requestsとresources.limitsが設定されているか - PodDisruptionBudget(PDB)が設定されているか(VPA利用時は必須)
- metrics-serverまたはPrometheus Adapterが正常に動作しているか
- HPAとVPAが同じメトリクスで衝突していないか
maxReplicasがクラスタのノードオートスケーラーの最大ノード数と整合しているか- KEDA利用時に
fallbackの設定がされているか
モニタリングのチェックリスト
- HPAの現在のレプリカ数と目標レプリカ数のダッシュボード
- VPAの推奨値と実際の使用量の推移
- KEDAのトリガーメトリクスの値と活性化の状態
- スケーリングイベントの通知(Slack/PagerDuty連携)
- ノードオートスケーラーとの連携状態(Pending Podのモニタリング)
コスト最適化チェックリスト
- 開発/ステージング環境はKEDAでゼロスケーリングを適用
- 業務時間外はCronトリガーで最小レプリカを縮小
- VPA Offモードで推奨値を収集し、定期的にrequestを更新
- Spot/Preemptibleインスタンスとオートスケーリングを組み合わせる
おわりに
Kubernetesのオートスケーリングは単一のツールでは解決できません。ワークロードの特性に応じてHPA、VPA、KEDAを適切に組み合わせる必要があります。CPU/メモリにもとづく単純なスケーリングにはHPA v2で十分であり、リソース最適化が必要ならVPAを併用し、イベントドリブンなワークロードやゼロスケーリングが必要ならKEDAが適しています。
特に運用環境では、behavior 設定によるスケーリング速度の制御、PDBとの連携、そしてfallback戦略が重要です。本記事で扱った設定例とトラブルシューティング事例を参考に、安定しながらもコスト効率の高いオートスケーリング戦略を構築していただければと思います。