kube-state-metrics を用いてラベルとアノテーションをメトリクスに出力する方法

## はじめに
kubernetes の監視をしていると、特定のラベル名をキーに複数のメトリクスを繋げてダッシュボードを作成したり、特定のアノテーションの設定状況を確認することで、該当する機能がクラスタ内で使用されてるかを確認したいなどの場面に遭遇することがあります。

kube-state-metrics はそれらのニーズに対応する機能が実装されているので、該当機能を用いて Kubernetes 上のオブジェクトに付与されている任意のラベル名とアノテーション名をメトリクスとして取得する方法を説明します。
(2022/6/28 時点で最新であった v2.5.0 の内容を元に執筆しています。)

- https://github.com/kubernetes/kube-state-metrics/releases/tag/v2.5.0

## 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 のオブジェクトの状態をメトリクスとして提供してくれます。

## 任意のラベル名を取得する方法について

### 背景
kube-state-metrics v1.9.7　以前では　`kube_＜オブジェクト名＞_labels` メトリクス(e.g. `kube_pod_labels`, `kube_node_labels` etc) においてオブジェクトに付与される全てのラベル名がメトリクスで出力されていました。

v2.0.0 系以降から `kube_＜オブジェクト名＞_labels` においてデフォルトでラベル名の出力がされないようになりました。

v1.9.7 と v2.5.0 での `kube_＜オブジェクト名＞_labels` メトリクスの出力差異の例を下記に記載します。

```diff
# v1.9.7 
- kube_node_labels{node="kind-control-plane",label_beta_kubernetes_io_arch="amd64",label_beta_kubernetes_io_os="linux",label_kubernetes_io_arch="amd64",label_kubernetes_io_hostname="kind-control-plane",label_kubernetes_io_os="linux",label_node_role_kubernetes_io_control_plane="",label_node_kubernetes_io_exclude_from_external_load_balancers=""} 1
- kube_node_labels{node="kind-worker2",label_beta_kubernetes_io_arch="amd64",label_beta_kubernetes_io_os="linux",label_kubernetes_io_arch="amd64",label_kubernetes_io_hostname="kind-worker2",label_kubernetes_io_os="linux"} 1
- kube_node_labels{node="kind-worker",label_beta_kubernetes_io_arch="amd64",label_beta_kubernetes_io_os="linux",label_kubernetes_io_arch="amd64",label_kubernetes_io_hostname="kind-worker",label_kubernetes_io_os="linux"} 1

# v2.5.0 
+ kube_node_labels{node="kind-control-plane"} 1
+ kube_node_labels{node="kind-worker2"} 1
+ kube_node_labels{node="kind-worker"} 1
```

オブジェクトが持つ全てのラベル名が含まれるメトリクスはカーディナリティが高くなりやすく、Prometheus の性能問題を引き起こすことが多いため、デフォルトでの出力が制限されました。メトリクスのカーディナリティと Prometheus のパフォーマンスの関係ついては[こちら](https://www.robustperception.io/cardinality-is-key)をご参照ください。

そのため kube-state-metrics の v2.0.0 以降を利用する際に Kubernetes のオブジェクトに付与されているラベルをメトリクスとして使用したい場合は、利用者が設定する必要があります。


### 設定方法

起動時の引数に `--metric-labels-allowlist` を指定することで任意のラベル名をメトリクスに出力できるようになります。

公式ドキュメント上の説明は以下になります。

> --metric-labels-allowlist string             Comma-separated list of additional Kubernetes label keys that will be used in the resource' labels metric. By default the metric contains only name and namespace labels. To include additional labels provide a list of resource names in their plural form and Kubernetes label keys you would like to allow for them (Example: '=namespaces=[k8s-label-1,k8s-label-n,...],pods=[app],...)'. A single '*' can be provided per resource instead to allow any labels, but that has severe performance implications (Example: '=pods=[*]').

- https://github.com/kubernetes/kube-state-metrics/blob/master/docs/cli-arguments.md


