[Kubernetes]Horizontal Pod Autoscalerの動作を確認する

#はじめに
Horizontal Pod Autoscalerは、CPU負荷などに応じてreplication controller/deployment/replica set /stateful setのreplica数をスケールさせる（増やす）機能です。
今回はこのHorizontal Pod Autoscalerの動作を確認したいと思います。

#（参考）スケールアウトとスケールアップ
サーバーの性能を上げる場合、大きく分けてスケールアウトとスケールアップ2つの方式があります。

##スケールアウト
スケールアウトは「横に広げる」と言ったりもします。一般的にはサーバーの台数を増やすことで負荷を分散し、システムとして「スループット」を上げます。
スループットとは、一定時間内にどれだけプロセスを処理できるかです。

Horizontal Pod Autoscalerは、スケールアウトする機能です。

##スケールアップ
スケールアップは「縦に伸ばす」と言ったりもします。一般的にはCPUを高性能なもの（周波数が高いもの）にすることで「レスポンス」を上げます。
レスポンスとは、1つのプロセスをどれだけ速く処理できるかです。

#前提条件
Horizontal Pod Autoscalerを使用するには、事前にmetrics-serverをデプロイする必要があります。

>metrics-server monitoring needs to be deployed in the cluster to provide metrics via the resource metrics API, as Horizontal Pod Autoscaler uses this API to collect metrics. 

>[Before you begin](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/)

