Kubernetes上でKEDAを用いたイベント駆動型オートスケーリング:RabbitMQ、Kafka、AWS SQSに応じたPodのスケーリング

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

クイックセットアップ:5分でKEDAをデプロイ

Kubernetes標準のHorizontal Pod Autoscaler(HPA)は、CPUやメモリ使用率に依存するHTTPサービスには効果的に機能します。しかし、非同期キューのワーカー処理では全く事情が異なります。例えば、50,000件の動画エンコードジョブが蓄積しているにもかかわらず、ワーカースレッドのCPU使用率が12%にとどまることがあります。逆に、単一の計算負荷が高いタスクによってCPU使用率が95%に達し、キューが空であるにもかかわらず不要なスケールアウトがトリガーされることもあります。

Kubernetes Event-driven Autoscaling(KEDA)はこの課題を解決します。KEDAはカスタムメトリクスサーバーおよびライフサイクルコントローラーの両方として機能します。外部キューを直接監視し、ジョブが到着するとPodを起動し、キューがアイドル状態になるとデプロイメントをゼロまでスケールダウンして、アイドル状態のワーカーノードにかかるインフラコストを削減します。

1. HelmによるKEDAのインストール

公式のHelmチャートを使用して、任意のKubernetesクラスター(v1.27以上)にKEDAをインストールします。

# KEDAのHelmリポジトリを追加および更新
helm repo add kedacore https://kedacore.github.io/charts
helm repo update

# KEDAを専用の名前空間にデプロイ
helm install keda kedacore/keda \
  --namespace keda \
  --create-namespace \
  --set metricsServer.dnsPolicy=ClusterFirstWithHostNet

2. インストールの確認

オペレーターPodとメトリクスAPIサーバーPodの両方が稼働していることを確認します。

kubectl get pods -n keda
# 出力例:
# NAME                                               READY   STATUS    RESTARTS   AGE
# keda-operator-7b6494cb68-x28qf                     1/1     Running   0          45s
# keda-operator-metrics-apiserver-585474775d-n4vwx   1/1     Running   0          45s

コアコンセプトと本番向けスケーラー

KEDAは主に2つのカスタムリソース定義(CRD)を利用します。

  • ScaledObject: イベントソース(RabbitMQやSQSなど)をDeploymentまたはStatefulSetにマッピングし、レプリカ数の最小値・最大値や目標しきい値を定義します。
  • TriggerAuthentication: 認証情報、接続文字列、クラウドIAMロールをデプロイメントマニフェストから分離して管理します。

0から1へのスケーリングはKEDAが直接処理します。最初のPodが起動すると、KEDAはメトリクスを標準のKubernetes HPAに引き渡し、1からNレプリカへのスケールアウトを管理します。

1. RabbitMQのキュー長に応じたスケーリング

image-tasksというRabbitMQキューからジョブを処理する画像リサイズサービスを例に挙げます。目標は、バックログメッセージ30件ごとに1つのPodを割り当て、最大レプリカ数を20に制限することです。

apiVersion: v1
kind: Secret
metadata:
  name: rabbitmq-credentials
  namespace: default
type: Opaque
data:
  # 次の文字列のBase64エンコード値: amqp://user:[email protected]:5672/vhost
  host: YW1xcDovL3VzZXI6cGFzc3dvcmRAcmFiYml0bXEuZGVmYXVsdC5zdmMuY2x1c3Rlci5sb2NhbDo1NjcyL3Zob3N0
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: rabbitmq-trigger-auth
  namespace: default
spec:
  secretTargetRef:
    - parameter: host
      name: rabbitmq-credentials
      key: host
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: image-processor-scaler
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: image-processor
  minReplicaCount: 0
  maxReplicaCount: 20
  pollingInterval: 15
  cooldownPeriod: 60
  triggers:
    - type: rabbitmq
      metadata:
        queueName: image-tasks
        mode: QueueLength
        value: "30"
      authenticationRef:
        name: rabbitmq-trigger-auth

2. Apache KafkaのコンシューマーグループLagに応じたスケーリング

Kafkaのスケーリングでは、単純なメッセージ数ではなくコンシューマーグループのLagを対象とします。KEDAはブローカーのパーティションオフセットを照会し、アクティブなコンシューマー全体での未コミットLagの総数を算出します。

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: telemetry-consumer-scaler
  namespace: default