公式ドキュメントの説明だと少し分かりにくいのですが、`--metric-labels-allowlist` には、オブジェクト名(複数形)とラベル名を組み合わせを以下のように設定します。

```
<オブジェクト名1(複数形)>=[<ラベル名1>,<ラベル名2>,...],<オブジェクト名2(複数形)>=[<ラベル名1>,<ラベル名2>],...
```

設定例は以下になります。

```yaml
containers:
- image: registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.5.0
  args:
  - --metric-labels-allowlist=pods=[app,k8s-app],nodes=[*]
```

:::note warn
kube-state-metircs v2.2.0 以降からはワイルドカード(`*`)を用いた指定もできます。
`*` を指定した場合には、対象のオブジェクトの全てのラベルがメトリクスに出力されるため、カーディナリティの増加に注意してください。特に pod, configmap, secret に対しては `*` を利用せずに、出力したいラベルを個別に設定することを強くお勧めします。
:::

出力例は以下になります。

```diff
# --metric-labels-allowlist 指定なし
- kube_node_labels{node="kind-control-plane"} 1
- kube_node_labels{node="kind-worker2"} 1
- kube_node_labels{node="kind-worker"} 1

# --metric-labels-allowlist 指定あり 
+ kube_node_labels{node="kind-worker2",label_beta_kubernetes_io_arch="amd64",label_beta_kubernetes_io_os="linux",label_kubernetes_io_arch="amd64",label_kubernetes_io_hostname="kind-worker2",label_kubernetes_io_os="linux"} 1
+ kube_node_labels{node="kind-worker",label_beta_kubernetes_io_arch="amd64",label_beta_kubernetes_io_os="linux",label_kubernetes_io_arch="amd64",label_kubernetes_io_hostname="kind-worker",label_kubernetes_io_os="linux"} 1
+ kube_node_labels{node="kind-control-plane",label_beta_kubernetes_io_arch="amd64",label_beta_kubernetes_io_os="linux",label_kubernetes_io_arch="amd64",label_kubernetes_io_hostname="kind-control-plane",label_kubernetes_io_os="linux",label_node_role_kubernetes_io_control_plane="",label_node_kubernetes_io_exclude_from_external_load_balancers=""} 1
```

## 任意のアノテーション名を取得する方法について

### 背景
ラベルと同様にアノテーションもメトリクスとして取得したいという需要があったので、kube-state-metrics v2.2.0 から追加されました。

### 設定方法

起動時の引数に `--metric-annotations-allowlist` を指定することで任意のラベル名をメトリクスに出力できるようになります。

公式ドキュメント上の説明は以下になります。

> --metric-annotations-allowlist string        Comma-separated list of Kubernetes annotations keys that will be used in the resource' labels metric. By default the metric contains only name and namespace labels. To include additional annotations provide a list of resource names in their plural form and Kubernetes annotation keys you would like to allow for them (Example: '=namespaces=[kubernetes.io/team,...],pods=[kubernetes.io/team],...)'. A single '*' can be provided per resource instead to allow any annotations, but that has severe performance implications (Example: '=pods=[*]').

- https://github.com/kubernetes/kube-state-metrics/blob/master/docs/cli-arguments.md


公式ドキュメントの内容だと少しわかりにくいですが、設定の方法はラベルの時と同じになります。

設定例は以下になります。

```yaml
containers:
- image: registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.5.0
  args:
  - --metric-annotations-allowlist=services=[prometheus.io/scrape,prometheus.io/path,prometheus.io/port],nodes=[*]
```

出力例は以下になります。

