Kuberentes の水平オートスケール

**この記事は HPA v1(v2beta1) ベースとなっており、現在(2021/12)では非推奨の方法となっております。最新の内容は https://qiita.com/shmurata/items/e6bd8c56f3e4f9a8e384 にまとめていますのでこちらを参照ください**

この記事ではKuberentesのオートスケール機能の一つであるHorizontal Pod Autoscalerについてまとめていきます。

# Horizontal Pod Autoscaler

Horizontal Pod AutoscalerはReplicationControllerまたはDeployment, ReplicaSetのPodの数を自動的にスケールします。スケールの判定に利用するメトリクスはCPUやカスタムメトリクスを利用することができます。
Horizontal Pod AutoscalerはKubernetes APIオブジェクトとコントローラとして実装されています。このコントローラは定期的にユーザによって設定されたしきい値とメトリクスの値を比較しReplica数を調整します。

## Horizontal Pod Autoscalerの動き

Horizontal Pod Autoscalerのコントローラ(HorizontalPodAutoscalerController)はControllerManagerにあるコントローラの一つとして実装されていて、デフォルトではコントロールループ(メトリクスの値を収集してReplica数を調整する処理のループ)は30秒間隔で周期しています。この間隔はControllerManagerの`--horizontal-pod-autoscaler-sync-period`フラグで変更することができます。
このコントローラが扱うメトリクスはPod一つの単位で生成されるメトリクス(例:PodのCPU使用率)と全体で一つのメトリクス(例:アプリケーション全体のリクエスト数)があります。Pod一つの単位で生成されるメトリクス(per-pod metrics)には注意が必要で、この場合は一部のPodのメトリクスが取得できない場合は考慮しなければいけません。
HorizontalPodAutoscalerControllerはRequestが設定されていない場合はなんのアクションもしません。詳細は
[autoscalerアルゴリズム](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/autoscaling/horizontal-pod-autoscaler.md#autoscaling-algorithm)に記載されています。

HorizontalPodAutoscalerControllerがメトリクスを収集する方法は2種類あります。一つは直接Heapsterにアクセスして取得する方法で、もう一つはREST clientでアクセスする方法です。直接HeapsterにアクセスするときにはAPIサーバのServiceProxySubResourceを通してアクセスします。このときHeapsterは`kube-system`名前空間で動作するようにデプロイされている必要があります。REST clientでのアクセスについては後述するカスタムメトリクスについてのところで記載します。

## API version

HorizontalPodAutoscalerは`autoscaling` API Groupのオブジェクトです。2018/5/9時点ではCPU使用率のautoscalingのみがstableとなっていて`autoscaling/v1`で提供されています。メモリ使用率によるスケールやカスタムメトリクスでのスケールは`autoscaling/v2beta1`で提供されています。`autoscaling/v2` のリリース時期については現在はまだ[未確定](https://github.com/kubernetes/features/issues/117)のようです。

## 利用方法

Heapsterが既にデプロイされている環境なら以下のHorizontalPodAutoscalerオブジェクトを作成するだけで利用することができます。

```yaml
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: WebFrontend
  namespace: default
spec:
  # オートスケールさせるターゲットを設定
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: WebFrontend
  # downscaleする最小値
  minReplicas: 2
  # upscaleする最大値
  maxReplicas: 5
  # すべてのPodの平均CPU使用率の目標値(％の値を設定する)
  targetCPUUtilizationPercentage: 70
```

ここで重要なのはHorizontalPodAutoscalerControllerはメトリクスを収集してすべてのPodの平均値が指定した値(targetCPUUtilizationPercentage)に近づくようにReplicasを調整するということです。つまり上記の設定であれば平均CPU使用率が70%を超えていたらupscaleし、70%に届かない場合はdownscaleします。Pod数の計算式は以下になります。

```
TargetNumOfPods = ceil(sum(CurrentPodsCPUUtilization) / targetCPUUtilizationPercentage)
```

## 利用する上での注意事項

### ReplicationControllerの場合はローリングアップデートするとHorizontalPodAutoScalerの対象から外れる

現在は殆ど使っている人はいないと思いますがHorizontalPodAutoScalerをReplicationControllerで利用している場合には注意が必要です。`kubectl rolling-update`コマンドなどでReplicationControllerのローリングアップデートを行うと新たなオブジェクトが作成されるためHolizontalPodAutoScalerの対象に新しいオブジェクトはバインドされません。HorizontalPodAutoscalerを利用する場合はDeploymentを利用しましょう。

### スラッシングが発生する場合はControllerManagerのパラメータを調整する

メトリクスは動的に評価される値のため頻繁にupscale/downscaleされることがあります。これをスラッシングというらしいです。これを緩和するためにControllerManagerには`--horizontal-pod-autoscaler-downscale-delay`と`--horizontal-pod-autoscaler-upscale-delay`というパラメータがあります。これらのパラメータはdownscaleまたはupscaleが行われた場合に同じ処理を再度行う際に設定した時間待ってから処理するというものです。downscaleはデフォルト5分、upscaleはデフォルト3分となっています。このパラメータは長くするとその分upscale/downscaleが遅れることになるので調整する場合は注意が必要です。

## v2からサポートされる機能

### マルチメトリクス

複数のメトリクスによるオートスケールが可能になりました。複数設定した場合な各メトリクスに基づいて評価し最大のReplicasが採用されます。
複数設定するには以下のように設定します。

```yaml
kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta1
spec:
  scaleTargetRef:
    kind: Deployment
    name: WebFrontend
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      targetAverageUtilization: 80
  - type: Object
    object:
      target:
        kind: Service
        name: Frontend
      metricName: hits-per-second
      targetValue: 1k
```

各値の詳細などは[こちら](https://github.com/kubernetes/api/blob/master/autoscaling/v2beta1/types.go)に定義があるので参照してください。

### カスタムメトリクス

カスタムメトリクスによるオートスケーリングが可能になりました。
これを利用するためには次の設定をしなければなりません。

- API aggregationの設定
  - APIサーバを経由して任意のサービスにアクセスするための機能です
  - 設定の方法などは[こちら](https://kubernetes.io/docs/tasks/access-kubernetes-api/configure-aggregation-layer/)を参照してください
- ControllerManagerを設定する
  - `--horizontal-pod-autoscaler-use-rest-clients`をtrueにする

minikubeであれば上記は設定済みですので、特に設定などは不要です。カスタムメトリクスサーバは　https://github.com/kubernetes-incubator/custom-metrics-apiserver にサンプルがあり、これをベースに作成すると良さそうですが、今回は予め用意されたサンプルを利用して設定してみます。

内容が若干古いですが https://docs.bitnami.com/kubernetes/how-to/configure-autoscaling-custom-metrics/ にカスタムメトリクスの設定方法が記載されていたのでこの方法を参考にサンプルアプリケーションのカスタムメトリクスによるオートスケーリングを試してみます。

```
# サンプルのマニフェストが含まれるリポジトリを取得
$ git clone git@github.com:luxas/kubeadm-workshop.git
$ cd kubeadm-workshop

# Prometheusのデプロイ
# この例ではPrometheusのメトリクスを利用してオートスケーリングするためPrometheusを用意します
$ kubectl apply -f demos/monitoring/prometheus-operator.yaml
$ kubectl apply -f demos/monitoring/sample-prometheus-instance.yaml.yaml

# カスタムメトリクスサーバのデプロイ
$ kubectl apply -f demos/monitoring/custom-metrics.yaml

# 確認
$ kubectl api-versions
# custom.metrics.k8s.io/v1beta1 が登録されている
$ kubectl get --raw apis/custom.metrics.k8s.io/v1beta1
{"kind":"APIResourceList","apiVersion":"v1","groupVersion":"custom.metrics.k8s.io/v1beta1","resources":[{"name":"namespaces/scrape_duration_seconds","singularName":"","namespaced":false,"kind":"MetricValueList","verbs":["get"]},{"name":"services/scrape_duration_seconds","singularName":"","namespaced":true,"kind":"MetricValueList","verbs":["get"]},{"name":"jobs.batch/scrape_samples_post_metric_relabeling","singularName":"","namespaced":true,"kind":"MetricValueList","verbs":["get"]},{"name":"namespaces/up","singularName":"","namespaced":false,"kind":"MetricValueList","verbs":["get"]},{"name":"jobs.batch/http_requests","singularName":"","namespaced":true,"kind":"MetricValueList","verbs":["get"]},{"name":"namespaces/http_requests","singularName":"","namespaced":false,"kind":"MetricValueList","verbs":["get"]},{"name":"jobs.batch/scrape_duration_seconds","singularName":"","namespaced":true,"kind":"MetricValueList","verbs":["get"]},{"name":"services/scrape_samples_post_metric_relabeling","singularName":"","namespaced":true,"kind":"MetricValueList","verbs":["get"]},{"name":"services/scrape_samples_scraped","singularName":"","namespaced":true,"kind":"MetricValueList","verbs":["get"]},{"name":"namespaces/scrape_samples_scraped","singularName":"","namespaced":false,"kind":"MetricValueList","verbs":["get"]},{"name":"pods/scrape_samples_scraped","singularName":"","namespaced":true,"kind":"MetricValueList","verbs":["get"]},{"name":"pods/http_requests","singularName":"","namespaced":true,"kind":"MetricValueList","verbs":["get"]},{"name":"services/http_requests","singularName":"","namespaced":true,"kind":"MetricValueList","verbs":["get"]},{"name":"pods/scrape_duration_seconds","singularName":"","namespaced":true,"kind":"MetricValueList","verbs":["get"]},{"name":"namespaces/scrape_samples_post_metric_relabeling","singularName":"","namespaced":false,"kind":"MetricValueList","verbs":["get"]},{"name":"pods/scrape_samples_post_metric_relabeling","singularName":"","namespaced":true,"kind":"MetricValueList","verbs":["get"]},{"name":"jobs.batch/scrape_samples_scraped","singularName":"","namespaced":true,"kind":"MetricValueList","verbs":["get"]},{"name":"jobs.batch/up","singularName":"","namespaced":true,"kind":"MetricValueList","verbs":["get"]},{"name":"pods/up","singularName":"","namespaced":true,"kind":"MetricValueList","verbs":["get"]},{"name":"services/up","singularName":"","namespaced":true,"kind":"MetricValueList","verbs":["get"]}]}

# サンプルアプリケーションのデプロイ
$ kubectl apply -f demos/monitoring/sample-metrics-app.yaml

# hpa を確認
$ kubectl get hpa
NAME                     REFERENCE                       TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
sample-metrics-app-hpa   Deployment/sample-metrics-app   866m/100   2         10        2          2h
```

HPAの中身は以下のように設定されています。

```
kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta1
metadata:
  name: sample-metrics-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: sample-metrics-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Object
    object:
      target:
        kind: Service
        name: sample-metrics-app
      metricName: http_requests
      targetValue: 100
```

これはsample-metrics-appサービスのQPSが1Podあたり100QPSになるようにオートスケールが実施されるという設定になります。
実際に負荷をかけてみると以下のようにオートスケールが実施されました。

```
NAME                     REFERENCE                       TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
sample-metrics-app-hpa   Deployment/sample-metrics-app   603/100   2         10        6          2h
```

## 参考

- https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/
- https://github.com/kubernetes/community/blob/master/contributors/design-proposals/autoscaling/horizontal-pod-autoscaler.md#autoscaling-algorithm
- https://github.com/kubernetes-incubator/custom-metrics-apiserver
- https://docs.bitnami.com/kubernetes/how-to/configure-autoscaling-custom-metrics/