【Kubernetes】Horizontal Pod Scaler is 何

## Horizontal Pod Scalerとは
Horizontal Pod Scaler(以降HPA)とはPodのCPU使用の負荷に応じて、どの程度Podをスケールイン/アウトをするかを定義する機能です。

HPAのマニフェストの定義において、Deploymentに対して命令を送り、Replica数を制御します。
以下のマニフェストファイルを例に解説していきます。

```yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: aws-hpa
  namespace: aws-01
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: aws-auth-internal
  minReplicas: 1
  maxReplicas: 3
  metrics: 
  - type: ContainerResource
    containerResource:
      container: aws-auth-internal
      name: cpu
      target:
        type: Utilization
        averageUtilization: 90
  - type: ContainerResource
    containerResource:
      container: aws-auth-internal
      name: memory
      target:
        type: Utilization
        averageUtilization: 90    
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
      - type: Pods
        value: 1
        periodSeconds: 300
    scaleDown:
      stabilizationWindowSeconds: 3600
      policies:
      - type: Pods
        value: 1
        periodSeconds: 300
```

### spec.scaleTargetRef
```yaml
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment # スケーリング対象のリソース種類
    name: aws-auth-internal # スケーリング対象のDeployment名
  minReplicas: 1 # 最小レプリカ数
  maxReplicas: 3 # 最大レプリカ数
```
`spec.scaleTargetRef`において、スケーリング対象のリソースを指定します。
- `kind: Deployment` -> スケールする対象に`Deployment`を指定しています
- `name: aws-auth-internal` -> スケーリング対象の`Deployment`名を指定いています。ここで指定した`Deployment`名と`Deployment`のマニフェストファイルの`metadata.name`の名称と一致させる必要があります
- `minReplicas: 1` -> 最小のレプリカ数を指定
- `maxReplicas: 3` -> 最大のレプリカ数を指定

### spec.metrics
```yaml
  metrics: 
  - type: ContainerResource # コンテナのリソース使用率を監視
    containerResource:
      container: aws-auth-internal # 監視対象のコンテナ名
      name: cpu # 監視するリソース = CPU
      target: 
        type: Utilization # 使用率
        averageUtilization: 90 # 使用率90%以上
```
`spec.metrics`においてHPAがスケーリングの判断に使用するメトリクスを定義しています。
- `type: ContainerResource` -> コンテナのリソース使用率を監視することを定義しています
- `container: aws-auth-internal` -> 監視対象のコンテナ名を指定しています。ここで指定したコンテナ名は`Deployment`で指定する`spec.containers.name`の名称と一致する必要があります
- `name: cpu` -> 監視するリソースがCPUであることを示しています
- `type: Utilization` -> 監視するCPUの使用率を基準にスケーリングします
- `averageUtilization: 90` -> CPU使用率の具体的な数字を定義しています。この場合90%を超えるとスケールアウトします。


```yaml
 - type: ContainerResource
    containerResource:
      container: aws-auth-internal
      name: memory # 監視するリソース = メモリ
      target:
        type: Utilization
        averageUtilization: 90
```
リスト形式で監視するリソースの対象を複数指定します。
`name: memory`とすることでメモリ使用率が90%以上の場合にスケールアウトする定義をしています。

### spec.behavior
```yaml
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0 # 即時反映
      policies:
      - type: Pods
        value: 1 # 1Podずつ増加
        periodSeconds: 300
    scaleDown:
      stabilizationWindowSeconds: 3600
      policies:
      - type: Pods
        value: 1
        periodSeconds: 300
```
`spec.behavior`においてスケールする挙動を定義しています。
### scaleupの挙動
具体的な挙動は、メトリクスが90%を超えた場合に、`stabilizationWindowSeconds: 0`と定義しているため即時にスケールアウトが開始します。
- `value: 1` -> Podが1つ追加されます
- `periodSeconds: 300` -> 90%超過が300秒、つまり5分間続けば1つPodを追加します(ただし、`spec.scaleTargetRef`で定義している通り`maxReplicas: 3`までです)

:::note info
- 時刻 00:00: CPU使用率が95% → Podが1つ増え 2つに。
- 時刻 00:05: まだ90%超え → さらに1Pod増え 3つに（maxReplicas到達）。
- 時刻 00:10: 3Podでも90%超えていても、これ以上増やせない（maxReplicas制限）。
:::

### scaleDownの挙動
具体的な挙動はメトリクスが継続的に90%を下回っている場合にスケールインをします。
継続的とは`stabilizationWindowSeconds: 3600`でメトリクスが90%を下回ってから一時的な負荷低下でスケールインすることを防ぐために3600秒、つまり1時間は様子を見るという意味になります。
1時間以内に90%を超えることがなければ、Podを一つ削除します。
そこからさらに`periodSeconds: 300`で5分間隔でPodを一つ削除します。

:::note info
- 時刻 00:00: CPU使用率が95% -> Podが増える (Podが2つ)
- 時刻 00:05: まだ90%超え → さらに1Pod増え 3つに
- 時刻 01:05: CPU使用率が60% -> スケールインを検知開始しここから1時間メトリクスの状況を監視
- 時刻 02:05: CPU使用率が60% -> Podが1つ削除 (Podが2つ)
- 時刻 02:10: CPU使用率が60% -> Podが1つ削除 (Podが1つ`minReplicas: 1`の上限となりこれ以下とはならない)
:::