metrics-serverの構築はこちらでも紹介してますので、ご参照ください。
[Kubernetesのmetrics-serverを構築する](https://qiita.com/dingtianhongjie/items/a8ddc2d7f7b57291a13e)

#Deployment/Serviceの作成

まずはDeploymentと、外部と接続するためのServiceとしてここではLoadBalancerを作成します。
以下のマニフェストをapplyします。

```nginx-hpa.yaml
apiVersion: v1
kind: Service
metadata:
  name: load-balancer
spec:
  ports:
  - name: load-balancer
    port: 8080
    protocol: TCP
    targetPort: 80
    nodePort: 30002
  selector:
    app: nginx-dep
  type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-dep
  template:
    metadata:
      labels:
        app: nginx-dep
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: 100m
            limits:
              cpu: 300m
```

```
$ kubectl apply -f nginx-hpa.yaml
service/load-balancer created
deployment.apps/nginx created
$ kubectl get svc
NAME            TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)          AGE
kubernetes      ClusterIP      10.96.0.1       <none>         443/TCP          93d
load-balancer   LoadBalancer   10.97.254.155   10.20.30.150   8080:30002/TCP   26s
$ kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
nginx-65585fc7c9-fb92v   1/1     Running   0          31s
```

#Horizontal Pod Autoscalerの作成
上記で作成したDeploymentに対して、以下のHorizontal Pod Autoscalerを作成します。今回はスケールしやすいように、しきい値を低め（25%）にしました。

```hpa.yaml
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nginx
  minReplicas: 1
  maxReplicas: 10
  targetCPUUtilizationPercentage: 25
```

```
$ kubectl apply -f hpa.yaml
horizontalpodautoscaler.autoscaling/nginx-hpa created
$ kubectl get horizontalpodautoscalers.autoscaling
NAME        REFERENCE          TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
nginx-hpa   Deployment/nginx   0%/25%    1         10        1          59s
```

ちなみに、Deploymentにはlimits/requestsの設定が必要になります。
もし、設定していない場合は以下のようにTARGETSの値が\<unknown>となって、負荷が上がってもスケールしませんのでご注意ください。

```
$ kubectl get hpa
NAME        REFERENCE          TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
nginx-hpa   Deployment/nginx   <unknown>/25%   2         10        2          12m
$ kubectl describe horizontalpodautoscalers.autoscaling nginx-hpa
Name:                                                  nginx-hpa
・・・
Events:
  Type     Reason                        Age                 From                       Message
  ----     ------                        ----                ----                       -------
  Warning  FailedComputeMetricsReplicas  18m (x12 over 21m)  horizontal-pod-autoscaler  invalid metrics (1 invalid out of 1), first error is: failed to get cpu utilization: missing request for cpu
  Warning  FailedGetResourceMetric       72s (x81 over 21m)  horizontal-pod-autoscaler  missing request for cpu
```

#動作確認
では、実際に負荷をかけてスケールするか確認します。

外部からLoadBalancerを経由してコンテナにHTTPのリクエストを無限に送ります。DoS攻撃ですね。。。

```
[client]$ while true; do curl -s http://10.20.30.150:8080 > /dev/null ;done
```

このときの負荷の状況とスケールする様子を別ターミナルで確認します。

```
$ kubectl get horizontalpodautoscalers.autoscaling -w
NAME        REFERENCE          TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
nginx-hpa   Deployment/nginx   0%/25%    1         10        1          2m52s
nginx-hpa   Deployment/nginx   44%/25%   1         10        1          3m33s
nginx-hpa   Deployment/nginx   44%/25%   1         10        2          3m48s
nginx-hpa   Deployment/nginx   40%/25%   1         10        2          4m34s
nginx-hpa   Deployment/nginx   40%/25%   1         10        4          4m49s
nginx-hpa   Deployment/nginx   19%/25%   1         10        4          5m34s
nginx-hpa   Deployment/nginx   18%/25%   1         10        4          6m35s
nginx-hpa   Deployment/nginx   0%/25%    1         10        4          7m36s
nginx-hpa   Deployment/nginx   0%/25%    1         10        4          11m
nginx-hpa   Deployment/nginx   0%/25%    1         10        3          11m
nginx-hpa   Deployment/nginx   0%/25%    1         10        3          12m
nginx-hpa   Deployment/nginx   0%/25%    1         10        1          12m
```

CPU負荷がしきい値の25%を超えたら、replicaを増やして負荷を分散させていることがわかりますね。
負荷をかける側のリソースが足らないため、4つまでしかスケールはしていません。

今回は1Pod/1コンテナですが、複数コンテナがある場合には、Pod内の全てのコンテナの平均値が対象になります。

負荷を止めるとreplicaも減って、最終的には1に戻っています。

Podの増減の様子も別ターミナルで確認しました。
分かりづらいですが、負荷に応じてPodがデプロイされて、負荷を止めると削除されていることがわかります。

```
$ kubectl get pod -w
NAME                     READY   STATUS    RESTARTS   AGE
nginx-65585fc7c9-w8p92   1/1     Running   1          7h7m
nginx-65585fc7c9-fh6vm   0/1     Pending   0          0s
nginx-65585fc7c9-fh6vm   0/1     Pending   0          0s
nginx-65585fc7c9-fh6vm   0/1     ContainerCreating   0          0s
nginx-65585fc7c9-fh6vm   0/1     ContainerCreating   0          1s
nginx-65585fc7c9-fh6vm   1/1     Running             0          6s
nginx-65585fc7c9-xbb9n   0/1     Pending             0          0s
nginx-65585fc7c9-xbb9n   0/1     Pending             0          0s
nginx-65585fc7c9-c56nb   0/1     Pending             0          0s
nginx-65585fc7c9-c56nb   0/1     Pending             0          0s
nginx-65585fc7c9-xbb9n   0/1     ContainerCreating   0          0s
nginx-65585fc7c9-c56nb   0/1     ContainerCreating   0          0s
nginx-65585fc7c9-c56nb   0/1     ContainerCreating   0          1s
nginx-65585fc7c9-xbb9n   0/1     ContainerCreating   0          1s
nginx-65585fc7c9-xbb9n   1/1     Running             0          5s
nginx-65585fc7c9-c56nb   1/1     Running             0          6s
nginx-65585fc7c9-c56nb   1/1     Terminating         0          6m50s
nginx-65585fc7c9-c56nb   0/1     Terminating         0          6m51s
nginx-65585fc7c9-c56nb   0/1     Terminating         0          6m52s
nginx-65585fc7c9-c56nb   0/1     Terminating         0          6m52s
nginx-65585fc7c9-fh6vm   1/1     Terminating         0          8m52s
nginx-65585fc7c9-xbb9n   1/1     Terminating         0          7m51s
nginx-65585fc7c9-fh6vm   0/1     Terminating         0          8m53s
nginx-65585fc7c9-xbb9n   0/1     Terminating         0          7m52s
nginx-65585fc7c9-xbb9n   0/1     Terminating         0          7m53s
nginx-65585fc7c9-xbb9n   0/1     Terminating         0          7m53s
nginx-65585fc7c9-fh6vm   0/1     Terminating         0          8m54s
nginx-65585fc7c9-fh6vm   0/1     Terminating         0          8m54s
```

#Horizontal Pod Autoscalerの動作について
マニュアルを確認しましたので、以下に整理します。英語のマニュアルですので、間違ってたらすみません。

[Horizontal Pod Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/)

##監視間隔
horizontal-pod-autoscaler-sync-periodによって、15秒間隔で監視されています。

##replica数の決定
必要なreplica数は、設定値と現在のメトリクス値の比から、以下の式で決まります。

　必要なreplica数＝ceil[現在のreplica数 * ( 現在のメトリクス値 / 設定値 )]

※ceil:切り上げ

##スケーリングの対象
APIバージョンが現行の安定板であるautoscaling/v1の場合、CPUのみがサポートされています。
ベータ版のautoscaling/v2beta2の場合、メモリやカスタムメトリクスもサポートされています。

#まとめ
今回はHorizontal Pod Autoscalerについて確認しました。
スケールアウトはWebサービスなどの比較的軽い業務を大量に処理するシステムに向いています。Kubernetesのマイクロサービスと相性が良い拡張方法ですね。
マニュアルを読むとAPIバージョンがautoscaling/v2beta2からautoscaling/v1へ変わると、機能や動作が異なるようです。マニフェストの書き方も少し変わっていました。オープンソースは変化が早いので、常に最新情報をチェックしないといけないですね。