1. 「メトリクスは正常」なのにHPAが反応しない、という事故

K3sでHPA(Horizontal Pod Autoscaler)とPrometheus Adapterを組み合わせて運用していると、ある日突然「CPU使用率もカスタムメトリクスも数値上は正常なのに、Podがまったく増えない」という事故に遭遇することがあります。
kubectl describe hpa を実行すると、ターゲットの値が <unknown>/50 のように表示されることがあります。Kubernetes公式ドキュメントによれば、これはPodのresource requestsが未設定、metrics-serverが未導入、あるいは metrics.k8s.io / custom.metrics.k8s.io / external.metrics.k8s.io のいずれかのメトリクスAPIへのクエリが失敗している場合に起きます。
さらに厄介なのは、メトリクスが「古いまま固定される」だけでなく、逆にメトリクス破損が過剰スケールを引き起こすケースもあることです。prometheus-adapterの公式リポジトリに寄せられた実際の障害報告では、CPU・メモリ・カスタムメトリクスの3つを使うHPA構成で、カスタムメトリクスが404を返すようになった結果、HPAが制御不能になりDeploymentが過剰にスケールアップされてしまったという事例が報告されています。「メトリクスが古い」ことと「メトリクスが壊れている」ことは別の障害モードであり、どちらもPrometheus Adapterというメトリクスパイプラインの状態に起因します。
こうしたメトリクス基盤の運用負荷は、自前でPrometheus Adapterを構築・保守している限りついて回ります。KuboのようにPrometheus監視がK3sベースのマネージド基盤に標準搭載されていれば、少なくとも「監視基盤自体の保守」からは解放されます。
2. 犯人はHPAではなく、メトリクスが届くまでの"三層の時差"
この種の事故の多くは、HPA本体のバグではなく、メトリクスが「Prometheus→Prometheus Adapter→HPA」という3つの層を通過する間に生じる遅延の積み重ねが原因です。
- Prometheusのスクレイプ間隔: Prometheus公式ドキュメントによると、
scrape_intervalのグローバル既定値は1分です。ターゲットごとに上書きしない限り、メトリクスはこの間隔でしか更新されません。 - Prometheus Adapterの鮮度パラメータ: prometheus-adapter公式READMEによれば、
--metrics-relist-interval(既定10分)はPrometheusから「どんなメトリクスが存在するか」の一覧を更新する間隔、--metrics-max-age(既定はmetrics-relist-intervalと同値)はアダプターが「このメトリクスはまだ有効だ」とみなすための時間窓です。公式READMEはこの窓を口語的に"discovery interval"とも呼んでいます。 - HPAのポーリング間隔: Kubernetes公式ドキュメントが示す通り、
kube-controller-managerの--horizontal-pod-autoscaler-sync-periodは既定15秒です。K3sでもこの値を明示的に変更しない限りデフォルトのまま動きます。
ここで見落とされがちなのが、HPAは15秒ごとに律儀にメトリクスを見に行っているが、見ている値自体がmetrics-relist-interval/metrics-max-ageの設定時間ぶん古い可能性があるという非対称です。既定値の組み合わせだと、アダプター側の鮮度窓(10分オーダー)がHPAのポーリング間隔(15秒)よりはるかに粗く、瞬間的なトラフィックスパイクに対してはPodが増えるまでに数分単位の遅れが生じても不思議ではありません。
3. discoveryInterval(metrics-max-age)を収集間隔より短くすると何が起きるか

discoveryInterval(実体は--metrics-max-age)の設定で特に注意すべきなのが、Prometheusのスクレイプ間隔との大小関係です。prometheus-adapter公式READMEは明確に「metrics-max-age はPrometheusのスクレイプ間隔と同じか、それ以上にすべき。さもないとメトリクスがアダプターから時々消える」と警告しています。
逆に言えば、discoveryIntervalを必要以上に短く設定していると、Prometheus側のスクレイプがたまたま遅れたタイミングでアダプターのウィンドウからメトリクスが外れ、HPAからは「メトリクスが存在しない」状態に見えてしまいます。prometheus-adapterのconfig.mdが定義する metricsQuery のrate窓(例: rate(http_requests_total[2m]))も同様で、窓が短すぎると瞬間的な欠損に敏感になり、長すぎると平滑化されすぎて反応が遅れるというトレードオフがあります。
K3s環境でPrometheus Operatorやkube-prometheus-stackをデフォルト設定のまま導入していると、この「discoveryIntervalとスクレイプ間隔の大小関係」を意識する機会自体がほとんどありません。だからこそ、HPAが不安定になったときに真っ先に疑うべき設定項目になります。
4. KEDAを足しても速くならない理由