:::note warn
KubernetesのHPAマニフェストにおけるスケールアップ/スケールダウンの言葉の意味はAWSなどで使用されるスケールアップ/スケールダウンの意味とは異なります。
マニフェスト上では`scaleUp`と`scaleDown`と記載しておりますが、実際にはスケールアウト = Podを増やすこと、スケールイン = Podを減らすことの意味です。
:::

## 検証
ローカルkindクラスター上でDeployment, HPAをapplyして動作検証してみます。
前提としてメトリクスを収集するために`metrics-server`が必要となるめ、`metrics-server`のインストールから実施していきます。
kindのクラスターは構築できている前提でお話します。
環境はWindowsのWSL2の環境で実行していきます。

### metrics-serverの構築
以下コマンドで`metrics-server`をapplyします。
```bash
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

serviceaccount/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
service/metrics-server created
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created

# metrics-serverがkubeletとのTLS証明書検証をスキップする
# このコマンドを実行しておかないとmetrics-serverが正常に起動しない
kubectl patch -n kube-system deployment metrics-server --type=json \
  -p '[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"}]'

# metrics-serverが正常に起動しているかの確認
kubectl get pods -n kube-system -l k8s-app=metrics-server
NAME                              READY   STATUS    RESTARTS   AGE
metrics-server-598746d78d-kf7nw   1/1     Running   0          49s
```

以下Deployment.yamlを`apply`します。
以下yamlは0.1CPUを最低限保証するリソースとしてPodに割り当てます。
また`limits`で0.2CPUを上限として設定することで、Podがこの制限を超えてCPUを使用することを制限します。

```yaml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cpu-load-generator
  labels:
    app: cpu-load-generator
spec:
  replicas: 1
  selector:
    matchLabels:
      app: cpu-load-generator
  template:
    metadata:
      labels:
        app: cpu-load-generator
    spec:
      containers:
      - name: busybox
        image: busybox
        resources:
          requests:
            cpu: "100m"  # 0.1 CPUコアをリクエスト
          limits:
            cpu: "200m"  # 0.2 CPUコアが上限
        command: ["sh", "-c"]
        args:
        - while true; do echo 'Generating CPU load...'; timeout 0.5 sha256sum /dev/zero; sleep 0.5; done
```

STATUSがRunningになっていればOKです。
```bash
kubectl apply -f deployment.yaml 
deployment.apps/cpu-load-generator created

kubectl get pods -w
NAME                                  READY   STATUS    RESTARTS   AGE
cpu-load-generator-6897fdd78f-qpjxq   1/1     Running             0          73s
```

以下のhpa.yamlを`apply`します。
```yaml
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: hpa-demo
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: cpu-load-generator  # Deploymentの名前と一致させる
  minReplicas: 1
  maxReplicas: 5
  metrics:
  - type: ContainerResource
    containerResource:
      container: busybox  # Deploymentのコンテナ名と一致
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50  # CPU使用率50%でスケール
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
      - type: Pods
        value: 1
        periodSeconds: 30  # 30秒ごとに1Pod増加
    scaleDown:
      stabilizationWindowSeconds: 60  # 1分待機後に削除
      policies:
      - type: Pods
        value: 1
        periodSeconds: 30  # 30秒ごとに1Pod削除
```

HPAを`apply`します。
```bash
kubectl apply -f hpa.yaml 
horizontalpodautoscaler.autoscaling/hpa-demo created
```

以下コマンドでPodのスケール状況を確認してみます。
```bash
kubectl get hpa hpa-demo -w
NAME       REFERENCE                       TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
hpa-demo   Deployment/cpu-load-generator   118%/50%   1         5         1          17s
hpa-demo   Deployment/cpu-load-generator   117%/50%   1         5         2          31s
hpa-demo   Deployment/cpu-load-generator   115%/50%   1         5         2          46s
hpa-demo   Deployment/cpu-load-generator   116%/50%   1         5         3          61s
hpa-demo   Deployment/cpu-load-generator   113%/50%   1         5         3          76s
hpa-demo   Deployment/cpu-load-generator   115%/50%   1         5         4          92s
hpa-demo   Deployment/cpu-load-generator   118%/50%   1         5         4          107s
hpa-demo   Deployment/cpu-load-generator   112%/50%   1         5         5          2m2s
hpa-demo   Deployment/cpu-load-generator   114%/50%   1         5         5          2m32s
hpa-demo   Deployment/cpu-load-generator   115%/50%   1         5         5          2m48s
hpa-demo   Deployment/cpu-load-generator   116%/50%   1         5         5          3m3s
hpa-demo   Deployment/cpu-load-generator   113%/50%   1         5         5          3m18s
```

