Prometheus Adapterを利用したPodのオートスケール

## はじめに

[独自メトリクスによるPodの水平スケール](https://qiita.com/Ladicle/private/f97ab3653e8efa0e9d58) では独自メトリクスの種類と、設定方法、そして独自メトリクスを提供するためのAPIサーバをいくつか紹介しました。今回は、その1つであるPrometheus Adapterの概要と、これを利用したPodのオートスケールについて紹介します。

## Prometheus Adapterとは

Prometheus Adapter[^0]は、その名の通りPrometheusの任意のメトリクスをカスタム・外部メトリクスとして利用するためのAPIサーバです。また、現在はDirectXManの個人リポジトリで開発されていますが、現在kubernetes-sigsへの移行作業が進んでおり[^1]、Kubernetes公式リポジトリの1つとなる予定です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26683/739d043d-74e6-6f5d-57f3-a8f464742449.png)

上の図は、Prometheus Adapterの大まかな構成です。Prometheus Adapterは、KubernetesとPrometheusの間をつなぐアダプターとしての役割を持っています。ConfigMapで設定したカスタムメトリクスのルールに合わせ、実際のメトリクスをPrometheusから取得し、メトリクスAPIフォーマットに合わせて値を整形・公開します。

### インストール方法

Prometheus Adapterはインストール用のHelm Chart[^2]を提供しています。Prometheusサーバのアドレスやこのあと説明するメトリクスのルールなど、各種設定はHelmのValuesファイル[^3]から変更できます。各環境に合わせて修正してください。

```bash
$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
$ helm repo update
$ helm install --name my-release prometheus-community/prometheus-adapter
```

正常に起動していれば、以下のようにカスタムメトリクスAPIのレスポンスが返ります。(メトリクスのルール設定に応じて`resources`配列の中身は異なります。)

```bash
$ kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1
{"kind":"APIResourceList","apiVersion":"v1","groupVersion":"custom.metrics.k8s.io/v1beta1","resources":[]}
```

## カスタムメトリクスのルール設定

次の順序つきリストはPrometheus Adapterの処理内容とその順序を示しています。まず、Prometheusから該当のメトリクスが存在するかを確認し、存在すれば実際のKubernetesリソースと紐づけます。そして、それをメトリクスAPIとして公開します。

1. **Discovery**: 有効なメトリクスの確認
2. **Association**: メトリクスとKubernetesリソースとの紐づけ
3. **Naming**: カスタムメトリクスAPIとして公開するための名前づけ
4. **Quering**: Prometheusからメトリクスを取得するためのクエリ実行

カスタムメトリクスの設定はConfigMapにconfig.yamlとして保存します。config.yamlのフォーマットは、先程説明したのPrometheus Adapterの動作に合わせ、大まかに4つに分かれています。また、ルールは複数個設定可能です。

```yaml
rules:
  # Discovery: Discovery対象のSeriesを指定する (関数は使えない)
- seriesQuery: 'http_request_duration_seconds_bucket{deploy_namespace!="",deploy_name!=""}'
  # Association: メトリクスをマッピングするリソース設定、APIのパスになる
  resources:
    overrides:
      deploy_name: {resource: "deployment", group: "apps"}
      deploy_namespace: {resource: "namespace"}
  # Naming: メトリクス名として公開する名前
  name:
    as: 'http_request_per_seconds'
  # Quering: 公開するメトリクスをPrometheusから取得するためのクエリ
  metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)'
```

### 読み取り権限の付与

Prometheus AdapterにはPod以外のリソースの参照権限が付与されていないため、カスタムメトリクスに他のリソースを設定した場合は権限の付与が必要です。 読み取りリソースの権限は`prometheus-adapter-resource-reader`というClusterRoleに定義されているので、ここに必要な設定を追加してください。例えば、Deploymentの参照権限を追加したい場合は以下のとおりです。

```yaml
- apiGroups:
  - "apps"
  resources:
  - deployments
  verbs:
  - get
  - list
  - watch
```

### 困ったときは

設定を間違えているとメトリクスAPIは空のresources配列を返します。Prometheus Adapterのリポジトリにもよくresourcesが空になるというIssueが作られていますが、多くの場合ユーザの設定ミスです。以下の点に注意して設定を見直しましょう。

1. Prometheus AdapterのPodが正常に起動しており、エラーログを吐いていないか
2. SeriesQueryに関数を使ってないか
3. Namingで利用するラベルがメトリクスに含まれているか
2. Prometheusでも該当のクエリ結果が正常に返るか
3. 該当リソースを取得する権限がRBACで付与されているか

## 設定したカスタムメトリクスの確認

すべての設定が正常に完了すれば、設定したカスタムメトリクスAPIを叩くことができます。`kubectl get`からAPIのルートを叩くと、登録されているカスタムメトリクスの一覧が表示されます。