「HPAが遅いならKEDAを入れればいい」という判断は半分だけ正しく、半分は誤解です。KEDA公式のScaledObject仕様によれば、pollingInterval(既定30秒)はKEDAが各トリガーをチェックする間隔で、レプリカ数が0のときはこの間隔でポーリングしてスケール判断を行います。
しかし、レプリカ数が1〜Nの範囲にある間は、KEDAに加えて通常のHPAも定期的にメトリクスを評価しており、その頻度はkube-controller-managerの--horizontal-pod-autoscaler-sync-period(既定15秒)で決まります。つまり1→Nのスケールアップ・ダウン局面では、結局HPAがPrometheus Adapter経由のメトリクスをそのまま見ているため、discoveryInterval/metrics-relist-intervalが引き起こす鮮度問題はKEDAを足しても解消しません。KEDAが追加で担っているのは、あくまで0→1の起動判定という別レイヤーの話です。
ちなみにKubernetesコミュニティでは、外部メトリクス取得が一定時間失敗し続けた場合に静的なフォールバックレプリカ数を使う仕組み(KEP-5679)が議論されていますが、これはあくまで「取得失敗」への対処であり、「取得はできるが値が古い」という本稿のテーマには直接効きません。現状はメトリクスパイプラインの間隔設計を運用側で揃えるしかない、というのが実情です。
KEDAもHPAも結局は自分たちで監視基盤の間隔設計を背負うことになる、という点は見落とされがちです。Kubo CloudならGitOps対応・Helmチャート対応と合わせてPrometheus + Grafanaが標準搭載されており、KEDA/HPAの設定変更もArgoCD/Flux経由でバージョン管理しながら運用できます。
5. K3s/Kubo環境での診断チェックリスト

実際に「HPAが反応しない」事故が起きたときの診断手順を、優先度順に整理します。
- Conditionsを確認する:
kubectl describe hpa <name>を実行し、ターゲット値が<unknown>になっていないか、Conditions欄にエラーが出ていないか確認します。Datadog公式のトラブルシューティングドキュメントが示すように、FailedGetExternalMetricはRBAC設定不足やメトリクスプロバイダーとの接続問題、FailedComputeMetricsReplicasはメトリクスプロバイダー自体が起動していない場合に出やすいエラーです。 - APIServiceの状態を確認する:
kubectl get apiservices | grep metricsでv1beta1.custom.metrics.k8s.io/v1beta1.external.metrics.k8s.ioがAvailable=Trueになっているか確認します。 - メトリクスがAPIに登録されているか直接確認する:
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1で、狙っているメトリクスが一覧に出てくるかを直接見ます。 - 間隔設定を突き合わせる: prometheus-adapterの起動引数
--metrics-relist-interval/--metrics-max-ageと、実際のPrometheusのscrape_intervalを比較します。後者が前者を上回っていればメトリクスが周期的に消える可能性があります。 - resource requestsを確認する: CPU使用率ベースのHPAでは、Kubernetes公式ドキュメントの通り、Podにresource requestsが定義されていないとそもそも使用率が計算できません。
K3sでPrometheus + Grafanaを自前で構築・運用している場合、こうした間隔設定のチューニングと監視自体がSREチームの継続的な運用負荷になります。KuboのようにPrometheus + Grafanaが標準搭載されたマネージドK3s環境であれば、メトリクス基盤そのものの構築・バージョン追従に時間を割く必要がなくなり、こうした「鮮度設計」のチューニングに集中できます。
6. まとめ
HPAが「メトリクス上は正常なのに反応しない」事故の多くは、HPA自体の不具合ではなく、Prometheusのスクレイプ間隔・prometheus-adapterのmetrics-relist-interval/metrics-max-age・HPAのsync-periodという3つの独立した時間設定が噛み合っていないことが原因です。discoveryInterval(metrics-max-age)という一見地味なパラメータこそ、障害調査で真っ先に疑うべき設定だと言えます。
KEDAの導入は0→1のスケール判定を速くしますが、1→Nのフェーズでは結局HPAがPrometheus Adapter経由のメトリクスに依存するため、本稿で扱った鮮度問題そのものの解決策にはなりません。「メトリクスの値」だけでなく「メトリクスが届く速さ」を疑う視点を持つことが、K3s/Kubernetes運用における次のオートスケーリング障害を未然に防ぐ鍵になります。
こうした運用ノウハウを自前で蓄積し続けるコストを下げたいなら、Kubo CloudはPrometheus + Grafanaの監視基盤をK3sベースで標準搭載しており、EKS/AKSの半額以下のコストで本格的なKubernetes運用を始められます。メトリクス周りの設定を一つひとつ手作業で揃えるのではなく、お問い合わせから構成相談をしてみるのも一つの選択肢です。