上記の結果から、hpa.yamlで定義した通りの動作になっているのがわかります。
apply直後からCPU使用率が閾値50%に対して118%と大幅に超過しています。
```bash
NAME       REFERENCE                       TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
hpa-demo   Deployment/cpu-load-generator   118%/50%   1         5         1          17s
```
applyから30秒経過後、CPU使用率117%と超過しており、Pod数が1 -> 2に増加しています
 ```bash
NAME       REFERENCE                       TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
hpa-demo   Deployment/cpu-load-generator   118%/50%   1         5         1          17s
hpa-demo   Deployment/cpu-load-generator   117%/50%   1         5         2          31s
```

さらに30秒経過後、CPU利用率116%と超過しており、Pod数が 2 -> 3へ増加しています。
```bash
NAME       REFERENCE                       TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
hpa-demo   Deployment/cpu-load-generator   118%/50%   1         5         1          17s
hpa-demo   Deployment/cpu-load-generator   117%/50%   1         5         2          31s
hpa-demo   Deployment/cpu-load-generator   115%/50%   1         5         2          46s
hpa-demo   Deployment/cpu-load-generator   116%/50%   1         5         3          61s
```

以降最終的にPod数が最大数の5つまでスケールアウトしていることがわかります。
```bash
NAME       REFERENCE                       TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
hpa-demo   Deployment/cpu-load-generator   118%/50%   1         5         1          17s
hpa-demo   Deployment/cpu-load-generator   117%/50%   1         5         2          31s
hpa-demo   Deployment/cpu-load-generator   115%/50%   1         5         2          46s
hpa-demo   Deployment/cpu-load-generator   116%/50%   1         5         3          61s
hpa-demo   Deployment/cpu-load-generator   113%/50%   1         5         3          76s
hpa-demo   Deployment/cpu-load-generator   115%/50%   1         5         4          92s
hpa-demo   Deployment/cpu-load-generator   118%/50%   1         5         4          107s
hpa-demo   Deployment/cpu-load-generator   112%/50%   1         5         5          2m2s
```

それでは逆にスケールインの動作確認をしてみます。
以下コマンドでPodが5つ起動していることを確認します。
```bash
kubectl get hpa hpa-demo; kubectl get pods -l app=cpu-load-generator
NAME       REFERENCE                       TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
hpa-demo   Deployment/cpu-load-generator   113%/50%   1         5         5          23m
NAME                                  READY   STATUS    RESTARTS   AGE
cpu-load-generator-6897fdd78f-2mkr5   1/1     Running   0          23m
cpu-load-generator-6897fdd78f-78scs   1/1     Running   0          22m
cpu-load-generator-6897fdd78f-nbv7d   1/1     Running   0          22m
cpu-load-generator-6897fdd78f-qpjxq   1/1     Running   0          70m
cpu-load-generator-6897fdd78f-qslsj   1/1     Running   0          21m
```

以下コマンドでdeploymentの負荷設定を編集します。
```bash
kubectl edit deployment cpu-load-generator
```
以下の-argsの値を変更します。
```bash
# 変更前
    spec:
      containers:
      - args:
        - while true; do echo 'Generating CPU load...'; timeout 0.5 sha256sum /dev/zero;
```
`sleep 5`で5秒間プロセスが完全停止するループを実行するため、CPU使用率を極小にします。
```bash
# 変更後
    spec:
      containers:
      - args:
        - while true; do echo 'Low CPU load'; sleep 5; done
```

以下コマンドでスケールインする遷移を確認してみましょう。
```bash
kubectl get hpa hpa-demo -w
NAME       REFERENCE                       TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
hpa-demo   Deployment/cpu-load-generator   114%/50%   1         5         5          32m
hpa-demo   Deployment/cpu-load-generator   114%/50%   1         5         5          32m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         5          33m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         4          33m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         4          33m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         3          33m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         3          34m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         2          34m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         2          34m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         1          34m
```

最初のスケールインでは1分待機後にPodを削除する動きをする想定ですが、
CPU使用率が32m時点ではまだ114%でそこから1分後の33mに1%まで負荷が低下し、Pod数が5 -> 4に減少しているがわかります。
```bash
NAME       REFERENCE                       TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
hpa-demo   Deployment/cpu-load-generator   114%/50%   1         5         5          32m
hpa-demo   Deployment/cpu-load-generator   114%/50%   1         5         5          32m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         5          33m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         4          33m
```

以降`m`表記で少しわかりづらいですが、1分経過せずにPodが1つずつ減少していっているのがわかります。
```bash
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         4          33m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         4          33m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         3          33m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         3          34m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         2          34m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         2          34m
hpa-demo   Deployment/cpu-load-generator   1%/50%     1         5         1          34m
```

マニフェストの想定通りに検証ができましたので最後に後片付けをします。
以下コマンドでHPAとDeploymentを削除します。
```bash
kubectl delete hpa hpa-demo
horizontalpodautoscaler.autoscaling "hpa-demo" deleted

kubectl delete deployment cpu-load-generator
deployment.apps "cpu-load-generator" deleted

# 削除されていることの確認
kubectl get hpa,deploy -l app=cpu-load-generator
No resources found in default namespace.
```

## 参考
- https://kubernetes.io/ja/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/
- https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/horizontal-pod-autoscaler.html