```diff
# --metric-annotations-allowlist 指定なし
- kube_service_annotations{namespace="kube-system",service="kube-dns",uid="fae94b07-09d6-4939-8409-1a16d7cda19f"} 1
- kube_service_annotations{namespace="kube-system",service="kube-state-metrics",uid="2b87f007-4953-46e1-ade4-5496a2ca8c20"} 1
- kube_service_annotations{namespace="default",service="kubernetes",uid="69799a55-9095-4ef9-8e59-c99ad01e8bbd"} 1

# --metric-annotations-allowlist 指定あり 
+ kube_service_annotations{namespace="kube-system",service="kube-dns",uid="fae94b07-09d6-4939-8409-1a16d7cda19f",annotation_prometheus_io_port="9153",annotation_prometheus_io_scrape="true"} 1
+ kube_service_annotations{namespace="kube-system",service="kube-state-metrics",uid="2b87f007-4953-46e1-ade4-5496a2ca8c20",annotation_prometheus_io_scrape="true"} 1
+ kube_service_annotations{namespace="default",service="kubernetes",uid="69799a55-9095-4ef9-8e59-c99ad01e8bbd"} 1
```

:::note alert
`metric-annotations-allowlist` に対して `*` を指定できますが、その場合は `kubectl.kubernetes.io/last-applied-configuration` の内容もメトリクスに出力されます。`kubectl.kubernetes.io/last-applied-configuration` は前回適用したオブジェクトの情報を保持しているため、情報量が大きくなりやすいです。大きなメトリクスは Prometheus のメモリ使用量が増え、TSDB のデータサイズを圧迫するため `metric-annotations-allowlist` を利用する際には、 `*` を利用せずに明示的に値を指定することを強くお勧めします。
:::


以下が`--metric-annotations-allowlist=sdeployments=[*]`を指定した際の deploymet の例になります。

```yaml
# HELP kube_deployment_annotations Kubernetes annotations converted to Prometheus labels.
# TYPE kube_deployment_annotations gauge
kube_deployment_annotations{namespace="kube-system",deployment="kube-state-metrics",annotation_deployment_kubernetes_io_revision="12",annotation_kubectl_kubernetes_io_last_applied_configuration="{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"labels\":{\"app.kubernetes.io/component\":\"exporter\",\"app.kubernetes.io/name\":\"kube-state-metrics\",\"app.kubernetes.io/version\":\"2.5.0\"},\"name\":\"kube-state-metrics\",\"namespace\":\"kube-system\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app.kubernetes.io/name\":\"kube-state-metrics\"}},\"template\":{\"metadata\":{\"labels\":{\"app.kubernetes.io/component\":\"exporter\",\"app.kubernetes.io/name\":\"kube-state-metrics\",\"app.kubernetes.io/version\":\"2.5.0\"}},\"spec\":{\"automountServiceAccountToken\":true,\"containers\":[{\"args\":[\"--metric-labels-allowlist=pods=[app,k8s-app],nodes=[*]\",\"--metric-annotations-allowlist=services=[*],nodes=[*],pods=[*],comfigmaps=[*],deployments=[*]\"],\"image\":\"registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.5.0\",\"livenessProbe\":{\"httpGet\":{\"path\":\"/healthzs\",\"port\":8080},\"initialDelaySeconds\":5,\"timeoutSeconds\":5},\"name\":\"kube-state-metrics\",\"ports\":[{\"containerPort\":8080,\"name\":\"http-metrics\"},{\"containerPort\":8081,\"name\":\"telemetry\"}],\"readinessProbe\":{\"httpGet\":{\"path\":\"/\",\"port\":8081},\"initialDelaySeconds\":5,\"timeoutSeconds\":5},\"securityContext\":{\"allowPrivilegeEscalation\":false,\"capabilities\":{\"drop\":[\"ALL\"]},\"readOnlyRootFilesystem\":true,\"runAsUser\":65534}}],\"nodeSelector\":{\"kubernetes.io/os\":\"linux\"},\"serviceAccountName\":\"kube-state-metrics\"}}}}\n"} 1
```

`annotation_kubectl_kubernetes_io_last_applied_configuration` の内容が全てメトリクスに含まれたおり、情報量が多いことが分かります。

## さいごに
普段はゼットラボ株式会社でYahoo! JAPAN向けのKubernetes as a Serviceの開発に従事しており、利用者向けのドキュメントの執筆等の仕事もしています。今回は利用者向けのドキュメントで執筆している内容の一部を外部でも公開できるように書き直してみました。

お役に立てれば幸いです。