spec:
  scaleTargetRef:
    name: telemetry-consumer
  minReplicaCount: 1
  maxReplicaCount: 16
  pollingInterval: 10
  triggers:
    - type: kafka
      metadata:
        bootstrapServers: kafka-cluster-kafka-bootstrap.kafka.svc:9092
        consumerGroup: telemetry-workers
        topic: device-telemetry
        lagThreshold: "100"
        offsetResetPolicy: latest

3. IAMロール(IRSA)を使用したAWS SQSでのスケーリング

Kubernetes Secretに静的なAWSアクセスキーを保存するのは避けるべきです。Amazon EKS上で実行する場合は、IAM Roles for Service Accounts(IRSA)またはEKS Pod Identityを使用してKEDAを認証します。

apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: keda-aws-sqs-auth
  namespace: default
spec:
  podIdentity:
    provider: aws-eks
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: order-processor-scaler
  namespace: default
spec:
  scaleTargetRef:
    name: order-processor
  minReplicaCount: 0
  maxReplicaCount: 50
  triggers:
    - type: aws-sqs-queue
      metadata:
        queueURL: https://sqs.us-east-1.amazonaws.com/123456789012/order-processing-queue
        queueLength: "10"
        awsRegion: us-east-1
      authenticationRef:
        name: keda-aws-sqs-auth

オートスケーリング動作の微調整

1. Podのフラッピング防止

メッセージの流入がスパイクすると、過剰なオートスケーリングのループが発生しやすくなります。Podが起動し、数秒でキューを処理し尽くして直ちに終了し、次のバッチが到着した際に再び起動するという挙動です。ScaledObject内でHPAの動作を直接調整することで、スケールダウンを安定化させることができます。

spec:
  advanced:
    horizontalPodAutoscalerConfig:
      behavior:
        scaleDown:
          stabilizationWindowSeconds: 300
          policies:
            - type: Percent
              value: 10
              periodSeconds: 60
        scaleUp:
          stabilizationWindowSeconds: 0
          policies:
            - type: Percent
              value: 100
              periodSeconds: 15

2. キュートリガーとCPUフォールバックの組み合わせ

KEDAは単一のワークロードに対して複数のトリガーを設定する複合トリガーをサポートしています。複数のメトリクスを定義した場合、コントローラーは各トリガーの目標値を個別に計算し、最も高いレプリカ数を算出したトリガーを採用します。

spec:
  triggers:
    - type: rabbitmq
      metadata:
        queueName: high-priority-jobs
        mode: QueueLength
        value: "10"
      authenticationRef:
        name: rabbitmq-trigger-auth
    - type: cpu
      metadata:
        type: Utilization
        value: "75"

本番運用における注意点と落とし穴

イベント駆動型ワークロードを大規模に運用すると、いくつかの運用上の落とし穴が見えてきます。本番稼働前に以下の3つのルールを確認してください。

1. Kafkaパーティション数と最大レプリカ数の整合

Kafkaのパーティションは、同一コンシューマーグループ内では1つのコンシューマーからしか読み取ることができません。トピックに12個のパーティションしかない状態でmaxReplicaCount: 30を設定した場合、18個のPodは全く作業を行わないままCPUとメモリを消費することになります。maxReplicaCountはパーティションのキャパシティに合わせて上限を設定してください。

2. スケールダウン時の処理中ジョブの保護

Kubernetesはレプリカを終了する際にSIGTERMシグナルを送信します。ワーカーが60秒かかるバッチ処理の40秒目を実行している最中に強制終了されると、接続の切断や処理の重複が発生します。

PodスペックでterminationGracePeriodSecondsを延長し、ワーカーアプリケーション側でシグナルを適切に捕捉してハンドリングできるように実装してください。

spec:
  template:
    spec:
      terminationGracePeriodSeconds: 120
      containers:
        - name: worker
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "touch /tmp/draining && sleep 5"]

3. ブローカーへのポーリング負荷の監視

KEDAは定期的にキューのエンドポイントをポーリングします(デフォルト: 30秒)。ポーリング間隔を2秒に設定したScaledObjectリソースを200個デプロイすると、ブローカーに対して毎秒100回のHTTPまたはAMQP管理クエリが発生します。1秒未満の極めて厳格なキュー応答が求められる場合を除き、ポーリング間隔は15〜30秒に維持することをおすすめします。

Share: