【KubernetesでPodをスケーリングさせる】その２ - オートスケーリング

## 概要
KubernetesでPodをオートスケーリングをしてみる。
ここではMinikube環境を利用したが、その他の環境の場合は適宜読み替えてください。

手動でスケーリングさせる方法はこちら
[【KubernetesでPodをスケーリングさせる】その１ - 手動スケーリング](https://qiita.com/Esfahan/items/10affb682f4206e90931)

Nodeをスケーリングさせることもできるが、ここでは触れない。
参考：[Kuberentes クラスタのオートスケール](https://qiita.com/shmurata/items/f75ff02a8096e8cfd6cf)

## 目次
- [Kubernetesハンズオン目次](https://qiita.com/Esfahan/items/4fcf51622fed20dffb88)

## 環境
- CentOS 7.6
- Minikube v1.1.1
- Kubernetes v1.14.3

## 関連記事
- [Minikubeを使ってローカルにkubernetes環境を構築](https://qiita.com/Esfahan/items/f5c846088281c39f73a4)
- [KubernetesでDockerコンテナ「php:7.0-apache」を利用してみる](https://qiita.com/Esfahan/items/35b31c6642ad5d375faf)

## Deploymentの仕組み
KubernetesのDeploymentについて事前に理解しておくことを推奨。
[Deploymentの仕組み](https://qiita.com/Esfahan/items/10affb682f4206e90931#deployment%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF)

## 事前準備
### metrics-serverのインストール
metrics-serverを入れないと、以下のコマンドなどが利用できず、リソースのチェックができない。

```shell-session
$ sudo kubectl top node
error: metrics not available yet
```

オートスケーリングをする際にもリソースのチェックをするため、metrics-serverが必要となる。
minikubeのaddonで用意されているので、有効化する。

```shell-session
$ sudo minikube addons enable metrics-server
✅  metrics-server was successfully enabled
```

minikubeを利用していない場合は、以下を参考に入れる。

- https://github.com/kubernetes-incubator/metrics-server
- [Kubernetes 1.12にmetrics-serverをインストール](https://qiita.com/rk05231977/items/64825c095a6ec37e49f8)

ちなみにHeapsterはKubernetes 1.11で非推奨になったらしい。
https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#support-for-metrics-apis
> Fetching metrics from Heapster is deprecated as of Kubernetes 1.11.

これで`kubectl top`コマンドが利用できるようになった。
metrics-serverインストール直後では`error: metrics not available yet`となる可能性があるので、その場合は少し待つ。

```sh
$ sudo kubectl top node
NAME       CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
minikube   92m          1%     2276Mi          14%
# podが存在していない場合は何も返ってこない
$ sudo kubectl top pods
NAME                               CPU(cores)   MEMORY(bytes)
http-deployment-545bd7d765-9ghmf   0m           39Mi
```

## 各種定義
### Deployment

```yaml:deployment.yml
kind: Deployment
apiVersion: apps/v1
metadata:
  name: http-deployment
spec:
  selector:
    matchLabels:
      ########################################################
      # deploymentの.spec.selector.matchLabels.appの値と、
      # 後述のservice.ymlの.spec.selector.appの値を合わせることで、
      # serviceからdeploymentにリクエストを流すことが可能となる。
      ########################################################
      app: http-app
  # podの設定
  # ここでは触れないが、このPodTemlateの部分に変更があったときに、ローリングアップデートが実行される
  template:
    metadata:
      labels:
        app: http-app
    spec:
      containers:
        # Dockerコンテナ名
        - name: http-container
          # 利用するDockerイメージ
          image: php:7.0-apache
          # localイメージ利用。ただし、localに指定したイメージが存在しない場合はレポジトリを参照する
          imagePullPolicy: IfNotPresent
          # Podのリソース定義
          resources:
            # PodをDeployする際に、必要とするリソースを指定できる仕組み。枠外(*1)を参照
            requests:
              memory: 64Mi
              cpu: 250m
            # 該当のPodのリソース使用量の上限を設定することができる。枠外(*2)を参照
            limits:
              memory: 128Mi
              cpu: 500m
          ports:
            # service.ymlで定義する外部公開用port設定の箇所と名前を合わせることでbindされる
            # nameは15文字以内でないとエラーになる
            - name: ha-inner-port
              # コンテナ内のport
              containerPort: 80
          # mountするディレクトリ：コンテナ側の設定
          volumeMounts:
            - name: documentroot
              mountPath: /var/www/html
      # mountするディレクトリ：ホストOS側の設定
      volumes:
        - name: documentroot
          hostPath:
            path: /home/username/containers/web/html
```

#### (*1)Resource Requestsとは
- PodをDeployする際に、必要とするリソースを指定できる仕組み
- Podの利用上限リソースを定義するものではない（そちらはResource Limitを利用する）
- 指定したリソースが確保されるわけではなく、PodをDeployする際に必要なリソースがあるか確認し、不足していればDeployされない
- nodeのリソースはチェックせず、Resource Requestsで指定したリソースしか見ないので、nodeのリソースが不足していてもDeployしようとする。その際は、該当のPodはPending状態となる

#### (*2)Resource Limitsとは
- 該当のPodのリソース使用量の上限を設定することができる
- nodeのリソースに空きがあっても、該当のPodではResource Limitsの内容で制限される
- nodeのリソース上限を超えた設定ができる
    - CPUの場合、処理遅延が起きるだけで、特に問題はない
    - メモリの場合、コンテナのプロセスがKillされる
        - Podの設定で、`restart policy`がAlways、もしくは`OnFailure`が設定されていた場合は、コンテナは自動で再起動される
        - PodのStatusが`CrashLoopBackOff`になっている場合は、コンテナ再起動が繰り返されている可能性があるので要注意。PodのRESTARTSで再起動回数が分かる
    - Resource Limitsを指定しない場合、リソース上限は無制限となる

参考：[KubernetesのResource RequestsとResource Limitsについて](https://qiita.com/sheepland/items/eb0e4c65aaae70ec4e2f)

### Service

```yaml:service.yml
kind: Service
apiVersion: v1
metadata:
  # service名
  name: http-service
spec:
  selector:
    ########################################################
    # 前述のdeploymentの.spec.selector.matchLabels.appの値と、
    # service.ymlの.spec.selector.appの値を合わせることで、
    # serviceからdeploymentにリクエストを流すことが可能となる。
    ########################################################
    app: http-app
  ports:
    - name: ha-outer-port
      # 外部公開するport
      port: 8080
      # deployment.ymlで定義したport名と合わせることでbindされる
      targetPort: ha-inner-port
  type: NodePort
```

### HorizontalPodAutoscaler
オートスケーリングの定義をする。

2019/06現在、apiVersionの最新は`autoscaling/v1`だが、`autoscaling/v2beta1`と書式が異なるので、両方書いておく。

参考：[API version](https://qiita.com/shmurata/items/8ebaf74028365fc950c9#api-version)
> 2018/5/9時点ではCPU使用率のautoscalingのみがstableとなっていてautoscaling/v1で提供されています。メモリ使用率によるスケールやカスタムメトリクスでのスケールはautoscaling/v2beta1で提供されています。autoscaling/v2 のリリース時期については現在はまだ[未確定](https://github.com/kubernetes/features/issues/117)のようです。

#### autoscaling/v2beta1

```yaml:autoscaling.yml
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: http-autoscaling
  namespace: default
spec:
  # オートスケーリング対象を定義
  scaleTargetRef:
    kind: Deployment
    apiVersion: apps/v1
    # 対象のdeployment名を定義する
    name: http-deployment
  # 最小レプリカ数
  minReplicas: 1
  # 最大レプリカ数
  maxReplicas: 5
  # スケーリングをする条件
  metrics:
  - type: Resource
    resource:
      name: cpu
      # CPU使用率が常に10%になるように指定。超えたらオートスケーリングする。
      targetAverageUtilization: 10
```

#### autoscaling/v1
CPU使用率の定義方法が異なる。
`v1`では`metrics`は利用できないため、`v2beta1`の書式でcreateを実行すると、以下のエラーが出る。

```
$ sudo kubectl create -f autoscaling.yml
error: error validating "autoscaling.yml": error validating data: ValidationError(HorizontalPodAutoscaler.spec): unknown field "metrics" in io.k8s.api.autoscaling.v1.HorizontalPodAutoscalerSpec; if you choose to ignore these errors, turn validation off with --validate=false
```

```yaml:autoscaling.yml
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: http-autoscaling
  namespace: default
spec:
  # オートスケーリング対象を定義
  scaleTargetRef:
    kind: Deployment
    apiVersion: apps/v1
    # 対象のdeployment名を定義する
    name: http-deployment
  # 最小レプリカ数
  minReplicas: 1
  # 最大レプリカ数
  maxReplicas: 5
  # スケーリングをする条件
  # すべてのPodの平均CPU使用率の目標値(％の値を設定する)
  targetCPUUtilizationPercentage: 10
```

### その他のAPI Resource(kind)については以下を参照
https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.14/

## 作成

```shell-session
$ sudo kubectl create -f deployment.yml -f service.yml -f autoscaling.yml
deployment.extensions/http-deployment created
service/http-service created
horizontalpodautoscaler.autoscaling/http-autoscaling created
```

### 確認
作成されたことを確認。

```shell-session
$ sudo kubectl get all
NAME                                   READY   STATUS    RESTARTS   AGE
pod/http-deployment-545bd7d765-hmqnh   1/1     Running   0          4s

NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
service/http-service   NodePort    10.108.236.223   <none>        8080:31212/TCP   4s
service/kubernetes     ClusterIP   10.96.0.1        <none>        443/TCP          31h

NAME                              READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/http-deployment   1/1     1            1           4s

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/http-deployment-545bd7d765   1         1         1       4s

NAME                                                   REFERENCE                    TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/http-autoscaling   Deployment/http-deployment   <unknown>/10%   1         5         0          4s
```

## 負荷のかかるプログラムを作成
以下のPHPプログラムを実行して負荷をかけ、オートスケーリングをさせる。
deployment.ymlのvolumesで定義した、コンテナにマウントした領域に設置する。

```php:/home/username/containers/web/html/autoscaling.php
<?php
$max = 1*1000*1000*1000;

$x = 0;
for ($i=0; $i<=$max; $i++) {
    $x += $i;
}

echo $x . "\n";
echo "done\n";
```

## 負荷のかかるプログラムを実行
実行前にPodの数を確認。Podは1つしかない。

```shell-session
$ sudo kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
http-deployment-545bd7d765-hmqnh   1/1     Running   0          18s
```

プログラムを実行する。

```shell-session
$ curl $(sudo minikube service http-service --url)/autoscaling.php
500000000500000000
done
```

### プログラムを実行後の状況を確認
autoscaling.ymlで定義した、最大レプリカ数 `maxReplicas: 5` の通り、Podの数が5つになった。
リソースのチェックは30秒ごとに行っているらしいので、スケールアウトされるまでは少しタイムラグがある。

```shell-session
$ sudo kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
http-deployment-545bd7d765-9wlg7   1/1     Running   0          77s
http-deployment-545bd7d765-bcgqs   1/1     Running   0          77s
http-deployment-545bd7d765-cgnn2   1/1     Running   0          77s
http-deployment-545bd7d765-hmqnh   1/1     Running   0          3m19s
http-deployment-545bd7d765-ww2h2   1/1     Running   0          62s
```

プログラムが停止後、しばらくしたらPodの数が最小レプリカ数 `minReplicas: 1`の通り、Podが1つに戻った。

```shell-session
$ sudo kubectl get pod
NNAME                               READY   STATUS    RESTARTS   AGE
http-deployment-545bd7d765-hmqnh   1/1     Running   0          11m
```

### オートスケーリングのアルゴリズムについては以下を参照
[Autoscaling Algorithm](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/autoscaling/horizontal-pod-autoscaler.md#autoscaling-algorithm)
> TargetNumOfPods = ceil(sum(CurrentPodsCPUUtilization) / Target)

## 手動でスケーリングさせる方法はこちら
- [【KubernetesでPodをスケーリングさせる】その１ - 手動スケーリング](https://qiita.com/Esfahan/items/10affb682f4206e90931)

## 参考
- [Horizontal Pod Autoscaler Walkthrough](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/)
- [KubernetesのPodとNodeのAuto Scalingについて](https://qiita.com/sheepland/items/37ea0b77df9a4b4c9d80)
- [KubernetesのPodのオートスケールについて確認してみる](https://varu3.hatenablog.com/entry/2018/05/31/121500)
- [Kubernetesに負荷をかけオートスケールを観察する](https://akaimo.hatenablog.jp/entry/2018/05/16/221916)
- [PodとNode](https://nownabe.github.io/kubernetes-doc/tutorials/kubernetes_basics/3_explore_your_app.html)
- [Nodeの水平オートスケーリング](https://qiita.com/sheepland/items/37ea0b77df9a4b4c9d80#node%E3%81%AE%E6%B0%B4%E5%B9%B3%E3%82%AA%E3%83%BC%E3%83%88%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%AA%E3%83%B3%E3%82%B0)
- [Kubernetesはクラスタで障害があったとき、どういう動きをするのか](http://dr-asa.hatenablog.com/entry/2018/04/02/174006)