KubernetesのPodとNodeのAuto Scalingについて


# PodのHorizontal Pod Autoscaling(水平podオートスケーリング)


podのHorizontal pod autoscaling(水平podオートスケーリング)とは、podの負荷に応じて自動的にpodの数を増減させることを指す。
`HorizontalPodAutoscaler`リソースを定義することで水平オートスケーリングが可能になる。

まずDeployment, ReplicaSet, ReplicationController, StatefulSetは`scaled resource object`と呼ばれる。
これらがauto scalingが可能なリソース。

`HorizontalPodAutoscaler`リソースの定義例: 

```yaml
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: php-hpa
  namespace: default
spec:
  scaleTargetRef: # ここでautoscale対象となる`scaled resource object`を指定
    apiVersion: apps/v1
    kind: Deployment
    name: php-deploy
  minReplicas: 1 # 最小レプリカ数
  maxReplicas: 5 # 最大レプリカ数
  metrics:
  - type: Resource
    resource:
      name: cpu
      targetAverageUtilization: 50 # CPU使用率が常に50%になるように指定
```

autoscalingプロセスは以下の3つで行われる。

* 指定したscaled resource objectによって管理されている全てのpodのメトリックを取得する
* メトリックを定義されたターゲット値に近づけるためにはどのくらいのpodが必要か計算する
* scaled resource objectのreplicas fieldを更新する。


ポイントとしては、**auto scalingはtarget valueに近づくようにpod数が調整されるということ。**
target valueが80なら 全体(全podの合計)のCPU使用率が80%になるように調整される。たとえば全体のCPU使用率が90%ならpod数は増えるし、**50%ならpod数は減らされる。**

