KEDA v2 よる イベント駆動オートスケール 環境を構築する

## はじめに
Kubernetes を基盤としたプロジェクトで実際に利用している, KEDA というコンポーネントのメジャーバージョンが上がったので追加機能についてまとめる.

## KEDAとは

KEDA (Kubernetes Event-driven Autoscaling)は, kubernetes clusterに追加できる単一目的の軽量コンポーネントで,CNCFのSandboxプロジェクトにホストされているkubernetes ベースのイベント駆動オートスケーラーである. 宣言的に定義されたイベントに応じて, Kubernetes の任意のコンテナーのスケーリングを促進できる. Horizo​​ntal Pod Autoscaler などの kubernetesコンポーネントと連携して動作し,上書きや重複なしに機能拡張できる.

[公式サイト](https://keda.sh)


## KEDA を使うと嬉しいこと

Kubernetes Horizo​​ntal Pod Autoscaler では CPU , Memory , Metrics API をトリガーに自動スケーリングが可能である. しかし, 多くの点で, それは症状の治療に似ており, 実際の原因ではないことがある. 例えばメッセージングの仕組みを利用していた場合に, 大量のメッセージによって最終的にCPU使用率が上昇するが, この場合本当にスケーリングしたい条件は, キューに滞留しているメッセージの数であったりする.
これらの場合, KEDA を用いればシンプルに スケーリング環境を構築できる.


## v2の追加機能

### 追加されたスケール対象
- StatefulSet
- Custom Resource
- ScaledJobs

DeploymentやJobが主な対象だったが、サブリソースとしての `Custom Resource` や Jobスケール専用の `ScaledJobs` が追加されたり
対象毎の特性に応じたプロパティ設定が可能となった.


### 追加されたスケーラー
- CPU
- Memory
- External Push
- Metrics API
- Azure Log Analytics
- IBM MQ

Kubernetes Horizo​​ntal Pod Autoscaler でも取得が可能だった CPU , Memory も追加されているのが印象的である.
Kubernetesの複雑さの一部を取り除くために, Kubernetes API を使用しなくても対処できるようにするためだと推測している.

## 事前準備

localに kubernetes 環境を構築する。 今回は minikube を使って cluster の構築を行う.
[minikubeインストール手順](https://kubernetes.io/ja/docs/tasks/tools/install-minikube)

## インストール

- KEDAをインストールする. (今回はマニフェストを用いる)

```sh
$ kubectl apply -f https://github.com/kedacore/keda/releases/download/v2.0.0/keda-2.0.0.yaml

namespace/keda created
customresourcedefinition.apiextensions.k8s.io/scaledjobs.keda.sh created
customresourcedefinition.apiextensions.k8s.io/scaledobjects.keda.sh created
customresourcedefinition.apiextensions.k8s.io/triggerauthentications.keda.sh created
serviceaccount/keda-operator created
clusterrole.rbac.authorization.k8s.io/keda-external-metrics-reader created
clusterrole.rbac.authorization.k8s.io/keda-operator created
rolebinding.rbac.authorization.k8s.io/keda-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/keda-hpa-controller-external-metrics created
clusterrolebinding.rbac.authorization.k8s.io/keda-operator created
clusterrolebinding.rbac.authorization.k8s.io/keda:system:auth-delegator created
service/keda-metrics-apiserver created
deployment.apps/keda-metrics-apiserver created
deployment.apps/keda-operator created
apiservice.apiregistration.k8s.io/v1beta1.external.metrics.k8s.io created
```

- `Namespace` や `Deployment` など KEDA のリソースが作成されている.

```sh
$ kubectl get all -n keda
NAME                                          READY   STATUS    RESTARTS   AGE
pod/keda-metrics-apiserver-5bffbfbd68-ftg6q   1/1     Running   0          3m12s
pod/keda-operator-7b98595dc7-b4rnb            1/1     Running   0          3m12s

NAME                             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE  
service/keda-metrics-apiserver   ClusterIP   10.97.130.94   <none>        443/TCP,80/TCP   3m12s

NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/keda-metrics-apiserver   1/1     1            1           3m12s
deployment.apps/keda-operator            1/1     1            1           3m12s

NAME                                                DESIRED   CURRENT   READY   AGE
replicaset.apps/keda-metrics-apiserver-5bffbfbd68   1         1         1       3m12s
replicaset.apps/keda-operator-7b98595dc7            1         1         1       3m12s
```

## イベント駆動スケールを試す

せっかくなので KEDA v2から追加された `Metrics API` を試してみる
今回は自分で定義したAPIから Metrics を取得する.

[サンプルはこちら](https://github.com/JunichiMitsunaga/keda2-example)

-  KEDA の トリガー対象とmetrics用のアプリケーションをデプロイする.


```sh
$ make deploy-app
or 
$ kubectl apply -f ./deploy/demo/app/

namespace/demo created
service/components-mock created
deployment.apps/components-mock created
deployment.apps/nginx created
```

demo `Namespace` にmetrics用のアプリケーション<b>components-mock</b>とスケール対象の<b>nginx</b>がデプロイされる.

```sh

$ kubectl get all -n demo
NAME                                   READY   STATUS    RESTARTS   AGE
pod/components-mock-6b58c499dd-x6bf7   1/1     Running   0          95s
pod/nginx-6799fc88d8-p9jp5             1/1     Running   0          95s

NAME                      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/components-mock   ClusterIP   10.99.64.141   <none>        3003/TCP   95s

NAME                              READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/components-mock   1/1     1            1           95s
deployment.apps/nginx             1/1     1            1           95s

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/components-mock-6b58c499dd   1         1         1       95s
replicaset.apps/nginx-6799fc88d8             1         1         1       95s
```


- KEDA の `ScaledObject` をデプロイする

今回デプロイする `ScaledObject` の閾値は `2` に設定している.

```yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: http-scaledobject
  namespace: demo
  labels:
    deploymentName: nginx
spec:
  maxReplicaCount: 5
  scaleTargetRef:
    name: nginx
  triggers:
    - type: metrics-api
      metadata:
        targetValue: '2'  # 閾値
        url: "http://components-mock.demo.svc.cluster.local:3003/components"  # 対象のREST API
        valueLocation: "components.worker.tasks"  # responseのjson構造
```

なお, サンプルではレスポンスのデフォルトが `0`のため `ScaledObject` がトリガーされ<b>nginx</b>が`0`に Scale Inする.

```sh
$ make deploy-scaledobject
or 
$ kubectl apply -f ./deploy/demo/scaledobject/metrics-api.yaml
scaledobject.keda.sh/http-scaledobject created
```
demo Namespace を確認すると `0`に Scale In したことが分かる.

```sh
$ kubectl get po -n demo
NAME                               READY   STATUS    RESTARTS   AGE
components-mock-6b58c499dd-x6bf7   1/1     Running   0          7m16s
```

- 値を変更して, Scale Outさせる
`/deploy/demo/app/component.yaml` を変更する

```yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: components-mock
  name: components-mock
  namespace: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: components-mock
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: components-mock
    spec:
      containers:
      - name: components-mock
        image: cabi99/components-mock:0.0.1
        env:
        - name: WORKER_TASKS
          value: "0"   # ここの値を「2」に変更する
        imagePullPolicy: Always
        ports:
        - containerPort: 3003
```

変更後に再デプロイする.

```sh
$ make deploy-app
or 
$ kubectl apply -f ./deploy/demo/app/

namespace/demo unchanged
service/components-mock unchanged
deployment.apps/components-mock configured
deployment.apps/nginx unchanged
```

<b>nginx </b>が Scale Outしていることが確認できる.

```sh
$ kubectl get po -n demo -w

NAME                               READY   STATUS              RESTARTS   AGE
components-mock-6c494cd68b-4tkmv   1/1     Running             0          32s
nginx-6799fc88d8-22q9l             0/1     ContainerCreating   0          4s 
nginx-6799fc88d8-22q9l             1/1     Running             0          5s
```

なお、今回は閾値を2にしているので、WORKER_TASKS を `10` に変更してデプロイすれば,  `maxReplicaCount` で定義した値までScale Outする.

```sh
$ kubectl get po -n demo -w

NAME                               READY   STATUS    RESTARTS   AGE  
components-mock-6d66575d55-r8vxh   1/1     Running   0          2m17s
nginx-6799fc88d8-22q9l             1/1     Running   0          4m11s
nginx-6799fc88d8-95r6v             1/1     Running   0          2m4s 
nginx-6799fc88d8-jkdqw             1/1     Running   0          2m4s 
nginx-6799fc88d8-wkmq4             1/1     Running   0          2m4s
nginx-6799fc88d8-xvctr             1/1     Running   0          109s
```

## まとめ
KEDA v2を触ってみたが, v1との使い勝手に大きな差はなかった.
しかし, 今回のバージョンアップで CPU と メモリ が追加により, Kubernetes Horizo​​ntal Pod Autoscaler と共存する形でなく, 自動スケーリング のリソースは全て KEDA に統一することで シンプル性を担保できる.
また, Metrics API を利用すれば, APIが提供されているサービスをトリガーにすることが容易であったり, SQLクエリに基づいてワークフローを自動スケーリングも可能になるので拡張性の高い環境構築に活かせると感じた.