kube-state-metrics でカスタムリソースのメトリクスを取得する方法

## はじめに

Kubernetes を利用していると、カスタムリソースやカスタムコントローラーをして使用して機能を追加するケースが出てきます。それらのメトリクスを取得したい場合は対応する Exporter を自作するなりして用意する必要があります。

そこで、kube-state-metrics でもカスタムリソースをサポートするための機能(Custom Resource State Metrics)が `v2.4.0` から追加されました。該当機能のリリース当初はそこまで使いやすい機能ではなかったのですが、執筆時点の最新版の `v2.8.2` ではだいぶ改善されて使いやすくなりました。

ただ、[ドキュメント](https://github.com/kubernetes/kube-state-metrics/blob/main/docs/customresourcestate-metrics.md)がそこまで整備されておらず、ソースコードを確認しながらでないと使うのが難しいので記事にまとめてみました。

## 2024/10/24 追記

:::note warn
kube-state-metrics でカスタムリソースのメトリクスを出力する機能についてメンテナンスモードへ移行しました。該当機能は kube-state-metrics から切り離し、[KEP-4785: CRDMetrics Controller](https://github.com/kubernetes/enhancements/pull/4811) で別途専用のコントローラーを開発することが検討されています。
:::

### KEP-4785 CRDMetrics Controller について
 
 >[kubernetes/kube-state-metrics#1710] introduced the Custom Resource State API to Kube State Metrics, which allowed for generating metrics from Custom Resources' schemata.
>   :
> After many cycles of trading off maintenance complexity for more features, and seeing its maturity, the maintainers agreed on putting this on a "maintenance-only" mode in favor of a better solution that prioritizes scalability in the long run, while ensuring that it meets the same expectationsas its predecessor and more.

上記にも書かれている通り、kube-state-metrics でカスタムリソースのメトリクスを出力する機能については、 `experimental` のまま `maintenance-only` モードに移行しました。

該当機能によって kube-state-metrics が複雑になりメンテナンスが難しくなってきたので、切り離して `CRDMetrics Controller` の方で同等の機能を開発していくようです。

### kube-state-metrics 側での該当機能に関する開発状況
 
該当機能に関する追加開発については、 `KEP-4785 CRDMetrics Controller` でカバーされるということで開発は中止となり、クローズされいます。
 
- https://github.com/kubernetes/kube-state-metrics/issues/2050#issuecomment-2361632754
- https://github.com/kubernetes/kube-state-metrics/issues/1948#issuecomment-2391898474

### カスタムリソースのメトリクスを出力したい場合の今後

現状では、独自に exporter を作成するか、kube-state-metrics を利用するケースが多いと思います。(kube-state-metrics については `experimental` のまま `maintenance-only` モードになっている点に留意してください)

CRDMetrics Controller が現在、Proposal が策定中なので、その進捗が Beta release まで進んだくらいからそちらを使用するケースが増えてくると思います。(まだ Proposal の状態なので、実際に Stable release まで進むのかも不明なので、そこは注意する必要があります)

## kube-state-metrics とは

以下が[公式の Overview](https://github.com/kubernetes/kube-state-metrics#overview) から一部抜粋してきた説明文になります。

>kube-state-metrics is a simple service that listens to the Kubernetes API server and generates metrics about the state of the objects.

上記の説明に書いてある通り、 Kubernetes の API server を定期的に確認して Kubernetes のオブジェクトの状態をメトリクスとして提供してくれます。

今回の話は、メトリクスを取得する対象のオブジェクトに任意のカスタムリソースを指定する機能(Custom Resource State Metrics)の話になります。

## 概要

### 実際の利用イメージ

詳細な説明をする前に、簡単な利用イメージを先に説明します。


1. 設定ファイルの作成
  以下のような設定ファイルを作成します。

    ```yaml
    kind: CustomResourceStateMetrics
    spec:
      resources:
      - groupVersionKind:  #　対象となるカスタムリソースの情報を設定
          group: myteam.io
          kind: "Foo"
          version: "v1"
    
        metrics:   
          - name: "uptime"  # メトリクス名
            help: "Foo uptime"  # HELP に表示するメッセージを設定
            each:  
              type: Gauge  # メトリクスの Type を選択
              gauge:
                path: [status, uptime]  # メトリクスの値の設定。 e.g. `sataus.uptime` の値が設定される
    
          - name: "info"
            help: "Foo version"
            each:
              type: Info　 
              info:  # 条件に一致するときに value が　`1` になるメトリクス 
                labelsFromPath:
              　　　　　　　　　version: [spec, version]  # e.g. `spec.version` に値が存在するときにメトリクスが出力される 
    
          - name: "phase"
            help: "Foo phase"
            each:
              type: StateSet
              stateSet:  # list で指定した条件に一致する数をカウントして出力するメトリクス
                path: [status, phase]  # e.g. `status.phase` の配下で list に一致するものをカウントする
                labelName: phase  # list のラベルをメトリクスに追加する時の key 名の設定
                list: [Active, Running, Terminating, Pending]
    
    ```

2. kube-state-metrcsの引数に設定
  `--custom-resource-state-config-file` 等を用いて、kube-state-metrics に設定を読み込ませます。

    ```yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        k8s-app: kube-state-metrics
      name: kube-state-metrics
      namespace: kube-system
    spec:
      replicas: 1
      selector:
        matchLabels:
          k8s-app: kube-state-metrics
      template:
        metadata:
          labels:
            k8s-app: kube-state-metrics
        spec:
          containers:
          - name: kube-state-metrics
            args:
            - --custom-resource-state-config-file=/ksm-config/custom-resource-state-metrics.yaml  # 引数に設定ファルの path を設定する
            image: registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.8.2
                :
            volumeMounts:
            - mountPath: /ksm-config
              name: custom-resource-state-config-volume
          volumes:
          - configMap:
              items:
              - key: custom-resource-state-metrics.yaml
                path: custom-resource-state-metrics.yaml
              name: custom-resource-state-config
            name: custom-resource-state-config-volume
    ```


    :::note warn
    **注意点**
    `kind: CustomResourceStateMetrics` カスタムリソースではなく、単にそういう設定ファイルを用意して `--custom-resource-state-config-file` で対象のファイルパスを指定して使います。Kubernetes に使い慣れているとそういうカスタムリソースがあると誤解するケースが多いですが、ただの設定ファイルのフォーマットの話なので気をつけてください。`CustomResourceStateMetrics`をカスタムリソースにする件についてはこちらの [issue](https://github.com/kubernetes/kube-state-metrics/issues/1948) で現在検討中になります。


3. メトリクスの出力
  正しく設定できていると以下のようなメトリクスが出力されます

    - メトリクスタイプが`Gauge`の場合 

    ```sh
    # HELP kube_customresource_uptime Foo uptime
    # TYPE kube_customresource_uptime gauge
    kube_customresource_uptime{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1"} 43.21
    ```

    - メトリクスタイプが`Info`の場合 

    ```sh
    # HELP kube_customresource_info Foo version
    # TYPE kube_customresource_info info
    kube_customresource_info{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",version="v1.2.3"} 1
    ```

    - メトリクスタイプが`StateSet`の場合 

    ```sh
    # HELP kube_customresource_phase Foo phase
    # TYPE kube_customresource_phase stateset
    kube_customresource_phase{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",phase="Active"} 0
    kube_customresource_phase{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",phase="Pending"} 1
    kube_customresource_phase{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",phase="Running"} 0
    kube_customresource_phase{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",phase="Terminating"} 0
    ```


### 利用用途

kube-state-metrics でもカスタムリソースのメトリクスを出力する機能を`Custom Resource State Metrics`と言います。こちらの機能を使用する利用用途について記載します。

#### カスタムリソースのメトリクスを取得する

カスタムコントローラーを自作したり、OSSで公開されているものを利用したりする際に、カスタムリソースのメトリクスを取得したいケースで利用します。

自分達で exporter を作成して運用していくコストと kube-state-metrics の該当機能を使用して運用していくコストを比較して採用の可否を決定していくことになります。

#### VerticalPodAutoscaler のメトリクスを取得する

kube-state-metrics の`v2.9.0`から VerticalPodAutoscaler のメトリクスが出力されなく
なります。そのため該当のメトリクスを使用している既存の利用者は `Custom Resource State Metrics` を使用する必要があります。

こちらの変更については VerticalPodAutoscaler の`v1beta2`から`v1`への移行をどうするかが話し合われた結果、この方針となりました。詳細はこちらの [issue](https://github.com/kubernetes/kube-state-metrics/issues/1718) に記載されています。

上記の決定になった理由としては、「kube-state-metrics で出力するメトリクスについて Kubernetes のコアリソースのみを対象とする」と方針に従うためです。

今後、「Gateway API」等のリソースに関するメトリクスについては、kube-state-metrics としては　`Custom Resource State Metrics`機能を使用する方向になっていく可能性があることに注意してください。


## 詳細な説明

### 利用方法

`Custom Resource State Metrics`を用いてカスタムリソースのメトリクスを取得するためには、以下の２つの作業が必要になります。

- 設定ファイルの追加
- 対象リソースに対する参照権限の追加

#### 1. 設定ファイルの追加

`Custom　Resource　State　Metrics` の設定ファイルを作成して以下のいずれかの方法で kube-state-metrics に設定します。

- `--custom-resource-state-config` を利用して、インラインで設定を記述する
- `--custom-resource-state-config-file` でファイルパスを指定する

#### 2. 対象リソースに対する参照権限の追加

kube-state-metrics に対して対象のリソースの`list`と `watch`の権限を付与します。

e.g. 

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: kube-state-metrics
rules:
- apiGroups: ["autoscaling.k8s.io"]
  resources:
  - verticalpodautoscalers
  verbs: ["list", "watch"]
```

## `Custom Resource State metrics`の設定

`Custom Resource State metrics`の設定に関する内容について記載します。

### `Custom Resource State metrics`に関するフラグの一覧

Name | Value | Description
-- | -- | --
`--custom-resource-state-config` | String | Custom Resource State metrics の設定ファイルをインラインで記述できる
`--custom-resource-state-config-file` | String | Custom Resource State metrics の設定ファイルのパスを指定できる
`--custom-resource-state-only` | Boolean | 有効化するとコアリソースのメトリクスを出力せずにカスタムリソースのメトリクスのみを出力するようになる

`--custom-resource-state-config`と`--custom-resource-state-config-file`の両方を指定した場合は`--custom-resource-state-config`の設定が優先されます

#### `--custom-resource-state-config`の説明

`--custom-resource-state-config`を使用するとインラインで設定を記述できます。

e.g.

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kube-state-metrics
  namespace: kube-system
spec:
  template:
    spec:
      containers:
      - name: kube-state-metrics
        args:
          - --custom-resource-state-config
          -  |
              spec:
                resources:
                  - groupVersionKind:
                      group: myteam.io
                      version: "v1"
                      kind: Foo
                    metrics:
                      - name: active_count
                        help: "Count of active Foo"
                        each:
                          type: Gauge
                          ...
```

設定が長くなると管理が難しくなるので、後述する`--custom-resource-state-config-file`を使うケースの方が多いと思います。

#### `--custom-resource-state-config-file`の説明

`--custom-resource-state-config-file`に設定ファイルのパスを指定することができます。

e.g.

```yaml
spec:
  containers:
  - name: kube-state-metrics
    image: registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.8.2
    args:
    - --custom-resource-state-config-file=/ksm-config/custom-resource-state-metrics.yaml
    :
```

パスに指定しているファイルについては初回起動時だけでなく、定期的の読み込んで設定を反映してくれるので、設定変更時にkube-state-metricsの再起動は不要です。

現時点で使用する場合はインラインで記述する`--custom-resource-state-config`よりも本機能を使用するのがいいと思います。

こちらの[issue](https://github.com/kubernetes/kube-state-metrics/issues/1948)の内容が反映されればフラグでの設定ではなく該当の設定用のカスタムリソースを使用するケースが一般的になるかもしれません。

#### `--custom-resource-state-only`の説明

`--custom-resource-state-only=true`を指定するとDeployment等のコアリソースに関するメトリクスを出力せずに、カスタムメトリクスのメトリクス専用にすることができます。

```yaml
spec:
  containers:
  - name: kube-state-metrics
    image: registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.8.2
    args:
    - --custom-resource-state-config-file=/ksm-config/custom-resource-state-metrics.yaml
    - --custom-resource-state-only=true
    :
```

主なユースケースは以下の２つになると思います。

- 開発時
  `Custom Resource State metrics`の設定ファイル作成時に、他のメトリクスが出力されないと確認が楽になるのでそういうケースで利用します。

- 商用運用時
  コアリソースのメトリクスを出力しているkube-state-metricsと分けて管理したいときの利用が考えられます。`Custom Resource State metrics`の設定ファイルの書き方を間違えるとkube-state-metricsが落ちるので、その影響を避けるため等で分けたいケースがあると思います。


### `Custom Resource State metrics`の設定ファイルの設定イメージ

上述の`簡単な利用イメージ` に対して、オプショナルで設定できる値を追加した場合のイメージを記載します。

```yaml
kind: CustomResourceStateMetrics
spec:
  resources:
  - groupVersionKind:     
      group: myteam.io
      kind: "Foo"
      version: "v1"
    resourcePlural: "" 
    metricNamePrefix: "myteam_foos"
    errorLogV: 0
    commonLabels:  
      crd_type: "foo"
    labelsFromPath:
      name: [metadata, name]

    metrics:   
    - name: "ready_count"
      help: "Number Foo ready"
      each:  
        type: Gauge 
        gauge:
          path: [status, sub]
        labelFromKey: type
        labelsFromPath:
          active: [active]
      labelsFromPath:
        "*": [metadata, labels]
      commonLabels:  
        metrics_type: "gauge"
      errorLogV: 9

    - name: "info"
      help: "Foo version"
      each:
        type: Info
        info:
          path: [spec]
          labelsFromPath:
            version: [version]
          labelFromKey: spec
      labelsFromPath:
        "*": [metadata, labels]
      commonLabels:  
        metrics_type: "info"
      errorLogV: 9

    - name: "phase"
      help: "Foo phase"
      each:
        type: StateSet
        stateSet:
          path: [status, phase]
          labelName: phase
          list: [Active, Running, Terminating, Pending]
      labelsFromPath:
        "*": [metadata, labels]
      commonLabels:  
        metrics_type: "info"
      errorLogV: 9
```

設定内容を大きく分けると以下の3つに分かれます。

- `resources`: リソース全体に対する設定
- `resources.metrics`：　メトリクス毎の設定
- `resources.metrics.each`：　選択したメトリクスタイプ毎の固有の設定

### `Custom Resource State metrics`の設定値の説明

`Custom Resource State metrics`で設定値について記載します。

#### `spec.resources`

リソース全体に関する設定

Name | Require | Description
-- | -- | --
groupVersionKind | 必須 | 対象となるカスタムリソースの情報の group, version, kind を指定する
resourcePlural | 任意 | 未指定の場合は kind の lower case の複数形が代入される。CRD で設定した plural と異なる場合は指定が必要
metricNamePrefix | 任意 | メトリクス名の prefix の設定。未指定の場合は`kube_customresource_`が代入される
commonLabels | 任意 | ラベルの追加。メトリクスに指定した任意のkey,valueのラベルを追加できる
labelsFromPath | 任意 | ラベルの追加。メトリクスに指定した任意のkeyとCRの値に応じたvalueのラベルを追加できる
errorLogV | 任意 | ログレベルを指定できる
metrics | 必須 | メトリクスに関する設定

ref https://github.com/kubernetes/kube-state-metrics/blob/v2.8.2/pkg/customresourcestate/config.go#L45-L65

- 設定イメージ

```yaml
kind: CustomResourceStateMetrics
spec:
  resources:
  - groupVersionKind:     
      group: myteam.io
      kind: "Foo"
      version: "v1"
    resourcePlural: "" 
    metricNamePrefix: "myteam_foos"
    errorLogV: 0
    commonLabels:  
      crd_type: "foo"
    labelsFromPath:
      name: [metadata, name]
```


##### `groupVersionKind`の説明

メトリクスを取得をしたい対象のカスタムリソースの group, version, kind　(GVK)を指定します。

```yaml
- groupVersionKind:      
    group: myteam.io
    kind: "Foo"
    version: "v1"
```

ちなみ、こちらで既存のDeployment等のリソースを指定した場合には以下のようなエラーとなり取得できません。

```
E0330 13:33:15.578686       1 reflector.go:513] pkg/mod/k8s.io/client-go@v0.26.1/tools/cache/reflector.go:169: expected gvk apps/v1, Kind=deployments, but watch event object had gvk apps/v1, Kind=Deployment
E0330 13:33:15.582022       1 reflector.go:513] pkg/mod/k8s.io/client-go@v0.26.1/tools/cache/reflector.go:169: expected gvk apps/v1, Kind=deployments, but watch event object had gvk apps/v1, Kind=Deployment
```

指定できるのはカスタムリソースのみになります。

##### `resourcePlural`の説明

対象のリソースのpluralの値を指定します。未指定の場合はkubebuilderでのデフォルトの作成時の挙動に合わせるためにkindの`lower case`の複数形が代入されるようになっています。

`kind=Foo`を設定している場合に以下の指定をすると`resourcePlural=foos`が設定されます。

```yaml
- resourcePlural: "" 　　
```

##### `metricNamePrefix`の説明

出力されるメトリクスをの Prefix を指定できます。未指定の場合は `kube_customresource_` がメトリクスの Prefix になります。

```yaml
- metricNamePrefix: "myteam_foos" 
```

e.g. 

```diff
# 未設定　　`kube_customresource_`が prefix になる
-　kube_customresource_uptime{crd_type="foo",customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",name="foo",namespace="kube-system"} 43.21

# "myteam_foos" を設定時
+ myteam_foos_uptime{crd_type="foo",customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",name="foo",namespace="kube-system"} 43.21
```


##### `errorLogV`の説明

ログレベルを指定できます。未指定の場合は`0`になります。

```yaml
- errorLogV: 0   
```

実際に触ってみた感じだと、`kind: Custom　Resource　State　Metrics`を記述する際にどこで設定を間違えてる確認する時に一時的にログレベルを上げるのが主な使い道になるかなと思います。

##### `commonLabels`の説明

出力するメトリクスに任意のラベルを追加する機能になります。

```yaml
- commonLabels:    
    crd_type: "foo"
```

上記の例だと、`crd_type`がkeyで`foo`がvalueに入る形でラベルが追加されます。

```diff
# 未設定
- kube_customresource_uptime{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1"} 43.21

# 設定時 e.g. crd_type="foo" が追加される
+ kube_customresource_uptime{crd_type="foo",customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1"} 43.21
```


##### `labelsFromPath`の説明

対象のリソースの状態に合わせてメトリクスにラベルを追加する機能になります。
ラベルに付与するkey名の指定方法が2パターンあります。

- key名に任意の値を指定する
- `*`を使用して条件に合致する全てをラベルに追加する

e.g. key名に任意の値を指定する

```yaml
- labelsFromPath:    
    name: [metadata, name]
```

上記の場合だと name が key になり、`[ metadata, name ]`を指定するとカスタムリソースの`metadata.name`の値がメトリクスのラベルに追加されます。

e.g. 

```diff
# 未設定
- myteam_foos_uptime{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1"} 43.21

# 設定時　e.g. name="foo"　が追加される
+ myteam_foos_uptime{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",name="foo"} 43.21
```

e.g. `*`を使用して条件に合致する全てをラベルに追加する

```yaml
- labelsFromPath:    
    "*": [metadata, labels]  
```

```diff
# 未設定
- myteam_foos_uptime{crd_type="foo",customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1"} 43.21

# 設定時 e.g. `metadata.labels` に該当する全てがメトリクスに追加される bar="baz" foo="bar"
+ myteam_foos_uptime{bar="baz",crd_type="foo",customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",foo="bar"} 43.21
```

labels や annotations などの情報の全てをラベルとしてメトリクスに追加したいケースで使用します。ただ、メトリクスのラベルが大量に増えると　Prometheus等の性能の問題を引き起こす恐れがあるのでkey名を指定したものを使用した方が良いです。もし、`*`を使う場合は　`Info`のメトリクスだけに使用して、他のメトリクスで使用したいときは`Info`のメトリクスとjoinする使い方をした方が良いです。

`commonLabels`が`key/value`を指定してラベルを追加するのに対して、`labelsFromPath`はカスタムリソースの内容に応じてラベルを追加する機能になります。


#### `spec.resources.metrics`

各メトリクス単位の設定

Name | Require | Description
-- | -- | --
name | 必須 | メトリクスの名前を指定する。メトリクス名は `metricNamePrefix` + `name` となる
help | 任意 | HELPに表示するメッセージに任意の文字列を追加できる
commonLabels | 任意 | `resources` の `commonLabels` と設定方法は同じ。こちらは設定の範囲がメトリクス単位となる
labelsFromPath | 任意 | `resources` の `labelsFromPath` と設定方法は同じ。こちらは設定の範囲がメトリクス単位となる
errorLogV | 任意 | `resources` の `errorLogV` と設定方法は同じ。こちらは設定の範囲がメトリクス単位となる
each | 必須 | メトリクスの `type` の選択し、固有の設定を指定する

ref https://github.com/kubernetes/kube-state-metrics/blob/v2.8.2/pkg/customresourcestate/config.go#L123-L136

- 設定イメージ

```yaml
metrics:    
- name: "uptime"   
  help: "Foo uptime"  
  each:  
    type: Gauge  
    gauge:
      path: [status, uptime]
  commonLabels:
    custom_metric: "yes"
  labelsFromPath:    
    name: [metadata, name]
  errorLogV: 0   
```

##### `name`の説明

出力するメトリクスの名前になります。 `resources.metricNamePrefix` で設定した値と`name`で指定した値によって出力するメトリクス名前が決まります。

```yaml
- name: "uptime"    
```

`resources.metricNamePrefix`が未指定の場合は`kube_customresource_`が代入されるので出力されるメトリクスは以下のようになります。

```sh
kube_customresource_uptime{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1"} 43.21
```

##### `help`の説明

メトリクスの HELP に表示するメッセージを指定できます。

```yaml
- help: "Foo uptime" 
```

e.g. 

```diff
# 未設定
- # HELP myteam_foos_uptime

# 設定時
+ # HELP myteam_foos_uptime Foo uptime
```

設定すると `# HELP <メトリクス名>` の後に設定した文字列が表示されるようになります。


##### `commonLabels`の説明

`resources`の`commonLabels`と設定方法は同じ。
こちらは設定の範囲がメトリクス単位となる

##### `labelsFromPath`の説明

`resources`の`labelsFromPath`と設定方法は同じ。
こちらは設定の範囲がメトリクス単位となる

##### `errorLogV`の説明

`resources`の`errorLogV`と設定方法は同じ。
こちらは設定の範囲がメトリクス単位となる

##### `each`の説明

メトリクスの`type`の選択し、固有の設定を指定する.
詳細は後述する`type`毎の説明に記載。


#### `spec.resources.metrics.each`

メトリクスのTypeに関する設定

Name | Require | Description
-- | -- | --
type | 必須 | `Gauge`,`Info`,`StateSet` から1つのメトリクス `type`を指定する
gauge | 条件付き必須 | `Gauge`を選択した場合に固有の設定を指定する
info | 条件付き必須 | `Info`を選択した場合に固有の設定を指定する
stateSet | 条件付き必須 | `StateSet`を選択した場合に固有の設定を指定する

ref https://github.com/kubernetes/kube-state-metrics/blob/v2.8.2/pkg/customresourcestate/config.go#L138-L154

[OpenMetrics specification](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#type)では以下のメトリクスの `Type` が規定されています。

- unknown
- gauge
- counter
- info
- stateset
- histogram
- gaugehistogram
- summary

`v2.8.2` の時点では上記の内の以下の 3 つに関する設定が可能です。

- gauge
- info
- stateset

使い方の詳細は後述する`type`毎の説明に記載。


#### `spec.resources.metrics.each.gauge`

Name | Require | Description
-- | -- | --
path | 必須 | メトリクスの value になる値を指定する
labelsFromPath | 任意 | ラベルの追加。`metrics`の`labelsFromPath`と設定方法は同じ。こちらは`path`で指定した場所からの相対パスでの指定になる
valueFrom | 任意 | 出力するメトリクスの条件の指定。`path`配下で数値を持つフィールドを指定することで出力するメトリクスを絞り込む
labelFromKey | 任意 | ラベルの追加。`path`配下のオブジェクトを指定した値をkey名としてラベルに追加する
nilIsZero | 任意 | 指定した条件がnillの場合に、valueが`0`のメトリクスが出力される

ref https://github.com/kubernetes/kube-state-metrics/blob/v2.8.2/pkg/customresourcestate/config_metrics_types.go#L37-L48


- 設定イメージ

```yaml
- name: "ready_count"
  each:  
    type: Gauge 
    gauge:
      path: [status, uptime] 
      nilIsZero: true

- name: "ready_count"
  each:
    type: Gauge
    gauge:
      path: [status, sub]
      valueFrom: [ready]　　　
      labelsFromPath:
        active: [active]　
      labelFromKey: type
  labelsFromPath:
    name: [metadata, name]
```

##### `path`の説明

メトリクスに出力する対象のCRのパスを設定します。
例えばCRの一部が以下のようなっていたとします。

```yaml
status:
  uptime: 43.21
```

この時に`path: [status, uptime]`を指定します。

```yaml
each:  
type: Gauge 
gauge:
  path: [status, uptime] 
```

この時に`status.uptime`に数値が存在するので、該当の値がメトリクスのvalueとして選択されて出力されます。

```sh
kube_customresource_uptime{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1"} 43.21
```

これが`Gauge`の基本的な使い方になります。

path の指定方法については、配列の場合や、特定の`key/value`を指定したい場合などに合わせて以下のような指定も可能です。

```yaml
# simple path lookup
[spec, replicas]                         # spec.replicas == 1

# indexing an array
[spec, order, "0", value]                # spec.order[0].value = true

# finding an element in a list by key=value  
[status, conditions, "[name=a]", value]  # status.conditions[0].value = 45

# if the value to be matched is a number or boolean, the value is compared as a number or boolean  
[status, conditions, "[value=66]", name]  # status.conditions[1].name = "b"
```

また、後述する`spec.resources.metrics.each.gauge`配下の設定についてはpathで指定した位置を起点に設定が反映されることにも注意してください。こちらの注意点については後述します。

##### `valueFrom`の説明

出力するメトリクスの条件の指定になります。
`path`配下で数値を持つフィールドを指定することで出力するメトリクスを絞り込むことができます。

以下のようなCRがあるとします。

```yaml
metadata:
  name: foo
status:
  sub:
    type-a:
      active: 1
      ready: 2　　　　　# メトリクスの value に出力される箇所
    type-b:
      active: 3
      ready: 4　　　　# メトリクスの value に出力される箇所
```

valueFromを以下のように指定します。

```yaml
- name: "ready_count"
  each:
    type: Gauge
    gauge:
      path: [status, sub]
      valueFrom: [ready]
```

上記の場合だとpathで指定した`status.sub`の配下の`ready`に合致するものがメトリクスとして出力される。
出力されるメトリクスは以下になります。

```sh
# `status.sub.type-a.ready=2` と `status.sub.type-b.ready=4`がメトリクスになる
kube_customresource_ready_count{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1"} 2
kube_customresource_ready_count{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1"} 4
```

これだと、途中のpathが`type-a`,`type-b`に依存せずに指定できるのが利点です。ただ、このままだと分かりにくいメトリクスを区別できないため、実際に利用する際には識別できるようにラベルも合わせて追加するのが良いと思います。


##### `labelsFromPath`の説明

`metrics`の`labelsFromPath`と設定方法は同じ。
こちらは`path`で指定した場所からの相対パスでの指定になる点が異なります。

以下のようなCRがあるとします。

```yaml
metadata:
  name: foo　　# メトリクスのラベルに追加される箇所
status:
  sub:
    type-a:
      active: 1　　# メトリクスのラベルに追加される箇所
      ready: 2  
    type-b:
      active: 3　　# メトリクスのラベルに追加される箇所
      ready: 4  
```

labelsFromPathを以下のように指定します。

```yaml
- name: "ready_count"
  each:
    type: Gauge
    gauge:
      path: [status, sub]
      valueFrom: [ready]　　　
      labelsFromPath:
        active: [active]　　　# pathで指定した`status.sub` の位置からのpath指定になります
  labelsFromPath:
    name: [metadata, name]　　　# `.` の位置からのpath指定になります
```

出力されるメトリクスは以下になります。

```sh
# `status.sub.type-a.ready=2` と `status.sub.type-b.ready=4`がメトリクスになる
kube_customresource_ready_count{active="1",customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",name="foo"} 2
kube_customresource_ready_count{active="3",customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",name="foo"} 4
```

`name: [　metadata, name　]`は`.`からの位置なので、`metadata.name`が`name="foo"`のラベルが追加されます。

`active: [active]`はpathで指定した`status.sub`からの位置なので、`status.sub.type-a.active=1`等のラベルが追加されます。


##### `labelFromKey`の説明

ラベルの追加する機能です。
path配下のオブジェクトを指定した値をkey名としてラベルに追加します。

以下のようなCRがあるとします。

```yaml
metadata:
  name: foo
status:
  sub:
    type-a: # メトリクスのラベルに追加される箇所
      active: 1
      ready: 2  
    type-b: # メトリクスのラベルに追加される箇所
      active: 3  
      ready: 4  
```


labelFromKeyを以下のように指定します。

```yaml
- name: "ready_count"
  each:
    type: Gauge
    gauge:
      path: [status, sub]
      valueFrom: [ready]　　　
      labelFromKey: type  
```

pathで指定した配下に対して、`labelFromKey`指定した値をkey名としてラベルに追加します。
出力されるメトリクスは以下になります。

```sh
# labelFromKey の設定で追加されているラベルは `type="type-a"`と`type="type-b"`になります
kube_customresource_ready_count{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",type="type-a"} 2
kube_customresource_ready_count{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",type="type-b"} 4
```

`path`で指定した`status.sub`に対して、自分で指定した任意の値をkey名としてラベルに追加することができます。


##### `nilIsZero`の説明

指定した条件に一致する値がnillの場合は、メトリクスが出力されませんが、valueが`0`のメトリクスで出力する機能になります。

指定する場合は以下のようにbooleanで指定します。

```yaml
each:  
type: Gauge 
gauge:
  path: [status, uptime] 
  nilIsZero: true
```


#### `spec.resources.metrics.each.info`

Name | Require | Description
-- | -- | --
path | 任意 | `labelsFromPath` と `labelFromKey` の起点となる path を指定する。未指定時は `.` となる
labelsFromPath | 条件付き必須 | 出力するメトリクスの条件の指定。`gauge` の `labelsFromPath`   と設定方法は同じ。<br>条件に合致するものがある場合は value が `1` のメトリクスが出力される
labelFromKey | 条件付き必須 | 出力するメトリクスの条件の指定。`gauge` の `labelFromKey`   と設定方法は同じ。<br>条件に合致するものがある場合は value が `1` のメトリクスが出力される

ref https://github.com/kubernetes/kube-state-metrics/blob/v2.8.2/pkg/customresourcestate/config_metrics_types.go#L50-L56


- 設定イメージ

```yaml
- name: "info"
  help: "Foo version"
  each:
    type: Info
    info:
      labelsFromPath:
        version: [spec, version]

- name: "type_info"
  help: "Foo type"
  each:
    type: Info
    info:
      path: [status, active]
      labelFromKey: type
```

##### `labelsFromPath`の説明

`gauge`の`labelsFromPath`と設定方法は同じ。
条件に合致するものがある場合は value が`1`のメトリクスが出力されるようになる点が異なります。


以下のようなCRがあるとします。

```yaml
spec:
  version: v1.2.3
```

labelsFromPathを以下のように指定します。

```yaml
- name: "info"
  help: "Foo version"
  each:
    type: Info
    info:
      labelsFromPath:
        version: [spec, version]
```

CRの方に`sepc.version="v1.2.3"`が存在しており、labelsFromPathで指定した条件に合致しているので、以下のメトリクスが出力されます。

```sh
kube_customresource_info2{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",version="v1.2.3"} 1
```

ポイントは以下の２つです
- valueが`1`であること
- labelsFromPathで指定したラベルが追加されていること e.g. `version="v1.2.3"`

これが`Info`の基本的な使い方になります。

##### `path`の説明

`gauge`の`labelsFromPath`と設定方法は同じ。
`gauge`の時は`path`を使ってvalueを指定していましたが、こちらの場合は他の設定の起点の位置を変えるための補助的な役割になります。

以下のようなCRがあるとします。

```yaml
spec:
  version: v1.2.3
```

labelsFromPathを以下のように指定します。

```yaml
- name: "info"
  help: "Foo version"
  each:
    type: Info
    info:
      path: [spec]
      labelsFromPath:
        version: [version]
```

出力されるメトリクスは`labelsFromPath`の例と同じになります。

```sh
kube_customresource_info2{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",version="v1.2.3"} 1
```

##### `labelFromKey`の説明


以下のようなCRがあるとします。

```yaml
status:
  active:
    type-a: 1
    type-b: 3
```

labelFromKeyを以下のように指定します。

```yaml
- name: "type_info"
  help: "Foo type"
  each:
    type: Info
    info:
      path: [status, active]
      labelFromKey: type
```

出力されるメトリクスは以下になります。

```sh
kube_customresource_type_info{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",type="type-a"} 1
kube_customresource_type_info{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",type="type-b"} 1
```

pathで指定した`status.active`の配下をkey名をlabelFromKeyで指定した`type`にしてラベルに追加します。Infoなのでその時のメトリクスのvalueは必ず`1`が入ります。

`labelsFromPath`か`labelFromKey`で指定した条件に一致するものがある場合にvalueが`1`のメトリクスを出力するのが`Info`の挙動になります。


#### `spec.resources.metrics.each.stateSet`

Name | Require | Description
-- | -- | --
path | 任意 | `list` でカウントする対象となる値の path を指定する
labelsFromPath | 任意 | ラベルの追加。`gauge` の `labelsFromPath` と設定方法は同じ
list | 必須 | 出力するメトリクスの条件の指定。集計する対象となる値の一覧を指定する。`path`で指定した場所からの相対パスでの指定になる
labelName | 必須 | ラベルの追加。`list`で指定した値をラベルに追加する際のkey名を指定する
valueFrom | 任意 | 出力するメトリクスの条件の指定。`gauge` の `valueFrom` と設定方法は同じ

ref https://github.com/kubernetes/kube-state-metrics/blob/v2.8.2/pkg/customresourcestate/config_metrics_types.go#L58-L69


- 設定イメージ

```yaml
- name: "phase"
  each:
    type: StateSet
    stateSet:
      path: [status, phase]
      labelName: phase
      list: [Active, Running, Terminating, Pending]
      labelsFromPath:
        name: [name]
```

##### `path`の説明

`gauge`や`info`の`path`と設定方法は同じ。
`info`の時と同じように他の設定の起点の位置を変えるための補助的な役割になります。

##### `list`と`labelName`の説明

listとlabelNameは同時に使用します。

`list`で指定した値をカウントしたものがメトリクスのvalueになります。
出力されるメトリクスには`labelName`で指定した値をkey名に`list`で指定した値をvalueにしたラベルが追加されます。


以下のようなCRがあるとします。

```yaml
status:
  phase: Pending
```

listとlabelNameを以下のように指定します。

```yaml
- name: "phase"
  each:
    type: StateSet
    stateSet:
      path: [status, phase]
      labelName: phase
      list: [Active, Running, Terminating, Pending]
```

以下のメトリクスが追加されます。

```sh
kube_customresource_phase{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",phase="Active"} 0
kube_customresource_phase{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",phase="Pending"} 1
kube_customresource_phase{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",phase="Running"} 0
kube_customresource_phase{customresource_group="myteam.io",customresource_kind="Foo",customresource_version="v1",phase="Terminating"} 0
```

pathで指定した`status.phase`配下でlistで指定した値に一致するものをカウントして

これが`StateSet`の基本的な使い方になります。

##### `labelsFromPath`の説明

`gauge`や`info`の`labelsFromPath`と設定方法は同じ。
ラベルを指定する時に使います。

##### `valueFrom`の説明

`gauge`の`valueFrom`と設定方法は同じ。
出力するメトリクスの条件の指定するために使います。

## 参考

検証用に用意したCRDとCRを記載します。

- CRD

```yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: foos.myteam.io
spec:
  group: myteam.io
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                version:
                  type: string
                order:
                  items:
                    properties:
                      id: 
                        type: integer
                      value: 
                        type: boolean
                    type: object
                  type: array
                replicas:
                  type: integer
            status:
              type: object
              properties:
                phase:
                  type: string
                active:
                  properties:
                    type-a:
                      type: integer
                    type-b:
                      type: integer
                  type: object
                conditions:
                  items:
                    properties:
                      name: 
                        type: string
                      value: 
                        type: integer
                    type: object
                  type: array
                sub:
                  properties:
                    type-a:
                      properties:
                        active:
                          type: integer
                        ready:
                          type: integer
                      type: object
                    type-b:
                      properties:
                        active:
                          type: integer
                        ready:
                          type: integer
                      type: object
                  type: object
                uptime:
                  type: number
  scope: Namespaced
  names:
    plural: foos
    singular: foo
    kind: Foo
    shortNames:
    - fo
```

- CR

```yaml
kind: Foo
apiVersion: myteam.io/v1
metadata:
  annotations:
    qux: quxx
    quxx: quux
  labels:
    foo: bar
    bar: baz
  name: foo
spec:
  version: v1.2.3
  order:
  - id: 1
    value: true
  - id: 3
    value: false
  replicas: 1
status:
  phase: Pending
  active:
    type-a: 1
    type-b: 3
  conditions:
  - name: a
    value: 45
  - name: b
    value: 66
  sub:
    type-a:
      active: 1
      ready: 2
    type-b:
      active: 3
      ready: 4
  uptime: 43.21
```

## 所感

`Custom Resource State metrics`がリリースされた当初は自分でGo言語で記述してbuildしないといけなかったので、これはフォークして開発するのと何が違うんだろうか？みたいな機能でしたが、設定ファイル書くだけで使えるようになったのでだいぶ良くなったのかなと思います。

ただ、公式のドキュメントの方が今はあまりいけていない感じになっています。間違いとドキュメントに記載されていない内容が結構あったので自分で一から検証して今回まとめてみました。

今後もそれなりに変更がある機能なので、Changelog確認しながらkube-state-metricsをアップデートしていく運用ができないと商用利用は少し厳しいと思いますが、`--custom-resource-state-only=true`を有効化してカスタムリソース専用のkube-state-metricsにしておけば影響範囲を抑えられるので、それぐらいなら許容できるケースも結構あるのかなと思います。

自分たちでExporterを開発・運用していくコストと比較して、kube-state-metricsの変更点をキャッチアップしながら`Custom Resource State metrics`を利用するコストは比較できるラインに来たかなと思うで、今後の利用は進むと思います。