KubernetesでDatadog + Horizontal Pod Autoscalerをやってみた

# 概要
## TL;DR
Datadogのmetricsを使ってHorizontal Pod Autoscalerを設定したらとても便利だった（設定も簡単だった）
https://www.datadoghq.com/blog/autoscale-kubernetes-datadog/

AWS Integrationで取得したメトリクスだけは少し難しい（後述）

## 背景
KubernetesでHorizontal Pod Autoscalerを使っている人も多いと思いますが、どのような値を使ってAutoscalingしてますでしょうか？
我々も元々はCPUやメモリでやっていたのですが、段々満足できず色々なmetricsを使いたくなりました。custom metricsを利用可能なのでそちらを使おうと思ったのですが、調べるとPrometheusを使った方法が良く紹介されています。元々Prometheusを使っている人はPrometheusで良いですが、我々はそうではなかったのでHPAのためにPrometheus入れるのもなーと思い悩みました。


Custom Metrics APIの仕様は公開されているので自分で作ることも可能なのですが、結構手間なので決断できずにいました。
https://github.com/kubernetes/community/blob/master/contributors/design-proposals/instrumentation/custom-metrics-api.md

さらに調べるとexternal metricsといって外部のmetricsを利用したHPAも設定可能なことが分かりました。我々のプロジェクトでは既にDatadogを導入していたため、Datadogのmetrics使えないかなという視点で調べ始めたところドンピシャのやり方がDatadogの公式で紹介されてました。
https://www.datadoghq.com/blog/autoscale-kubernetes-datadog/

なのでそれを使ってHPA設定してみたら簡単に出来てとても便利になったというのがこの記事の趣旨です。

# 詳細
上の記事に書いてあるとおりなので、その通りにやってもらえばよいのですが今回はさらに簡単なやり方を紹介しておきます。
DatadogをExternal Metrics Providerとして動かすためにはDatadog Cluster Agentの導入やAPIServiceの登録が必要です。
そのYAMLは以下でexampleが公開されているのでkubectl applyしていけば動くものが出来上がります。
https://github.com/DataDog/datadog-agent/tree/9ceedbd0efb1efbf4de3f1f6d9f34206f2928f5b/Dockerfiles/manifests/cluster-agent

Datadog Cluster Agentについては以下に説明が書いてあります。
https://docs.datadoghq.com/agent/kubernetes/cluster/

Datadogは通常nodeごとにagentがデプロイされますが、それだとクラスタのmetricsを取得するためにnodeのagentにk8s APIの権限を渡さないといけないし、Datadog Cluster Agentがいれば1つでクラスタの情報を取得すればいいから負荷も軽減されるということのようです。さらにHPAでDatadogのmetricsを使うときのmetrics APIサーバとしても動いてくれるようです（多分）。

## Helm Chart
上にYAMLのリポジトリを貼りましたが、Helmであればオプションを渡すだけで導入可能です。
https://github.com/helm/charts/tree/master/stable/datadog#enabling-the-datadog-cluster-agent

```console
$ helm install --name datadog-monitoring \
    --set datadog.apiKey=YOUR-API-KEY-HERE \
    --set datadog.appKey=YOUR-APP-KEY-HERE \
    --set clusterAgent.enabled=true \
    --set clusterAgent.metricsProvider.enabled=true \
    stable/datadog
```

これだけでDatadog Cluster Agentもデプロイされ、APIServiceの登録等もされます。
つまりもうHPAが使える状態です！

```console
$ kubectl get pods | grep datadog
datadog-cluster-agent-74f88b78d6-t9nrl        1/1     Running     0          1h
datadog-datadog-k9fdl                         1/1     Running     0          1h
datadog-datadog-qqm42                         1/1     Running     0          1h
datadog-kube-state-metrics-5db89d6d78-qzlgc   1/1     Running     0          1h
```

datadog-cluster-agentがあればOKです。kube-state-metricsは既にデプロイ済みなので不要な人とかのためのオプションもありますし、他に細かい設定をしたい人はChartのオプションを見てみて下さい。