スケールアウトのための計算方法は以下 (詳しくは[Autoscaling Algorithm] (https://github.com/kubernetes/community/blob/master/contributors/design-proposals/autoscaling/horizontal-pod-autoscaler.md#autoscaling-algorithm)を参照)

```
必要なレプリカ数 = ceil(sum(Podの現在のCPU使用率) / targetAverageUtilization)
```


重要な点は、定期的(30秒に1回)にメトリックの取得をしているため、すぐにはスケーリングが行われないということ。少し時間がかかる。

* スケールアウト(pod数を増やす)イベントは3分おきに発生する。
* スケールイン(pod数を減らす)イベントは5分おきに発生する。


## 例

オートスケールされやすいようにCPUを100%使うようなpodを作成する

```yaml
$ cat hpa-test-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hpa-test-deploy
spec:
  replicas: 3
  template:
    metadata:
      name: hpa-app
      labels:
        app: hpa-app
    spec:
      containers:
      - image: busybox
        command: ["dd", "if=/dev/zero", "of=/dev/null"] # CPUを100%使うように
```

```
$ kubectl apply -f hpa-test-deploy.yaml
deployment "hpa-test-deploy" created
```

3つpodができていることを確認

```
$ kubectl get pod
NAME                               READY     STATUS    RESTARTS   AGE
hpa-test-deploy-69487b74dc-bfk6s   1/1       Running   0          9s
hpa-test-deploy-69487b74dc-rc4k2   1/1       Running   0          9s
hpa-test-deploy-69487b74dc-zs2j7   1/1       Running   0          9s
```

しばらくするとtopコマンドでメトリックスが取得可能状態になる

```
$ kubectl top pod
NAME                               CPU(cores)   MEMORY(bytes)
hpa-test-deploy-69487b74dc-rc4k2   945m         0Mi
hpa-test-deploy-69487b74dc-zs2j7   957m         0Mi
hpa-test-deploy-69487b74dc-bfk6s   950m         0Mi
```

CPU使用率を50%に保つHorizontalPodAutoscalerリソースを作る

```yaml
$ cat hpa-test.yaml
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: hpa-test
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hpa-test-deploy
  minReplicas: 1
  maxReplicas: 5
  metrics:
  - type: Resource
    resource:
      name: cpu
      targetAverageUtilization: 50
```

```
$ kubectl apply -f hpa-test.yaml
horizontalpodautoscaler "hpa-test" created
```

すぐにはメトリックスを取得しないので最初はTARGETSの左が<unknown>になる

```
$ kubectl get HorizontalPodAutoscaler hpa-test
NAME       REFERENCE                    TARGETS           MINPODS   MAXPODS   REPLICAS   AGE
hpa-test   Deployment/hpa-test-deploy   <unknown> / 50%   1         5         0          27s
```

しばらくするとTARGETSの左にメトリックスが表示される

```
$ kubectl get HorizontalPodAutoscaler hpa-test
NAME       REFERENCE                    TARGETS      MINPODS   MAXPODS   REPLICAS   AGE
hpa-test   Deployment/hpa-test-deploy   966% / 50%   1         5         3          30s
```

describeしてみると、Events欄にSuccessfulRescaleと表示され、pod数が5つになったことが分かる

```yaml
$ kubectl describe HorizontalPodAutoscaler hpa-test
Name:                                                  hpa-test
Namespace:                                             default
Labels:                                                <none>
Annotations:                                           kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"autoscaling/v2beta1","kind":"HorizontalPodAutoscaler","metadata":{"annotations":{},"name":"hpa-test","namespace":"default"},"spec":{"max...
CreationTimestamp:                                     Fri, 20 Apr 2018 22:38:53 +0900
Reference:                                             Deployment/hpa-test-deploy
Metrics:                                               ( current / target )
  resource cpu on pods  (as a percentage of request):  966% (966m) / 50%
Min replicas:                                          1
Max replicas:                                          5
Conditions:
  Type            Status  Reason            Message
  ----            ------  ------            -------
  AbleToScale     True    SucceededRescale  the HPA controller was able to update the target scale to 5
  ScalingActive   True    ValidMetricFound  the HPA was able to succesfully calculate a replica count from cpu resource utilization (percentage of request)
  ScalingLimited  True    TooManyReplicas   the desired replica count was more than the maximum replica count
Events:
  Type    Reason             Age   From                       Message
  ----    ------             ----  ----                       -------
  Normal  SuccessfulRescale  3s    horizontal-pod-autoscaler  New size: 5; reason: cpu resource utilization (percentage of request) above target
```

podの数を見てみると5つ(HorizontalPodAutoscalerで指定したMAXのpod replica数)になっていることが分かる

```
$ kubectl get pod
NAME                               READY     STATUS    RESTARTS   AGE
hpa-test-deploy-69487b74dc-6sp7l   1/1       Running   0          10s
hpa-test-deploy-69487b74dc-bfk6s   1/1       Running   0          2m
hpa-test-deploy-69487b74dc-d69mt   1/1       Running   0          10s
hpa-test-deploy-69487b74dc-rc4k2   1/1       Running   0          2m
hpa-test-deploy-69487b74dc-zs2j7   1/1       Running   0          2m
```


# 他のメトリックスによるAuto Scaling

`custom metrics`を用いてCPUメトリックス以外のメトリックスでオートスケーリングを行うことができる。
`HorizontalPodAutoscaler`リソースには以下の4種類を指定することができる。

* Resource 
    * Resource Requests, Resource Limitsのようなメトリックス
* Pods
    * Podのメトリックス。例えばQueries Per Second(QPS) や message broker’s queueやコネクションの数。
* Object
    * Podとは直接関係ないメトリックス。たとえばIngressのHit/sやlatency。
* External
      * Kubernetes外のメトリックス(例: LBのQPS、Cloud Pus/Subの溜まっているメッセージ数)

# どんなメトリックスがAuto Scalingに適しているか判断する

- podのメモリ消費量はオートスケーリングメトリックスに向かない。
  podが増えるほど全体のメモリ消費量は増え、使えるメモリ量が逆に減るからだ。
- メトリックスの一つとして、Queries per Second (QPS)がある。(※Requests per Secondと同義か？)
- オートスケーリングメトリックスとして`custom metrics`を使う前に、podが増減したらそのメトリックスがどのように振る舞うかよく考えること。

# PodのVertical Pod Autoscaling(垂直podオートスケーリング)

Vertical Pod Autoscalerは、コンテナに割り当てるCPU、メモリのリソースの割り当てを自動的にスケールさせるリソースです。
必要とするリソース（CPU、メモリ）量があらかじめ推測しにくいアプリケーションに対して、実績に基づいてそれらしい値を決めたい場合に効果を発揮します。

[猫でもわかる Vertical Pod Autoscaler](https://ccvanishing.hateblo.jp/entry/2018/10/02/203205) がわかりやすいです。


# Nodeの水平オートスケーリング

Kubernetesは、cloud providerが提供していれば、nodeのauto scalingもでき、Cluster Autoscalerという機能が担っている。
Cluster Autoscalerは、リソースが枯渇して既存のnodeにpodがschedulingできない場合に、nodeを追加する。
またリソース負荷が低いならnodeの削除も行う。
Cluster Autoscalerはnodeを追加する前にそのnodeにpodがscheduleできるかを確認する。じゃないとnodeを追加してもpodがscheduleできない事態になってしまう。

nodeを削除するとき(scale downするとき)system podsが実行されているかチェックし、されているならnodeの削除を行わない。ただしsystem podsがDaemonSetの場合を除く。DaemonSetは各nodeで実行されているためnodeからpodを削除しても問題ないため。
また unmanaged pod(replicasetやdeploymentによって管理されていないpod)やlocal storageを使っているpodが存在する場合もnodeを削除しない。
nodeを削除するさいには、nodeにunschedulableを設定する。これによりpodがscheduleされなくなる。
そしてnode上のpodが追い出される。
追い出されたpodはreplicasetやdeploymentの管理下なので他のnodeへ再scheduleされる。

## Tips

* `kubectl cordon <node>`でnodeにunschedulableを設定できる。unschedulableが設定されたnodeにはpodがscheduleされなくなる。
* `kubectl drain <node>`でnodeにunschedulableを設定し、node上のpodを追い出す。
* `kubectl uncordon <node>`を実行すればnodeのunschedulableを解除できる。

# cluster scale down時にサービスの中断を抑える

nodeが突然死んだ場合はどうしようもないが、手動もしくはCluster Autoscalerでnodeの削除が行われる場合は、サービスを中断させないことができる。
`PodDisruptionBudget`リソースを作成すればよい。
参考: https://qiita.com/tkusumi/items/946b0f31931d21a78058
「`PodDisruptionBudget` とは Node を計画的に停止したい場合に、Pod の状況を見ながら退去 (evict) させる機能です」

# まとめ

* podの水平スケーリング(scale out)はHorizontalPodAutoscalerリソースを作成し、それをDeployment, ReplicaSetに指定し、対象CPU使用量を指定することで簡単にできる。
* podのCPU使用量の他に、アプリケーションが提供するcustom metricsやclusterにdeployされている他のリソースのmetricsをもとにオートスケーリングすることができる。
* (cloud providerから提供されていれば)cluster nodeもオートスケーリングできる。

# 参考

* [Kubernetes in Action](http://amzn.asia/80rSDOx) (最高に分かりやすく書かれているのでぜひ買いましょう)
* [Horizontal Pod Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/)
* [Horizontal Pod Autoscaler Walkthrough](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/)