Kuberentesの水平オートスケール(HPA v2)

# はじめに

Kubernetes 1.23でついにHorizontail Pod Autoscaler(HPA) v2がGAになります。:tada:
それを記念？して昔[書いたHPA v1がメインの記事](https://qiita.com/shmurata/items/8ebaf74028365fc950c9)をベースにv1(v2beta1)との違いを見ながらHPA v2版のHPAの紹介しようと思います。
(まだたまに見られているようで情報が古くて心苦しくなるというのもあります😅 )

# Horizontal Pod Autoscalerとは

Horizontal Pod AutoscalerはDeployment, ReplicaSet, StatefulSetなどのScaleサブリソースを保持しているリソース[^1]を対象に設定した内容とメトリクスを元に計算した値にPodの数を調整します。
このスケールの判定に利用するメトリクスはCPUやメモリ、カスタムメトリクスなど様々なメトリクスを利用することができます。
Horizontal Pod AutoscalerはKubernetes APIオブジェクトとコントローラとして実装されています。このコントローラは定期的にユーザによって設定されたしきい値とメトリクスの値を比較しPodの数を調整します。

:pencil: **これは要はHPAで調整できるのはPodの数のみということを意味しています。Node自体のスケールやPodのリソースサイズの増減などは別の仕組み(cluster-autoscaler, VerticalPodAutoscalerなど)を利用する必要があるのは注意してください。**

[^1]: v1のときは特定のリソースのみでしたが、どの時点からかは忘れましたがScaleサブリソースがあればどのリソースでも対象にすることができ、CRDで作成したカスタムリソースも対象にすることができるようになりました。

## Horizontal Pod Autoscalerの動き

Horizontal Pod Autoscalerのコントローラ(HorizontalPodAutoscalerController)はControllerManagerにあるコントローラの一つとして実装されていて、デフォルトではコントロールループ(メトリクスの値を収集してReplica数を調整する処理のループ)は15秒間隔[^2]で周期しています。この間隔はControllerManagerの`--horizontal-pod-autoscaler-sync-period`フラグで変更することができます。

このループの中でHPAオブジェクトに指定された設定をもとにメトリクスを集計し、その値をもとにReplica数を計算します。
詳しいアルゴリズムは https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#algorithm-details に記載されています。

具体的な計算式は以下のとおりですが、いくつかのメトリクスタイプではPodの平均値を利用して計算します。

```
desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]
```

[^2]: 昔は30秒でしたが、スケール速度が課題となって短くなったと記憶しています

## API version

HorizontalPodAutoscalerは現在までに以下のバージョンが公開されていて、各バージョンでspecや挙動が異なるので注意してください。
HPAは`autoscaling` API Groupのオブジェクトとなっており、以下のAPI Versionが公開されています。

- v1
- v2beta1
- v2beta2
- v2 (v1.23.0から追加予定)

特別な事情がなければ最新のバージョンであるv2またはv2beta2を利用することをおすすめします。

## Horizontal Pod Autoscalerが扱えるメトリクスタイプ

現在利用できるタイプは以下のとおりです。[^3]

- `PodResource`: 各 Pod のリソースのメトリクス(v2beta2では`Resource`)
    - 例: PodのCPU、メモリ使用率など
    - このケースの場合`pod.spec.containers.resource.request`が指定されていないと機能しないため設定が必須
    - 値は目標値と比較する前に平均化されます
- `Object`: Kubernetes Objectに関連したメトリクス
    - 例: Ingressの秒間リクエスト数など
- `Pods`: 現在のスケールターゲットの各Pod単位で生成されるメトリクス
    - 例: 秒間処理数など
    - 値は目標値と比較する前に平均化されます
    - :memo: リソース使用量以外のPod単位で出力されるメトリクスなどを利用する場合に利用するようです。
- `External`: どのKubernetesオブジェクトにも関連付けられていないグローバルメトリクス
    - 例: メッセージングサービスのキューの長さや、クラスタ外のロードバランサーからのQPSなど
    - クラスタ外のコンポーネントからの情報に基づいてオートスケーリングを行うことができます
- `ContainerResource`: 各コンテナのリソースのメトリクス[^4]
    - 例: コンテナのCPU、メモリ使用率など
    - このケースの場合`pod.spec.containers.resource.request`が指定されていないと機能しないため設定が必須
    - 値は目標値と比較する前に平均化されます
    - :memo: Pod単位だとサイドカーなどのコンテナのリソースがノイズになってしまうことがあるため、コンテナ単体のリソースを対象にできるように最近追加されました

これらのメトリクスを１つ以上指定する必要があります。複数指定した場合はすべてのメトリクスタイプで計算が行われそのうち**最大値**が採用されます。
このとき一部のメトリクスが取得できない場合でも最大サイズに基づいてスケールアップは行いますが、スケールダウンは行いません。

[^3]: このファイルで定義されています。https://github.com/kubernetes/kubernetes/blob/bd8e8507093095bbe411fd501ea5971f5b077b3b/staging/src/k8s.io/api/autoscaling/v2/types.go#L97
[^4]: まだ alpha 扱いのようです。KEP: https://github.com/kubernetes/enhancements/tree/master/keps/sig-autoscaling/1610-container-resource-autoscaling

## HorizontalPodAutoscalerControllerがメトリクスを収集する方法

以下の３つの方法が用意されています。

- metrics-server[^5] から収集する方法
- Custom Metrics API: https://github.com/kubernetes/design-proposals-archive/blob/main/instrumentation/custom-metrics-api.md
- External Metrics API: https://github.com/kubernetes/design-proposals-archive/blob/main/instrumentation/external-metrics-api.md

これらのAPIはKuberneteのapiserverにあるaggregation layerというapiserverでは提供していないAPIをapiserverに組み込む機能を使用してapiserverからメトリクスを収集できるようにする必要があります。[^6]

具体的にはapiserverのいくつのフラグを設定して、`APIService`というリソースを追加し、APIを公開するServiceへリクエストを流すように設定します。
例えばmetrics-serverであれば https://github.com/kubernetes-sigs/metrics-server/blob/master/manifests/base/apiservice.yaml といったリソースを追加しています。

カスタムメトリクスであれば Prometheusのメトリクスをカスタムメトリクスとして利用できるようにするためのアダプタ([Prometheus Adapter](https://github.com/kubernetes-sigs/prometheus-adapter))があります。
このアダプタでは https://github.com/kubernetes-sigs/prometheus-adapter/blob/v0.9.1/deploy/manifests/custom-metrics-apiservice.yaml といったリソースを追加しています。
Prometheus Adapter の詳しい利用方法は@Ladicleさんがまとめた記事がありますので、そちらを参照してみてください。https://qiita.com/Ladicle/items/5ff251b89df2f1ebb821

[^5]: 以前はheapstarを利用していましたが、廃止され[metrics-server](https://github.com/kubernetes-sigs/metrics-server)を利用することが推奨されています。
[^6]: https://kubernetes.io/docs/tasks/extend-kubernetes/configure-aggregation-layer/

## 利用方法

メトリクスタイプごとに利用方法が異なります。ここでは一番標準的なPodResourceタイプのメトリクスでの利用方法を紹介します。
とはいっても公式ドキュメントに詳しい説明があるので、詳細な説明はそちらを見てもらえると良いと思います。
https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/

そこから抜粋して簡単な利用方法のみを紹介します。
公式ドキュメントの例だと簡単なPHPで作成されたWebサービスが1つのDeploymentとServiceで作成されています。このDeploymentをすべてのPodがCPU　５０％程度になるようにHPAを作成します。また、スケールしすぎても困るのでPodの最大値は10と制限をつけたいと思います。

HPAの設定をするだけであれば、metrics-serverが既にデプロイされている環境なら以下のコマンドまたはHorizontalPodAutoscalerオブジェクトを作成するだけで利用することができます。(スケールするサービスがデプロイ済みの前提です)

```
$ kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
```

```yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: PodResource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50
```

以上で終了です。簡単な負荷をかけてみればPodの数が上下することが体験できますので試してみたい方は公式ドキュメントを参考に負荷をかけてみると良いと思います。

:memo: このように設定すること自体はとても簡単にできますが、最適な設定値を見つけるのは容易ではありません。これについてはベストプラクティスなどは筆者も知らず、地道に負荷テストや実際のメトリクスなどを見ながら調整を重ねていくしかないでしょう。

## スケールの振る舞いの調整

Kubernetes 1.18からHPAに`hehaivor`フィールドが追加されています。これはこれまではスケールアップやダウンの頻度や間隔などの調整はKubernetes全体でしか設定できませんでしたが、HPAのspecに記述できるようになり、HPA単位で調整できるようになりました。
これによりアプリケーション単位でスケール速度の調整を行えるようになります。この設定をしない場合はControllerManagerに設定されたクラスタ全体のでデフォルト値が採用されます。

設定方法や詳細は背景などを確認したい場合は公式ドキュメントやKEPを参照してみてください。

公式ドキュメント: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#support-for-configurable-scaling-behavior
KEP: https://github.com/kubernetes/enhancements/tree/master/keps/sig-autoscaling/853-configurable-hpa-scale-velocity

## v2beta2からv2の変更点

(筆者もまだ実際には試せていないのでドキュメントやissueの内容からの推測のまとめになります。誤っている箇所があるかもしれませんのでご留意ください。)

HPA v2beta2からv2の変更点はあまり多くなくspecの変更は以下の点のみです。

- メトリクスタイプ(`spec.metrics.type`)の`Resource`が`PodResource`に変更されました
- `Disabled`から`ScalingDisabled`に変更されました
- behaviorのselectPolicy(`spec.behavior.scaleUp/Down.selectPolicy`)の値が次のように変更されました
  - `Max` -> `MaxChange`
  - `Min` -> `MinChange`

該当するspecを利用している場合は上記のように変更し、バージョンをv2に変更すれば移行は完了するでしょう。
変更されたのはspecの設定値だけで、HPA自体の振る舞いなどに変更はないようです。

詳しい内容は KEP を参照してみてください。
KEP: https://github.com/kubernetes/enhancements/issues/2702