EKSのpodをSQSのキューに応じてスケールさせる

## 背景

弊社ではAWSのEKSを使い、kubernetes環境を運用しております。
IoTデバイスからデータを受け取った際、直接dynamoDBには書き込まず、マイクロサービスの一環としてSQSを経由し別のサービスがキューからメッセージを取得し、dynamoDBに書き込んでいます。

kubernetesではHorizontal Pod Autoscalerでのpodのautoscaleを設定することができます。
[https://kubernetes.io/ja/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/](https://kubernetes.io/ja/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/)

ただしこれの場合cpu使用率ベースなどしかスケールの条件に設定できません。

このため、SQSのキューは増えているがpodに負荷がかかっていない場合、キューが増え続けてしまい、手動でスケールが必要なtoilが存在しました。

EKSのバージョンアップ前(eks1.19)は絶妙なさじ加減でCPU使用率が上がりスケールしたため、たまーーにしか無かったが、eks1.22へのバージョンアップをきっかけに毎日発生するようになってしまった。このため本対応を実施しました。

## EKSのpodをSQSのキュー数に応じてスケールさせるには？

KEDA(Kubernetes Event-driven Autoscaling)を使用します。

[https://keda.sh/](https://keda.sh/)

サイトを見ると、多数のevent sourcesに対応していることがわかるかと思います。

今回はその中のAWS SQS Queueを使います [https://keda.sh/docs/2.5/scalers/aws-sqs/](https://keda.sh/docs/2.5/scalers/aws-sqs/)

これを使うことで、SQSのキュー数に応じてpodをスケールすることができます。

※お手製ツールがいくつかあり、それで実現することもできますが、kubernetesの仕組みに乗っかったほうが良いかと思います。

## KEDAのインストール

サイトの通りhelmで行う

[https://keda.sh/docs/2.7/deploy/](https://keda.sh/docs/2.7/deploy/)

## ScaledObjectのapply

参考
[https://keda.sh/docs/2.5/scalers/aws-sqs/](https://keda.sh/docs/2.5/scalers/aws-sqs/)
[https://craftech.io/blog/event-driven-autoscaling-with-keda-and-sqs/](https://craftech.io/blog/event-driven-autoscaling-with-keda-and-sqs/)

あとは、適応させたいサービスを指定すれば良い。

今回の対象はRolloutリソースになっている。

```yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: <test-sqs-scaledobject>
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: argoproj.io/v1alpha1
    kind: Rollout
    name: <pod-name>
  minReplicaCount: 2  # We don't want pods if the queue is empty
  maxReplicaCount: 16  # We don't want to have more than 5 replicas
  pollingInterval: 100 # How frequently we should go for metrics (in seconds)
  cooldownPeriod:  300 # How many seconds should we wait for downscale  
  triggers:
  - type: aws-sqs-queue
    authenticationRef:
      name: keda-aws-credentials
    metadata:
      queueURL: https://sqs.ap-northeast-1.amazonaws.com/xxxxxxx/<sqs-name>
      queueLength: "10"
      awsRegion: "ap-northeast-1"
      identityOwner: operator # use node iam role
```

### パラメータの調整

```yaml
  minReplicaCount: 2
  maxReplicaCount: 16
  pollingInterval: 100
  cooldownPeriod: 300
  queueLength: "10"
...

```

これらを良い感じに調整します。

仕組みによっては通常は0台にして溜まったら処理するというケースも作れます。

今回はhpaと同じように最低台数、最大台数を指定し、頻繁に入れ替わりすぎても微妙なのでcooldownPeriodは気持ち長めにしました。

queueLengthは数を前後させながらいい感じのところにしました。
常時podを起動していて、それなりのキューを処理していると調整が難しい気がします。

### ハマったポイント

#### その1

> identityOwner - Receive permissions on the SQS Queue via Pod Identity or from the KEDA operator itself (see below). (Values: pod, operator, Default: pod, Optional)
> 

defaultはpodとなっており、[公式ページ](https://keda.sh/docs/2.5/scalers/aws-sqs/)には3つの認証例が示されている。

- Scaling a deployment using podIdentity providers
- Scaling a deployment using IAM Role
- Scaling a deployment using IAM Users

今回弊社では楽をするためにnodeのiam roleをそのまま使いたかったのだが、 `identityOwner: operator` にすれば実現するという情報をなかなか見つけられずハマってしまった。

(厳密に権限管理を行う場合は、 `identityOwner: pod` を指定し、上記のIAM users以外の方法を使うのがベストだと思われます。)

#### その2

hpaと喧嘩する。

ScaledObjectでは増やす必要があるが、hpaではCPU使用率的に減らす必要がある状態になると、ContainerCreatingとTerminatingが繰り返されます。

ScaledObjectをapplyするときはcpuベースなどのhpaを削除するのがベターだと思います。（頑張れば併用できる気もしますが…）

## 状況を確認する

大体この辺のコマンドを使う

```markdown
kubectl get ScaledObject -n default
kubectl get HorizontalPodAutoscaler -n default
kubectl describe ScaledObject -n default
kubectl logs -n keda keda-operator-xxxx
```

大体、認証情報が合ってないか、稀に指定するパラメーターが合ってなかったりする。

## 運用に乗せる

弊社ではdeploy時codebuildを経由の上、helm templateを使用し、applyするyamlを生成&applyしているので、これに対応します。

service_name.yaml

```yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: {{ .Chart.Name }}
  labels:
    app: {{ .Chart.Name }}
spec:
  scaleTargetRef:
    apiVersion: argoproj.io/v1alpha1
    kind: Rollout
    name: {{ .Chart.Name }}
  minReplicaCount: {{ .Values.scaledObject.minReplicaCount }}
  maxReplicaCount: {{ .Values.scaledObject.maxReplicaCount }}
  pollingInterval: {{ .Values.scaledObject.pollingInterval }}
  cooldownPeriod: {{ .Values.scaledObject.cooldownPeriod }}
  triggers:
  - type: aws-sqs-queue
    authenticationRef:
      name: keda-aws-credentials
    metadata:
    {{- if eq .Values.env "staging" }}
      queueURL: <sqs-staging-url>
    {{- else }}
      queueURL: <sqs-production-url>
    {{- end }}
      queueLength: "{{ .Values.scaledObject.queueLength }}"
      awsRegion: "ap-northeast-1"
      identityOwner: operator
---
...省略
```

values.yaml

```yaml
scaledObject:
  minReplicaCount: 2
  maxReplicaCount: 16
  pollingInterval: 100
  cooldownPeriod: 300
  queueLength: 10
```

## 終わりに

KEDAは便利でした。

対応しているevent sourcesが多いからか若干情報が分散されており、日本語記事は少なく、改めて英語情報を読み解く力、一層ソースコードを読み解く力が大事だと思いました。

今回の対応でkubernetesの足りない知識が補えた気がします。

採用募集しています。個人宛でもこちらからでも是非！
https://www.wantedly.com/projects/443273