Skip to main content

HPAのレプリカ数が数分おきに増減する。原因はメトリクスではなくscaleDownスタビライゼーションウィンドウの設計不足だった

本番K3sクラスタで、HPAのレプリカ数が5分おきに2↔6を繰り返した

ある本番のK3sクラスタで、HPA(HorizontalPodAutoscaler)を有効にしたワークロードのレプリカ数が、5分おきに2台と6台の間を行き来する現象が発生した。ダッシュボードを見る限り、リクエスト数もCPU負荷も横ばいで、明らかな異常は見当たらない。それなのにレプリカ数だけが波打つように増減し続けていた。

section01

これは典型的な「HPAのフラッピング(scaling flap)」と呼ばれる現象だ。原因調査を進めると、メトリクス取得自体には何も問題がなく、HPAのbehaviorフィールドを一度も明示的に設定していなかったことが根本原因だと分かった。デフォルトのscaleUp/scaleDown挙動が想定より攻撃的だったのだ。

この記事では、実際に起きた3つの原因(behaviorの非対称なデフォルト値、CPU requestsの分母問題、メトリクス同期間隔による遅延の積み上げ)を分解し、K3s環境での具体的な修正手順を解説する。こうした挙動の癖は自前でダッシュボードを組んで初めて気づくことが多いが、KuboのCaptain UIのようにスケーリング推移が最初から可視化されていれば、気づくまでの時間を大きく縮められる。

原因1: scaleUp/scaleDownのデフォルト値が非対称なのに、behaviorを何も設定していなかった

Kubernetes公式ドキュメントによれば、HPAのscaleUpとscaleDownにはそれぞれ異なるデフォルトの安定化ウィンドウ(stabilizationWindowSeconds)が設定されている。

  • scaleUp: 安定化ウィンドウ 0秒(即座)、selectPolicyはMax(最も積極的な推奨値を採用)
  • scaleDown: 安定化ウィンドウ 300秒(5分)、selectPolicyはMax

section02

つまり、負荷が上がった瞬間にレプリカ数は即座に増える一方、負荷が下がってもすぐには減らない。ここまでは意図された「安全側」の設計だ。しかし、behaviorフィールドを何も設定していない状態で負荷がわずかに上下し続けると、scaleUpの「即座」な反応がscaleDownの「5分待ち」の効果を上書きし続け、レプリカ数がじわじわと振動する。

stabilization windowの本当の役割

stabilization windowは単純な「遅延タイマー」ではない。公式ドキュメントによれば、ウィンドウ内に記録された過去の推奨レプリカ数の中から「最も保守的な値」を選ぶ仕組みだ。ウィンドウが短すぎると、この「保守的な値」を選ぶための材料が少なくなり、結果として振動を抑えきれなくなる。

原因2: CPU requestsを"最適化"したことが、逆に過敏なスケーリングを招いていた

コスト削減の一環として、Podのresources.requests.cpuを実測値に合わせて絞り込むチューニングを行うチームは多い。だが、これがHPAのフラッピングを悪化させることがある。

ScaleOpsのブログが指摘する「パーセンテージ計算問題」がまさにこれだ。HPAのCPU使用率は次の式で計算される。

text
utilization = 現在のCPU使用量 ÷ CPU requests × 100

section03

ワークロードの実際の消費量が変わっていなくても、requestsを200mから100mに最適化した瞬間、分母が半分になるため見かけの利用率は75%から150%に跳ね上がる。HPAはこれを「負荷が急増した」と誤認し、不要なスケールアウトを引き起こす。コスト削減のためのrequests最適化が、皮肉にもスケーリングの不安定化を招く典型例だ。

原因3: metrics-serverとHPAの同期間隔が遅延を積み上げていた

K3sには標準でmetrics-serverがバンドルされており、kubectl topコマンドやHPAのCPU/メモリメトリクスを支えている。この構成自体は便利だが、遅延の仕組みを理解しておく必要がある。

metrics-server公式リポジトリのFAQによれば、metric-resolutionのデフォルト値は60秒だが、kubeletが計算するメトリクス自体の解像度が15秒であるため、15秒未満に設定することは推奨されていない。多くの本番環境ではmetric-resolutionを15秒に設定して運用している。

