Tanzu Service MeshのAutoscale機能のUIとManifestの違いを調べてみた

Tanzu Service Mesh(以下TSM)はAutoscale機能が用意されており、負荷に応じてPodの数を自動でスケーリング出来る。（公式ドキュメントは[こちら]()）
設定方法は3種類用意されている。
1. [UIからGNS単位に設定する](https://docs.vmware.com/en/VMware-Tanzu-Service-Mesh/services/service-autoscaling-with-tsm-user-guide/GUID-BD13111F-E0BC-4A39-BB2C-E0C2B845D3B6.html)
2. [APIからGNS単位に設定する](https://docs.vmware.com/en/VMware-Tanzu-Service-Mesh/services/service-autoscaling-with-tsm-user-guide/GUID-5284CF8A-DFD5-4920-9A9A-DA6DD4F512D0.html)
3. [Manifestからクラスタ内に設定する](https://docs.vmware.com/en/VMware-Tanzu-Service-Mesh/services/service-autoscaling-with-tsm-user-guide/GUID-7F848C15-6DC4-466B-996C-BD4356EA4ADE.html)

※GNS：Global Namespace。クラスタを跨いだNamespaceでTSM独自の機能。

ここではUIとManifestから設定する方法を確認し、違いも見ていく。
ついでにKubernetes標準でついてくるオートスケール用リソースのHPAとの違いもサラっと確認する。

## UIからオートスケールを設定する
TSMにログインし、左サイドバーの`Policies`->`Autoscaling`をクリックすると今時点で設定しているオートスケールポリシーの一覧が確認できる。
![1690767681719.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/4a786313-eb05-81d6-6f45-755daca68881.png)
ここで`ACTIONS`から`New Policy`を選択し、新規にオートスケールポリシーを作成することができる。
入力する項目としては以下となる。
|項目|意味|
|---|---|
|`Autoscaling Policy Name`|ポリシー名。2-30文字で小文字と数字とハイフンが利用可能。|
|`GNS Scope`|対象となるGNS|
|`Target Service`| 対象となる`kind: Service`オブジェクト。デプロイ済みServiceは選択できるが、デプロイしていないものは入力して追加する。|
|`Service Version`|`Service`のバージョン。バージョンの概念は[こちら](https://docs.vmware.com/en/VMware-Tanzu-Service-Mesh/services/concepts-guide/GUID-75B37A79-80D2-4F0E-BC59-4A87421EAAEB.html?hWord=N4IghgNiBcIBIHsDuACAIggpgZxWFAKmAHYBeArigMqYBOAbgJYDGmKAsjgBbV1OsoASpghgALmzEI8KANLkARnWKYJuGgxaYA-CAC+QA)参照。|
|`Autoscaling Mode`|オートスケーリングのモード。パフォーマンス優先(Scale Downを認めない)の場合は`Performance`を選択し、リソースの効率活用を優先する場合は`Efficiency`を選択する。|
|`Autoscaling Metric`|オートスケールに使用するメトリックを指定し、メトリクスの平均値を算出する際のメトリクス取得秒数(60-3600秒の間、windowSecondsに該当)も指定する。メトリックは以下から選択可能<ul><li>RPS</li><li>リクエスト数</li><li>CPU利用率(％)</li><li>CPU利用量(コア数))</li><li>メモリ使用量(Bytes)</li><li>メモリ使用率(％)</li><li>p50 Latency</li><li>p90 Latency</li><li>p99 Latency</li></ul>|
|`Scale-up Condition`|スケールアップに使用する閾値|
|`Max. Instance Count`|最大Pod数|
|`Scale-down Condition`※Efficiencyモードの時のみ入力|スケールダウンする条件を設定。スケールアップの閾値より大きい値は指定できない。|
|`Min. Instance Count`※Efficiencyモードの時のみ入力|最小Pod数|
|`Scaling Method`|スケーリングの方法を定義する。<ul><li>`Default Autoscaling`: 計算式に基づいてPod数を決める</li><li>`Stepped Autoscaling`: 条件を満たした後、指定したスケールアップ数、スケールダウン数に基づいてPod数を増減する</li></ul>TSMではステートレスなサービスの場合はDefaultを用い、ライセンスコストやストレージコストなどを気にする場合はstepを使うことを推奨している。|
|`Panic Mode`|メトリクスが取得できなかったり、オートスケーラーがインスタンス数を計算出来なかったりした場合の振る舞いを指定する。<ul><li>`Do Not Scale Services`: 何もしない</li><li> `Scale to <指定値> instance(s)`: Pod数が指定値に達していない場合、指定値までスケールアップする</li></ul>|

入力後、`NEXT`をクリックして先に進むと、ポリシー保存後の振る舞いについて聞かれる。
- `Activate Policy`: 保存すると即座にポリシーがデプロイされる
- `Simualtion Mode Only`: シミュレーションモードでデプロイされる

選択して`NEXT`をクリックすると、最後に確認画面となる。

![1690769947272.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/6ed057a1-70ad-865d-2ad2-6faf39d2ca23.png)
問題なければ`SAVE`をクリックする。


[サンプル](#付録サンプルマニフェスト)をデプロイしてみる。
GNSが対象としているNamespaceに、Serviceを作成し、適切にDeploymentと紐付けられていれば、オートスケールしてくれる。
```sh
$ kubectl get pod
NAME                    READY   STATUS    RESTARTS   AGE
nginx-ff6774dc6-2r65z   2/2     Running   0          15m
nginx-ff6774dc6-8kjqh   2/2     Running   0          8m8s
nginx-ff6774dc6-jl8xx   2/2     Running   0          7m8s
nginx-ff6774dc6-s79p6   2/2     Running   0          7m8s
nginx-ff6774dc6-vqws2   2/2     Running   0          7m38s

```

## Manifestからクラスタ内に設定する
先程UIでやったようなポリシーの定義をManifestでも定義することが出来る。
ただし、Kubernetesリソースを使ってクラスタにManifestを適用するため、GNSの概念は持ち込めない。

ポリシーの定義は`kind: Definition`で行う。
`kind: Definition`の定義の説明は[Tanzu Service Mesh Service Autoscaler Configuration Reference](https://docs.vmware.com/en/VMware-Tanzu-Service-Mesh/services/service-autoscaling-with-tsm-user-guide/GUID-3C1EBD4E-3985-4565-9D94-416151C86D44.html)を参照するとよい。
ここでは、[サンプル](#kind-definition)を元にどういった設定項目があるかを説明する。
最初に、`scaleTargetRef`はオートスケール対象を選択する箇所となる。
```yaml
  scaleTargetRef:
    kubernetes:
      apiVersion: apps/v1
      kind: Deployment
      name: nginx
```
`apiVersion`、`kind`、`name`が全て一致するものを対象とする。
なお、`kind`は以下を利用することが出来る。
- `Deployment`
- `ReplicaSet`
- `StatefulSet`

Namespaceの指定ができないが、これは`kind: Definition`を対象となるオブジェクトと同じNamespaceにデプロイする必要があることを意味する。
また、対象となるオブジェクトが複数存在してはならないと、ドキュメントに[記載がある](https://docs.vmware.com/en/VMware-Tanzu-Service-Mesh/services/service-autoscaling-with-tsm-user-guide/GUID-EA4CF02D-CD84-40FA-8600-C2A08557D906.html#specscaletargetrefkubernetes-0)が、CRDとしては重複を禁止していないため注意が必要である。（おそらく意図しないオートスケールの挙動を取る可能性がある）。

次に`scaleRule`を見ていく。`mode`はUIと同じく`PERFORMANCE`か`EFFICIENCY`を指定する。それぞれの意味はUIと同じため割愛する。
```yaml
  scaleRule:
    mode: EFFICIENCY
```

`enabled`はUIでいう`Activate Policy` を設定していたところと同義で、`false`にするとシミュレーションモード相当で動作する。
```yaml
    enabled: true
```

`instances`ではPod数の最大・最小とデフォルト値、スケール方法を指定する。`stepsDown`, `stepsUp`を指定しない場合はDefault(使用リソース量に比例したスケーリング)でスケーリングする。
```yaml
    instances:
      min: 1
      max: 5
      default: 3
      stepsDown: 1
      stepsUp: 1
```

`trigger`では、どのメトリックがどれだけの期間閾値を超えていたらスケーリングするか、のトリガー部分を指定する。`gracePeriodSeconds`はスケールアップイベント発生後にイベントが鎮火してスケールダウンする際、イベント発生後から必要な経過時間となる。
```yaml
    trigger:
      gracePeriodSeconds: 200
```
`gracePeriodSeconds`のデフォルト値は300秒で、UIと異なり0に設定することで即座にスケールダウンすることも可能。
スパイクが頻繁に発生しないサービスの場合は0に設定するのもありかもしれない。

`metric`の項目では、オートスケールの指標とするメトリック(CPU/メモリ使用量)を`name`で指定し、`scaleUP`,`scaleDown`でメトリックの閾値を設定する。
```yaml
      metric:
        name: CPUUsageMillicores
        scaleUp: 500
        scaleDown: 100
        windowSeconds: 600 
```
`windowSeconds`は条件を満たしているかを判断する秒数でデフォルトは600秒であり、この期間の平均値を元にオートスケールが稼働する。

Manifestをデプロイすると、以下のようにスケールの状態が確認できる。
```sh
$ kubectl get asd
NAMESPACE         NAME           ENABLED   MODE         TRIGGER              MIN   MAX   CURRENT   DESIRED   READY
nginx-autoscale   frontend-asd   true      EFFICIENCY   CPUUsageMillicores   4     6     4         4         true
```

## UIとManifestとの違い
手元で確認した感じだとざっくり以下のような違いがあった。

|比較項目|UI|Manifest|
|---|---|---|
|対象|GNS内のリソース|クラスタ内Namespaceのリソース|
|スケールダウン時のGracePeriodSeconds|60秒から|0秒から|
|変更方法|UIからEditを選択して変更|Manifestやデプロイ済みオブジェクトを修正|

なお、UIからそれぞれの設定を見ると、以下のように表示も異なる。\
UIで設定した場合：
![1690798032184.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/36d48fd5-9adc-dce1-006f-8efbc0f268fe.png)
Manifestで設定した場合：
![1690798017238.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/36a443cc-a60c-b063-cdb4-cf333eed6023.png)



## HPAとの違い
この疑問に関しては、[公式FAQ](https://docs.vmware.com/en/VMware-Tanzu-Service-Mesh/services/service-autoscaling-with-tsm-user-guide/GUID-0CACA116-AEA1-4991-9F96-E8500809B420.html#does-the-tanzu-service-mesh-service-autoscaler-use-kubernetes-horizontal-pod-autoscaler-hpa-to-help-with-autoscaling-0)にも記載がある。
ざっくりまとめると、

- アルゴリズムが独自であり、モードが複数あったりスケーリングの猶予期間があったり、UIと組み合わせてSLOが設定できるなど
- 一方で、監視対象はCPU/メモリ/リクエストのみで、`type: External`などPrometheus連携みたいな利用はできない
- SaaS側にスケーリングの判断を任せているため、コントローラとSaaSとの接続が失われた場合はオートスケーリング出来ない

細かい検証は出来ていないが、HPAは経験上感度が悪かったりすることもあったので、アルゴリズムが独自という点で改善されていることを期待したい。
（手元で負荷をかけた時の反応は体感的にHPAより良かった気はする）。
ということで、HPAユーザも乗り換える価値はありそうだが、乗り換える場合は`type: External`を利用している人は注意した方がよい。

## 付録：サンプルマニフェスト
#### `kind: Deployment`
サンプルは以下。
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        imagePullPolicy: Always
        name: nginx
        ports:
        - containerPort: 80
          protocol: TCP
      restartPolicy: Always
```
#### `kind: Service`
サンプルは以下。
```yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  ports:
  - port: 80
  selector:
    app: nginx
```
#### `kind: Definition`
サンプルは以下。
```yaml
apiVersion: autoscaling.tsm.tanzu.vmware.com/v1alpha1
kind: Definition
metadata:
  name: nginx-asd
  labels: 
    app: nginx-asd
spec:
  scaleTargetRef:
    kubernetes:
      apiVersion: apps/v1
      kind: Deployment
      name: nginx
  scaleRule:
    mode: EFFICIENCY
    enabled: true
    instances:
      min: 1
      max: 5
      default: 3
      stepsDown: 1
      stepsUp: 1
    trigger:
      gracePeriodSeconds: 200
      metric:
        name: CPUUsageMillicores
        scaleUp: 500
        scaleDown: 100
        windowSeconds: 600 
```