## HPA
nginxのリクエスト数でAutoScalingする場合のexampleは以下にあります。
https://github.com/DataDog/datadog-agent/blob/9ceedbd0efb1efbf4de3f1f6d9f34206f2928f5b/Dockerfiles/manifests/cluster-agent/hpa-example/hpa-manifest.yaml

同じだと芸がないので、Redisのkey lengthでスケールしてみます。
我々のプロジェクトでは[Celery](https://github.com/celery/celery)というタスクキュー用のツールを使っています。
このツールではRedisをキューとして使うことが出来るのですが（SQSとかも使えます）、 `celery` というkeyでtaskを管理します。
つまりkey lengthがtask数を指します。今回はtask数が多い時にworkerをスケールさせたいというケースでやってみます。

デフォルトだとRedisのkey lengthが取得されないのでDatadog Agentの設定を少しいじります。values.yamlに以下のように設定します。

```yaml 
clusterAgent:
  enabled: true
  metricsProvider:
    enabled: true

datadog:
  confd:
    redisdb.yaml: |-
      init_config:
      instances:
        - host: "redis.local"
          port: 6379
          keys:
            - celery
```

上のvalues.yamlを適用します。

```console
$ helm upgrade -f values.yaml datadog-monitoring stable/datadog
```

ここではRedisのhost名を指定してkeyの長さを取得するようにしています。Datadogの設定についてはここでは細かくは触れません。
Datadogの画面からmetrics: `redis.key.length`のtag: `key:celery`で値が取得できていることを確認します。

無事に取得できていればあとはHPAの設定をするだけです。

```yaml
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: celery-worker
spec:
  minReplicas: 2
  maxReplicas: 5
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: celery-worker
  metrics:
  - type: External
    external:
      metricName: redis.key.length
      metricSelector:
        matchLabels:
          key: celery
      targetAverageValue: 4
```

ここでは`celery-worker`というDeploymentのAutoScalingの設定をしています。
targetAverageValueは全Podの数で割った数がその値に近づくように動くという目標値です（自分の理解では）。
詳細はドキュメント見て下さい。
https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#algorithm-details

上のYAMLをapplyしましょう。

```console
$ kubectl apply -f hpa.yaml
```

hpaの設定を見てみます。

```console
$ kubectl get hpa
NAME                         REFERENCE                   TARGETS             MINPODS   MAXPODS   REPLICAS   AGE
celery-worker                Deployment/celery-worker    4/4 (avg)   2         5         2          1m
```

今はキューに8つのtaskが登録されてる状態です。replicasが2のため、分子が8/2=4になっています。分母の4はtargetAverageValueの値。
もしこの状態で新たに4つtaskが登録されたとします（8+4=12）。そうすると以下のようになります。

```console
$ kubectl get hpa
NAME                         REFERENCE                   TARGETS             MINPODS   MAXPODS   REPLICAS   AGE
celery-worker                Deployment/celery-worker    6/4 (avg)   2         5         2          2m
```

今はPodが2つなので12/2=6になっています。
こうするとtargetAverageValueが4になるようにPodがAutoScalingされます。
そのため、Podが1つ増えます。そうすると12/3=4になるので以下のようになります。

```console
$ kubectl get hpa
NAME                         REFERENCE                   TARGETS             MINPODS   MAXPODS   REPLICAS   AGE
celery-worker                Deployment/celery-worker    4/4 (avg)   2         5         3          5m
```

replicasが3になっています。もしtaskの数が減ればscale-inされます。
ということで終わりです。

今回はRedisのkeyでやりましたが、SQSのメッセージ数でAutoScalingとか、ALBのレスポンスタイムとかを見て増やしたりとかも可能です。Datadogのmetricsが使えるとなると可能性は無限大です。
※追記：今だとDatadogのCluster Agentのデフォルト値に問題があって普通にChartを指定しても動かないです。環境変数でDD_EXTERNAL_METRICS_PROVIDER_MAX_AGEの値を大きく指定する必要があります。


# まとめ
Datadogのmetricsを使ってHorizontal Pod Autoscalerを設定したらとても便利だった（設定も簡単だった）