```bash
$ kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 | jq
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "custom.metrics.k8s.io/v1beta1",
  "resources": [
    {
      "name": "namespaces/http_request_per_seconds",
      "singularName": "",
      "namespaced": false,
      "kind": "MetricValueList",
      "verbs": [
        "get"
      ]
    },
    {
      "name": "service/http_request_per_seconds",
      "singularName": "",
      "namespaced": true,
      "kind": "MetricValueList",
      "verbs": [
        "get"
      ]
    }
  ]
}
```

またnamespaceにはワイルドカード(`*`)を使えませんが、nameには次のようにワイルドカードを利用できます。レスポンスのvalueは、このときのメトリクス値です。

```bash
$ kubectl get --raw '/apis/custom.metrics.k8s.io/v1beta1/namespaces/test_namespace/deployments.apps/*/http_request
_per_seconds'|jq
{
  "kind": "MetricValueList",
  "apiVersion": "custom.metrics.k8s.io/v1beta1",
  "metadata": {
    "selfLink": "/apis/custom.metrics.k8s.io/v1beta1/namespaces/test_namespace/deployments.apps/%2A/http_request_per_seconds"
  },
  "items": [
    {
      "describedObject": {
        "kind": "Deployment",
        "namespace": "test_namespace",
        "name": "testapp",
        "apiVersion": "apps/v1"
      },
      "metricName": "http_request_per_seconds",
      "timestamp": "2020-11-22T08:40:47Z",
      "value": "68",
      "selector": null
    }
  ]
}
```

## カスタムメトリクスを使ったHPAの動作確認

カスタムAPIが正常に値を返すようになれば後はHPAにカスタムメトリクスを設定するだけです。(詳細な設定方法は[前回](https://qiita.com/Ladicle/private/f97ab3653e8efa0e9d58#%E7%8B%AC%E8%87%AA%E3%83%A1%E3%83%88%E3%83%AA%E3%82%AF%E3%82%B9%E3%81%AE%E6%8C%87%E5%AE%9A%E6%96%B9%E6%B3%95)の記事を参照してください。) 次のマニフェストは、HPAに`http_request_per_seconds`というカスタムメトリクスを設定した例です。

```yaml
kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta1
metadata:
  name: testapp
spec:
  scaleTargetRef:
    kind: Deployment
    name: testapp
  metrics:
  # Pod以外のカスタムメトリクスはObject
  - type: Object
    object:
      describedObject:
        apiVersion: apps/v1
        kind: Deployment
        name: testapp
      metric:
        name: http_request_per_seconds # カスタムメトリクス名
      target:
        type: Value # Value
        value: "100" # しきい値
```

作成したHPAリソースを取得すると、`.status.conditions`の`ScalingActive`タイプから正常にカスタムメトリクスが設定できるていることが確認できます。またheyコマンドあたりで負荷をかけると、しきい値を超えたところでスケールアウトしていることが確認できます。

```yaml
$ kubectl get hpa.v2beta2.autoscaling testapp -n test_namespace -o yaml
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
...
  metrics:
  - object:
      describedObject:
        apiVersion: apps/v1
        kind: Deployment
        name: testapp
      metric:
        name: http_request_per_seconds
      target:
        type: Value
        value: "100"
    type: Object
...
status:
  conditions:
  - lastTransitionTime: "2020-11-22T07:42:03Z"
    message: the HPA was able to successfully calculate a replica count from object
      metric http_request_per_seconds
    reason: ValidMetricFound
    status: "True"
    type: ScalingActive
...
```

## おわりに

今回は、まもなくkubernetes-sigsに取り込まれるであろうPrometheus Adapterについて解説しました。また、インストールのためのHelm Chartも、公式Stable Chartsリポジトリのアーカイブに合わせ既にPrometheus Communityに移管されています。メトリクス管理にPrometheusを利用している方にとっては、HPAカスタムメトリクスを利用する際の良い選択肢になるかと思います。

[^0]: [DirectXMan12/k8s-prometheus-adapter: An implementation of the custom.metrics.K8s.io API using Prometheus](https://github.com/directxman12/k8s-prometheus-adapter)

[^1]: [Migrate DirectXMan12/k8s-prometheus-adapter to kubernetes-sigs · Issue #2182 · kubernetes/org](https://github.com/kubernetes/org/issues/2182)

[^2]: [helm-charts/charts/prometheus-adapter at main · prometheus-community/helm-charts](https://github.com/prometheus-community/helm-charts/tree/main/charts/prometheus-adapter)

[^3]: <https://github.com/prometheus-community/helm-charts/blob/main/charts/prometheus-adapter/values.yaml>