LabHub

ブログ

Kubernetes HPA・VPA・KEDAオートスケーリング戦略

한국어English日本語

Kubernetes HPA VPA KEDA Autoscaling

はじめに

Kubernetesのワークロードを運用していると、もっとも厄介な問題のひとつが リソースのスケーリング です。トラフィックが急増するとPodが不足して障害が発生し、トラフィックが減ると過剰プロビジョニングでコストが無駄になります。Kubernetesはこの問題を解決するために、3つの中核的なオートスケーラーを提供しています。

本記事では、各オートスケーラーの動作原理、設定方法、そして実践で遭遇するトラブルシューティング事例まで総合的に扱います。

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つのコンポーネントで構成されます:

VPAのモード

モード動作Podの再起動利用シナリオ
Off推奨の提供のみ、自動適用なしなしリソース分析、導入初期の段階
InitialPod作成時にのみ推奨値を適用なし (新規Podのみ)安定性を優先するワークロード
Auto推奨値を既存のPodにも適用 (再起動)ありステートレスなワークロード、開発環境
RecreateAutoと同じだが再起動を保証あり明示的な再起動が必要なとき

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'

この設定で注目すべき点は次のとおりです:

VPAの主な制限事項

  1. HPAとの同時利用の制限: VPAを同じメトリクス(CPU/メモリ)でHPAと併用すると衝突が発生します。HPAがCPU使用率をもとにPod数を増やすと同時にVPAがCPU要求量を増やすと、予測できない動作になります。
  2. 最大1,000 Podの制限: VPAが管理するPod数は、クラスタ当たり1,000個を超えないことが推奨されます。
  3. CronJob/Jobは非対応: VPAは長時間実行されるワークロード(Deployment、StatefulSet)でのみ動作します。
  4. JVMワークロード: JVMのヒープメモリは起動時に固定されるため、VPAがメモリを減らしても実際にJVMが使用するメモリは減りません。

KEDA: イベントドリブンなオートスケーリング

KEDAのアーキテクチャ

KEDAはHPAを置き換えるものではなく 拡張 します。KEDAは外部イベントソース(Kafka、Prometheus、Redis、AWS SQSなど)のメトリクスを取得してHPAへ注入する役割を担います。

中核となるCRDは次のとおりです:

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

この設定の主要なパラメータを分析します:

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の比較

項目HPAVPAKEDA
スケーリング方向水平 (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: カスタムメトリクスのみ使用
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

原因と解決:

  1. metrics-serverが未インストール: kubectl get deployment metrics-server -n kube-system で確認
  2. リソース要求が未設定: Podに resources.requests がないとUtilizationを計算できません。必ずrequestの設定が必要
  3. maxReplicasに到達: kubectl get hpa でMAXPODSに到達していないか確認
  4. 未知のメトリクス: 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分かかる

解決策:

事例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

運用チェックリスト

運用環境でオートスケーリングを適用する際に必ず確認すべき項目です:

デプロイ前のチェックリスト

モニタリングのチェックリスト

コスト最適化チェックリスト

おわりに

Kubernetesのオートスケーリングは単一のツールでは解決できません。ワークロードの特性に応じてHPA、VPA、KEDAを適切に組み合わせる必要があります。CPU/メモリにもとづく単純なスケーリングにはHPA v2で十分であり、リソース最適化が必要ならVPAを併用し、イベントドリブンなワークロードやゼロスケーリングが必要ならKEDAが適しています。

特に運用環境では、behavior 設定によるスケーリング速度の制御、PDBとの連携、そしてfallback戦略が重要です。本記事で扱った設定例とトラブルシューティング事例を参考に、安定しながらもコスト効率の高いオートスケーリング戦略を構築していただければと思います。

参考資料

  1. Kubernetes公式ドキュメント - Horizontal Pod Autoscaling
  2. Kubernetes公式ドキュメント - Vertical Pod Autoscaling
  3. KEDA公式ドキュメント - ScaledObject Specification
  4. KEDA公式ドキュメント - Authentication
  5. Kubernetes Autoscaler GitHub - VPA
  6. KEDA Apache Kafka Scaler
  7. Kubernetes HPA Walkthrough

コメント

まだコメントはありません。

ログインするとコメントできます