一方、HPAコントローラー自身にも同期間隔があり、kube-controller-managerのリファレンスで定義される--horizontal-pod-autoscaler-sync-periodのデフォルトは15秒だ。metrics-server公式サイトでも、このスクレイプ間隔とHPAのポーリング間隔が独立して動いていることが説明されている。

section04

つまり、負荷が急増してからHPAが実際に「気づく」までに、単純計算でも15〜30秒の遅延が積み上がる。この遅延の間にも負荷が細かく上下していれば、HPAは常に「少し前の、既に古くなった状態」を見て判断することになり、フラッピングをさらに助長する。

対処: behaviorに明示的なpolicies・selectPolicyを設定してフラッピングを止める

MeteorOpsのブログが示す実務的な推奨値を元に、K3s上のHPAマニフェストを次のように修正した。

section05

yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: example
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: example
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 65
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
      - type: Pods
        value: 2
        periodSeconds: 60
      selectPolicy: Max
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 25
        periodSeconds: 60
      selectPolicy: Min

ポイントは3つある。

  • scaleUpの安定化ウィンドウを60秒に設定: 即座の反応をやめ、短命なスパイクに過剰反応しないようにする
  • scaleDownをPercent 25%に制限: 一度に大量のレプリカを削らず、段階的に減らす
  • selectPolicyをMinに変更: 複数のポリシーが競合したとき、最も保守的な(変化の少ない)値を採用する

あわせて、CPU requestsは実測値に対して安全マージンを持たせ、averageUtilizationの目標を60〜75%程度に設定するのがMeteorOpsの推奨値だ。本番のminReplicasも3以上を確保しておくと、スケールアップの遅延が発生してもベース容量で吸収できる。

設定しただけで安心しない — stabilizationWindowSecondsには既知の挙動の癖がある

behaviorを設定して終わり、ではない。kubernetes/kubernetesのGitHub Issue #101371では、stabilizationWindowSeconds: 300と設定していても、実際には1〜2分でスケールダウンが実行されてしまうケースが報告されている。SIG Autoscalingでバグとして記録されている既知の挙動の癖だ。

つまり、「300秒に設定したから安全」という思い込みだけでは不十分で、実際の挙動を監視ダッシュボードで継続的に確認する必要がある。設定と実測は別物だと考えておいたほうがいい。

KuboのCaptain UIを使えば、レプリカ数の推移をリアルタイムのグラフで可視化できるため、「設定通りに動いているか」を目視で確認しやすくなる。ダッシュボードを自前で構築する手間をかけずに、こうした挙動のズレに早く気づける点は運用上のメリットが大きい。

まとめ

HPAのレプリカ数が振動する原因は、「メトリクスが取れていない」ことよりも「behaviorの設計不足」であることが多い。今回のケースでは、次の3つが絡み合っていた。

  1. scaleUp(0秒・即座)とscaleDown(300秒)の非対称なデフォルト値を、明示的なbehavior設定で上書きしていなかった
  2. CPU requestsの最適化が、utilizationの分母を変え、見かけの利用率を跳ね上げていた
  3. metrics-serverとHPAコントローラーの同期間隔(それぞれ約15秒)が遅延として積み上がっていた

対処としては、stabilizationWindowSeconds・policies(Percent/Pods)・selectPolicyを明示的に設定し、CPU requestsを実測値に合わせて調整することが基本になる。ただし設定して終わりではなく、実際の挙動を監視し続けることが重要だ。

K3sベースのKuboなら、こうしたHPAのbehaviorテンプレートをAI-Driven Deploymentで適用しつつ、Captain UIでスケーリングの推移を可視化できる。EKSやAKSでは月額8万円前後かかる構成でも、Kuboなら料金プランにある通り月額48,000円からフルスペックのKubernetes運用が可能だ。HPAの設定ミスに気づかないまま本番運用を続けるリスクを減らしたいなら、一度お問い合わせから相談してみてほしい。

Related articles

← Back to all posts