従来のCPU・メモリ指標の盲点
Kubernetesクラスターの運用を始めて5年、標準のHorizontal Pod Autoscaler (HPA)には大きな盲点があることに気づきました。多くのチームはCPUやメモリの使用率に基づいてスケーリングを開始します。ポッドの負荷が高ければポッドを増やす、というのは一見論理的に思えます。しかし、サービスがKafkaメッセージを消費したりRedisタスクを処理したりするイベント駆動型の世界では、これらの指標はシステムの実際の負荷状況を正しく反映していないことがよくあります。
この現実に直面したのは、昨年、高トラフィックのデータパイプラインを管理していた時でした。ボトルネックがI/Oに依存していたため、CPU使用率は12%程度で安定していましたが、Kafkaのラグは爆発的に増えていました。CPUがようやくHPAをトリガーするほど上昇した頃には、すでに450万件ものメッセージが滞留していました。システムは後手に回り、遅延が発生し、ユーザーの期待に応えられませんでした。この悩みを最終的に解決してくれたツールが、KEDA (Kubernetes Event-Driven Autoscaling) でした。
KEDAがどのようにギャップを埋めるのか
KEDAは、Kubernetesクラスターと外部イベントソースの間の軽量なブリッジとして機能します。HPAを置き換えるのではなく、むしろ強化するものです。KEDAを使用すると、キューやストリームで待機しているイベントの数に直接基づいてワークロードをスケーリングできます。Kafka、Redis、RabbitMQ、AWS SQSなどの外部システムと通信するために必要なカスタム指標とトリガーを提供します。
実践において、これは信頼性の面で大きなメリットとなります。既存のノード上で、インフラを「サーバーレス」モデルに近づけることができます。処理すべき仕事がない時はゼロまでスケールダウンでき、これによりステージング環境のコストを約40%削減できました。また、ブローカーに大量のデータが届いた際には、KEDAは数秒でデプロイメントを数百のポッドにバーストさせることができます。
コアコンポーネント
- Scaler: KafkaのラグやRedisのリスト長などの指標を取得するコネクタ。
- Metrics Adapter: 外部指標をKubernetes HPAが理解できる形式に変換するコンポーネント。
- ScaledObject: スケーリングのロジック(対象のデプロイメントや閾値など)を定義するカスタムリソース定義 (CRD)。
ハンズオン:実装手順
本番環境を安定させるために使用したセットアップ手順を紹介します。KEDAをインストールし、KafkaとRedisという2つの高トラフィックシナリオ向けに設定を行います。
1. KEDAのインストール
Helmを使うのが最も簡単な方法です。管理ツールをアプリケーションロジックと分離するために、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
2. Kafkaのラグに基づくスケーリング
order-processorという名前のコンシューマーグループがあるとします。ラグ(未処理のメッセージ数)がポッド1つあたり100メッセージを超えた場合に、ポッドを増やしたいと考えます。以下は私が使用したScaledObjectの設定です。
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: kafka-scaledobject
namespace: default
spec:
scaleTargetRef:
name: order-consumer-deployment
minReplicaCount: 1
maxReplicaCount: 20
triggers:
- type: kafka
metadata:
bootstrapServers: kafka-cluster-kafka-bootstrap.kafka.svc:9092
consumerGroup: order-processor
topic: orders
lagThreshold: "100"
offsetResetPolicy: latest
この設定により、KEDAはラグを監視します。もしラグが800メッセージに達し、ポッドが1つしかない場合、KEDAはバックログを解消するために即座に8ポッドまでスケールアップするよう HPAに指示します。この変更により、ピーク時のフラッシュセール中の処理時間を45分から3分未満に短縮することができました。
3. Redisのリスト長に基づくスケーリング
Redisは優れたタスクキューです。LPUSHとRPOPでRedisリストを使用している場合、リストの長さに応じてワーカーをスケーリングできます。これは、バックグラウンドでの画像処理やPDF生成に最適です。
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: redis-scaledobject
namespace: default
spec:
scaleTargetRef:
name: image-worker-deployment
minReplicaCount: 0
maxReplicaCount: 10
triggers:
- type: redis
metadata:
host: redis-master.default.svc.cluster.local
port: "6379"
listName: task_queue
listLength: "20"
type: list
ここで注目すべきはminReplicaCount: 0の設定です。Redisキューが空になると、KEDAはすべてのポッドを削除します。リストに1つでもアイテムがプッシュされると、KEDAは即座にポッドを起動します。これは非常に効率的で、アイドル状態の計算リソースに対する支払いを防ぐことができます。
本番環境から得た教訓
KEDAを6か月運用してみて、デフォルトの30秒というポーリング間隔 (polling interval) は、急激なトラフィックに対しては遅すぎることが多いと分かりました。私たちはこれを10秒に短縮しました。ただし、ポーリングを頻繁に行いすぎると、RedisやKafkaのメタデータAPIに不要な負荷をかける可能性があるため注意が必要です。
クールダウン期間 (cooldown periods) を無視してはいけません。ポッドが短期間に増減を繰り返す「フラッピング (flapping)」現象は避けたいものです。私は通常、scaleDown.stabilizationWindowSecondsを300に設定します。この5分間のウィンドウにより、一度スケールアップした後は、後続のスパイクにも対応できるよう一定期間その状態を維持できます。
最後に、アプリケーションが正常な終了 (graceful shutdowns) を処理できるようにしてください。KEDAがスケールダウンする際、KubernetesはSIGTERMを送信します。もしワーカーが10MBのKafkaメッセージを処理している途中でプロセスを強制終了してしまうと、データの破損や重複処理のリスクが生じます。必ずシグナルをキャッチし、現在のタスクを完了してから終了するようにしてください。
結論
リソースベースのスケーリングからイベント駆動型のスケーリングへの切り替えは、現代のマイクロサービスにとって論理的なステップです。これにより、インフラはCPUの熱だけでなく、実際の「仕事」そのものに応答できるようになります。KafkaストリームでもRedisキューでも、ラグに基づいたスケーリングを行うことで、ユーザーを待たせることなく、クラウドの利用料金を低く抑えることができます。閾値の調整には数週間かかりましたが、それによって本番環境にもたらされた安定性は、費やした時間以上の価値がありました。

