PrometheusメトリクスによるPodの水平スケール

## はじめに

Prometheusを活用して何らかのメトリクスを取得し、必要に応じてPodを自動的にスケールさせたいケースがあるかと思います。今回は、Prometheusとsidekiq-exporterを用いて、Redisの待機中のジョブの数を取得し、ジョブが多ければPodを水平スケール(HPA)させるようにします。

## Prometheus on Kubernetes & HPAのアーキテクチャ

Prometheusは複数のコンポーネントによって成り立っています。今回はわかりやすくするために図を省略しており、厳密には以下の画像より多くのリソースが存在します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/b698c828-344e-e419-70e1-1eb5c1123ee2.png" width="100%">

prometheus-operaterの以下の図が理解しやすいと思います。

<img src="https://github.com/prometheus-operator/prometheus-operator/raw/9c0db5656f04e005de6a0413fd8eb8f11ec99757/Documentation/custom-metrics-elements.png" width="100%">

Ref: https://github.com/prometheus-operator/prometheus-operator/blob/9c0db5656f04e005de6a0413fd8eb8f11ec99757/Documentation/troubleshooting.md#troubleshooting-servicemonitor-changes

## 1. Redisとsidekiq-exporterを接続する

exporterの導入は至って簡単です。以下のようにDeploymentを作成します。REDIS_URLはElasticCache for Redisのエンドポイントとします。
```yml:
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: sidekiq
  name: sidekiq-exporter
spec:
  selector:
    matchLabels:
      app: sidekiq-exporter
  replicas: 1
  template:
    metadata:
      labels:
        app: sidekiq-exporter
      name: sidekiq-exporter
    spec:
      containers:
      - name: sidekiq-exporter
        image: strech/sidekiq-prometheus-exporter:latest
        env:
        - name: REDIS_URL
          valueFrom:
            secretKeyRef:
              name: sidekiq-exporter-secret
              key: redis_url
        ports:
        - name: exporter
          containerPort: 9292
```

また、Prometheusがexporterからメトリクスを取得できるようにServiceとServiceMonitorを作成します。ServiceMonitorは、prometheus-operatorによって提供されるカスタムリソース定義(CRD)であり、サービスオブジェクトでEndpointsを検出し、PrometheusがこれらのPodを監視するように設定します。
```yml:
apiVersion: v1
kind: Service
metadata:
  namespace: sidekiq
  name: sidekiq-exporter-service
  labels:
    app: sidekiq-exporter
spec:
  selector:
    app: sidekiq-exporter
  ports:
  - name: exporter
    port: 9292
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  namespace: sidekiq
  name: sidekiq-exporter-service-monitor
spec:
  selector:
    matchLabels:
      app: sidekiq-exporter
  endpoints:
  - port: exporter
```

sidekiq-exporterがRedisと接続できているか確認します。ポートフォワードを使ってsidekiq-exporterのPodをローカルに転送します。
```
kubectl port-forward -n sidekiq <redis-exporterのPod> 8080:9292
```

curlコマンドでメトリクスが表示されると接続は上手くいっています。
```
curl -s localhost:8080/metrics
```

## 2. prometheus-operaterの導入

prometheus-operatorはPrometheusおよび、その関連ツール(AlertmanagerやGrafana etc)をKubernetesリソースとして管理することができます。今回は必要なものだけインストールしていきます。

```yml:prometheus-operater.yaml
・
・
spec:
  project: default
  source:
    repoURL: https://github.com/prometheus-community/helm-charts.git
    path: charts/kube-prometheus-stack/crds/
    targetRevision: kube-prometheus-stack-37.0.0
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: default
  syncPolicy:
    automated:
      prune: true
    syncOptions:
    - Replace=true
---
・
・
spec:
  project: default
  source:
    repoURL: 'https://prometheus-community.github.io/helm-charts'
    targetRevision: 37.0.0
    helm:
      skipCrds: true
      values: |
        alertmanager:
          enabled: false
        grafana:
          enabled: false
        kubeApiServer:
          enabled: false
        kubelet:
          enabled: false
        kubeControllerManager:
          enabled: false
        coreDns:
          enabled: false
        kubeDns:
          enabled: false
        kubeEtcd:
          enabled: false
        kubeScheduler:
          enabled: false
        kubeProxy:
          enabled: false
        kubeStateMetrics:
          enabled: false
        nodeExporter:
          enabled: false
        prometheusOperator:
          admissionWebhooks:
            enabled: false
            patch:
              enabled: false
        prometheus:
          prometheusSpec:
            serviceMonitorSelectorNilUsesHelmValues: false
            serviceMonitorNamespaceSelector: {}
            serviceMonitorSelector: {}
    chart: kube-prometheus-stack
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: default
  syncPolicy:
    automated:
      prune: true
    syncOptions:
    - Replace=true
```

以下でPrometheusのコンソール画面が表示できると思います。sidekiq-exporterがUpになっていれば大丈夫です。
```
kubectl port-forward prometheus-prometheus-operator-kube-p-prometheus-0 8000:9090
```

## 3. prometheus-adapterがPrometheusからメトリクスを取得し、APIとして使えるようにする

```yml:
・
・
spec:
  project: default
  source:
    repoURL: 'https://prometheus-community.github.io/helm-charts'
    targetRevision: 3.0.0
    helm:
      values: |
        rules:
          default: false
          custom:
          - seriesQuery: 'sidekiq_enqueued_jobs{namespace!="",service!=""}'
            resources:
              overrides:
                namespace:
                  resource: namespace
                service:
                  resource: service
            name:
              as: sidekiq_enqueued_jobs
            metricsQuery: '<<.Series>>{<<.LabelMatchers>>}'
        prometheus:
          url: http://prometheus-operator-kube-p-prometheus.default
        logLevel: 2
    chart: prometheus-adapter
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: default
```

以下のコマンドを打って表示されれば大丈夫です。
```
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/sidekiq/services/sidekiq-exporter-service/sidekiq_enqueued_jobs | jq .
```

# 4. custom.metrics.k8s.io APIを使ってHPAする

カスタムメトリクスAPIに登録したsidekiq_enqueued_jobsの値に応じてPodの数をコントロールすることができます。metrics.object.target.valueで閾値を設定します。以下の設定で待機中のジョブが1つ以上になるとPodが水平スケールするようになります。

```yml:
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: hpa
  namespace: sidekiq
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: <水平スケーツの対象>
  minReplicas: 1
  maxReplicas: 2
  metrics:
  - type: Object
    object:
      describedObject:
        apiVersion: v1
        kind: Service
        name: sidekiq-exporter
      metric:
        name: sidekiq_enqueued_jobs
      target:
        type: Value
        value: 1
```

## 参考文献

### sidekiq-exporter

https://github.com/Strech/sidekiq-prometheus-exporter


### prometheus-operater

https://github.com/prometheus-operator/prometheus-operator

https://github.com/helm/charts/tree/master/stable/prometheus-operator

### prometheus-adapter

https://qiita.com/masahata/items/062a2ee5762b108e8850

https://github.com/helm/charts/blob/master/stable/prometheus-adapter/README.md

https://github.com/kubernetes-sigs/prometheus-adapter/blob/master/docs/config-walkthrough.md

### Horizontal Pod Autoscaling (HPA) 

https://qiita.com/Ladicle/items/5ff251b89df2f1ebb821

https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#scaling-on-custom-metrics

### その他

https://kubernetes.io/ja/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/