⚓️

OpenTelemetry の k8sattributesprocessor を検証する

2024/07/02に公開

はじめに

OpenTelemetry を使ってテレメトリデータを取得する際、kubernetes オブジェクトの情報をテレメトリデータに付与したい場合があると思います。
例えば、Pod名,Namedpace,Deployment名 などなど。
これらのメタデータをテレメトリデータに付与できれば、例えば「Deployment ごとにメトリクスを集計したい」「特定のコンテナごとに集計したい」といった要望も満たせると思います。

そんな要望を満たしてくれる、Kubernetes Attributes Processor を使用して、どのような動きになるのかを検証してみます。

k8sattributesprocessorについて

github リポジトリはこちらです。

https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/k8sattributesprocessor

README.md より

Kubernetes attributes processor allow automatic setting of spans, metrics and logs resource attributes with k8s metadata.

The processor automatically discovers k8s resources (pods), extracts metadata from them and adds the extracted metadata to the relevant spans, metrics and logs as resource attributes. The processor uses the kubernetes API to discover all pods running in a cluster, keeps a record of their IP addresses, pod UIDs and interesting metadata. The rules for associating the data passing through the processor (spans, metrics and logs) with specific Pod Metadata are configured via "pod_association" key. It represents a list of associations that are executed in the specified order until the first one is able to do the match.

k8sattributesprocessor は、Pod を自動的に検出し、そこからメタデータを抽出します。
抽出したメタデータをテレメトリデータ(Span,Metrics,Logs)を Resource Attributes として付与する役割を担います。
付与するルールは、pod_association キーで構成します。

Which metadata to collect is determined by metadata configuration that defines list of resource attributes to be added. Items in the list called exactly the same as the resource attributes that will be added. The following attributes are added by default:
k8s.namespace.name
k8s.pod.name
k8s.pod.uid
k8s.pod.start_time
k8s.deployment.name
k8s.node.name

You can change this list with metadata configuration.

デフォルトでは上記の属性が追加されるようです。
追加するメタデータは metadata で設定できるようです。
当然ながら、Pod のメタデータに上記がなければテレメトリデータにも追加はされないでしょう。

事前準備

kubernetes 環境と OpenTelemetry の環境を準備します。
kubernetes オブジェクトのメトリクス情報は prometheus メトリクスとして公開されているため、Prometheus メトリクスを確認できる環境を用意します。

https://kubernetes.io/docs/concepts/cluster-administration/system-metrics/

EKS でクラスターを作成、ADOT を利用して Amazon Managed Service for Prometheus(AMP) へメトリクスを送信 → Amazon Managed Grafana (AMG)で可視化するデモは別記事で紹介しております。
こちらの環境ができている前提で進めていきます。
https://zenn.dev/k_yoshi/articles/2cae7319b5b7cb

OpenTelemetry の設定ファイル(初期状態)

さて、まずは processor を使用せず、できる限り小さい構成でメトリクスを取得してみます。
以下が OpenTelemetryCollector の manifest ファイルです。

apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
  name: custom-collector
spec:
  mode: deployment
  serviceAccount: adot-collector
  podAnnotations:
    prometheus.io/scrape: 'true'
    prometheus.io/port: '8888'
  resources:
    requests:
      cpu: "1"
    limits:
      cpu: "1"
  env:
    - name: CLUSTER_NAME
      value: observability-cluster
  config: |
    extensions:
      sigv4auth:
        region: ap-northeast-1
        service: "aps"
    receivers:
      prometheus:
        config:
          global:
            scrape_interval: 15s
            scrape_timeout: 10s

          scrape_configs:
          - job_name: kubernetes-pods-compared
            kubernetes_sd_configs:
            - role: pod
            relabel_configs:
            - action: keep
              regex: true
              source_labels:
              - __meta_kubernetes_pod_annotation_prometheus_io_scrape
            - action: drop
              regex: Pending|Succeeded|Failed|Completed
              source_labels:
              - __meta_kubernetes_pod_phase      
                                                            
    processors:
      batch/metrics:
        timeout: 60s

    exporters:
      prometheusremotewrite:
        endpoint: https://<AMGのURL>/api/v1/remote_write
        auth:
          authenticator: sigv4auth
        resource_to_telemetry_conversion:
          enabled: true  // テレメトリデータのResourceAttributeをPrometheusラベルに変換する
   
    service:
      extensions: [sigv4auth]
      pipelines:
        metrics:
          receivers: [prometheus]
          processors: [batch/metrics]
          exporters: [prometheusremotewrite]

Pod に prometheus.io/scrape=true のアノテーションが付与されている Running 状態のPod のみをスクレイプします。
今回色々と比較してみたいメトリクスですが、OpenTelemetry Collector Pod 自身のメトリクスから、otelcol_receiver_accepted_metric_points について確認していきます。

https://opentelemetry.io/docs/collector/internal-telemetry/#lists-of-internal-metrics

取得できたメトリクス(初期状態)

上記の設定ファイルで取得できた Prometheus メトリクスとラベルは以下でした。

otelcol_receiver_accepted_metric_points_total
{
    http_scheme="http",
    instance="172.31.135.161:8888",
    job="kubernetes-pods-compared",
    k8s_container_name="otc-container",
    k8s_namespace_name="aws-otel-eks",
    k8s_node_name="ip-172-31-137-76.ap-northeast-1.compute.internal",
    k8s_pod_name="custom-collector-collector-6fdd47449c-vrksx",
    k8s_pod_uid="ad1a7326-0952-4f9b-acce-9407f11301bb",
    k8s_replicaset_name="custom-collector-collector-6fdd47449c",
    net_host_name="172.31.135.161",
    net_host_port="8888",
    receiver="prometheus",
    service_instance_id="172.31.135.161:8888;25fc6aa3-2d8f-4b91-b747-16fba4a01875",
    service_name="kubernetes-pods-compared;aws-otel-collector",
    service_version="v0.38.1",
    transport="http"
}

それではここから k8sattributesprocessor を使って取得できる Prometheus メトリクスのラベルがどう変化するかをみていきます。

検証

1. pod_association - sources の追加 : k8s.namespace.name

まずは k8s.namespace.name のみを sources として追加します。

pod_association:
- sources:
    - from: resource_attribute
      name: k8s.namespace.name

これだと次のラベルが取得できました。
container_image_name,container_image_tag,k8s_deployment_name,k8s_pod_start_time が取得できていることがわかります。

otelcol_receiver_accepted_metric_points_total{
+    container_image_name="public.ecr.aws/aws-observability/aws-otel-collector",
+    container_image_tag="v0.38.1",
    http_scheme="http",
    instance="172.31.158.71:8888",
    job="kubernetes-pods-compared",
    k8s_container_name="otc-container",
+    k8s_deployment_name="custom-collector-collector",
    k8s_namespace_name="aws-otel-eks",
    k8s_node_name="ip-172-31-145-116.ap-northeast-1.compute.internal",
    k8s_pod_name="custom-collector-collector-547778589d-pj6w8",
+    k8s_pod_start_time="2024-07-01T12:41:26Z",
    k8s_pod_uid="31cc3bda-0fda-4090-b409-2bc6db57ee49",
    k8s_replicaset_name="custom-collector-collector-547778589d",
    net_host_name="172.31.158.71",
    net_host_port="8888",
    receiver="prometheus",
    service_instance_id="172.31.158.71:8888;21f18314-0fdc-4ca5-a250-b78603c83cea",
    service_name="kubernetes-pods-compared;aws-otel-collector",
    service_version="v0.38.1",
    transport="http"
}

2. pod_association - sources の追加 : k8s.pod.name, k8s.namespace.name

続いて. k8sattributesprocessor の github に例が掲載されています。こちらを試します。

  # below association matches for pair `k8s.pod.name` and `k8s.namespace.name`
  - sources:
      - from: resource_attribute
        name: k8s.pod.name
      - from: resource_attribute
        name: k8s.namespace.name

1 の結果と変わりませんね。
変わらなかった理由について、「考察」で記載しました。(先にお伝えしておきますが、明確な理由を特定できませんでした...)

otelcol_receiver_accepted_metric_points_total{
+    container_image_name="public.ecr.aws/aws-observability/aws-otel-collector",
+    container_image_tag="v0.38.1",
    http_scheme="http",
    instance="172.31.137.167:8888",
    job="kubernetes-pods-compared",
    k8s_container_name="otc-container",
+    k8s_deployment_name="custom-collector-collector",
    k8s_namespace_name="aws-otel-eks",
    k8s_node_name="ip-172-31-137-76.ap-northeast-1.compute.internal",
    k8s_pod_name="custom-collector-collector-789965869-kcgrx",
+    k8s_pod_start_time="2024-07-01T04:44:30Z",
    k8s_pod_uid="71e611bc-51d5-4b21-9142-decf76c3c84c",
    k8s_replicaset_name="custom-collector-collector-789965869",
    net_host_name="172.31.137.167",
    net_host_port="8888",
    receiver="prometheus",
    service_instance_id="172.31.137.167:8888;3afdeb73-c533-40fd-b718-6df0824e10f4",
    service_name="kubernetes-pods-compared;aws-otel-collector",
    service_version="v0.38.1",
    transport="http"
}

3. extract の追加

続いて extract を追加して、k8s.pod.name,k8s.pod.uid,k8s.deployment.name のみを指定してみます。
これにより、取得するメタデータを絞ることが可能になると思われます。
pod_association は 1 と同じです。

      k8sattributes:
        extract:
          metadata:
            - k8s.pod.name
            - k8s.pod.uid
            - k8s.deployment.name
        pod_association:
        - sources:
          - from: resource_attribute
            name: k8s.namespace.name

1, 2 の結果と比較し、container_image_name,container_image_tag,k8s_pod_start_time が消えていることがわかりました。

otelcol_receiver_accepted_metric_points_total{
    http_scheme="http",
    instance="172.31.152.36:8888",
    job="kubernetes-pods-compared",
    k8s_container_name="otc-container",
    k8s_namespace_name="aws-otel-eks",
    k8s_node_name="ip-172-31-145-116.ap-northeast-1.compute.internal",
    k8s_pod_name="custom-collector-collector-755bc6c94f-5tspz",
    k8s_pod_uid="d51dae90-7b16-4723-a008-391934f37bf3",
    k8s_replicaset_name="custom-collector-collector-755bc6c94f",
    net_host_name="172.31.152.36",
    net_host_port="8888",
    receiver="prometheus",
    service_instance_id="172.31.152.36:8888;5d505adc-7587-4eb2-8e58-737b1bc870cd",
    service_name="kubernetes-pods-compared;aws-otel-collector",
    service_version="v0.38.1",
    transport="http",
+    k8s_deployment_name="custom-collector-collector"
}

4. pod_association - sources と extract の指定 : k8s_pod_start_time のみ

次に以下のような設定にしてみました。

      k8sattributes:
        extract:
          metadata:
            - k8s.pod.start_time
        pod_association:
        - sources:
          - from: resource_attribute
            name: k8s.pod.start_time

この結果、初期状態と同じラベルであることがわかりました。

otelcol_receiver_accepted_metric_points_total{
    http_scheme="http",
    instance="172.31.158.71:8888",
    job="kubernetes-pods-compared",
    k8s_container_name="otc-container",
    k8s_namespace_name="aws-otel-eks",
    k8s_node_name="ip-172-31-145-116.ap-northeast-1.compute.internal",
    k8s_pod_name="custom-collector-collector-7ffbc748dc-btfjq",
    k8s_pod_uid="d27f920a-36d7-4207-a340-bf7f1ba59c98",
    k8s_replicaset_name="custom-collector-collector-7ffbc748dc",
    net_host_name="172.31.158.71",
    net_host_port="8888",
    receiver="prometheus",
    service_instance_id="172.31.158.71:8888;ba4a24b9-9531-4125-bc87-70b9dd5e9824",
    service_name="kubernetes-pods-compared;aws-otel-collector",
    service_version="v0.38.1",
    transport="http"
}

5. pod_association - sources の追加 : k8s.pod.ip

github の README.md にある例がもう一つあります。こちらをやってみます。

pod_association:
  # below association takes a look at the datapoint's k8s.pod.ip resource attribute and tries to match it with
  # the pod having the same attribute.
  - sources:
      - from: resource_attribute
        name: k8s.pod.ip

この結果、こちらも初期状態と同じラベルであることがわかりました。

otelcol_receiver_accepted_metric_points_total{
    http_scheme="http",
    instance="172.31.54.182:8888",
    job="kubernetes-pods-compared",
    k8s_container_name="otc-container",
    k8s_namespace_name="aws-otel-eks",
    k8s_node_name="ip-172-31-56-255.ap-northeast-1.compute.internal",
    k8s_pod_name="custom-collector-collector-6c6df6896f-2925l",
    k8s_pod_uid="f4f34e2b-0054-492b-8504-b8f5b1596e8b",
    k8s_replicaset_name="custom-collector-collector-6c6df6896f",
    net_host_name="172.31.54.182",
    net_host_port="8888",
    receiver="prometheus",
    service_instance_id="172.31.54.182:8888;ee63eb00-4fb8-4747-a784-608ed91619ab",
    service_name="kubernetes-pods-compared;aws-otel-collector",
    service_version="v0.38.1",
    transport="http"
}

よくよく調べたら、k8s.pod.ip は opentelemetry-collector-contrib:v0.104.0 にてリリースされた新しいメタデータのようです。

https://github.com/open-telemetry/opentelemetry-collector-contrib/releases/tag/v0.104.0

自分の環境の ADOT を見ると、public.ecr.aws/aws-observability/aws-otel-collector:v0.38.1 でありこちらは最新版でもないために k8s.pod.ip は対応していないようです。
アクティブに開発が進んでいるため、バージョンを見ないとハマる可能性があります(なぜ上手くいかないのかわからず、半日費やした)。

% k describe po custom-collector-collector-547778589d-pj6w8
Name:             custom-collector-collector-547778589d-pj6w8
...
Containers:
  otc-container:
    ...
    Image:         public.ecr.aws/aws-observability/aws-otel-collector:v0.38.1
    ...

https://github.com/aws-observability/aws-otel-collector/releases

考察

ここまでやってみて「extract で指定することで、抽出するメタデータを絞ることができる」ことはよくわかりました。

一方、pod_association - sources で指定する値により、取得できたラベルに差異が生じる理由がいまいちわかりませんでした。

再度 github を読んでみます。
https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/k8sattributesprocessor

The processor automatically discovers k8s resources (pods), extracts metadata from them and adds the extracted metadata to the relevant spans, metrics and logs as resource attributes. The processor uses the kubernetes API to discover all pods running in a cluster, keeps a record of their IP addresses, pod UIDs and interesting metadata. The rules for associating the data passing through the processor (spans, metrics and logs) with specific Pod Metadata are configured via "pod_association" key. It represents a list of associations that are executed in the specified order until the first one is able to do the match.

「プロセッサを通過するデータ (スパン、メトリック、ログ) を特定のポッド メタデータに関連付けるルールは、「pod_association」キーによって構成されます。これは、最初のものが一致できるようになるまで、指定された順序で実行される関連付けのリストを表します。」と書かれています。
例えば 2 の設定ファイルで考えてみます。

  - sources:
      - from: resource_attribute
        name: k8s.pod.name
      - from: resource_attribute
        name: k8s.namespace.name
  • まず k8s.pod.name で Pod とテレメトリデータを関連づけようと試みる
  • 一致する Pod が無い場合、k8s.namespace.name を使用して関連づけようとする
  • 一致する Pod がある場合、テレメトリデータに対して、一致した Pod からメタデータを付与する

以下の部分でも捕捉されています。
通常のテレメトリデータ(datapoint)では、そのデータを取得した IP と Pod の IP を関連づけるようです。それをk8s.pod.namek8s.namespace.nameなどで関連づけるようにルールを設定しているものと理解しました。

When it sees a datapoint (log, trace or metric), it will try to associate the datapoint to the pod from where the datapoint originated, so we can add the relevant pod metadata to the datapoint. By default, it associates the incoming connection IP to the Pod IP. But for cases where this approach doesn't work (sending through a proxy, etc.), a custom association rule can be specified.

となると、4 で pod_association のルールで k8s.pod.start_time のみを指定した場合にメトリクスのラベルにメタデータが付与されなかったのはなぜなのでしょうか?

単純に考えると、「メトリクスデータと Pod が関連づけられなかった」ということになるかと思います。
この「関連づけ」のロジックについては実装をみてみないとわからなそうだったため、次回の宿題にしたいと思います。

その他

今回はデフォルトに付与される Resource Attributes のみを使用しましたが、他にもあるようです。
こちらにリストがありました。
https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/processor/k8sattributesprocessor/documentation.md

また、Pod に付与されている label, annotation を抽出してテレメトリデータに付与することも可能なようです。

ADOT の公式 docs にも、こちらの processor に関するページがありますので、こちらも読んでみたいと思います。
https://aws-otel.github.io/docs/getting-started/adot-eks-add-on/k8s-attr-processor

最後に

時間をかけて検証した割に、理解しきれない部分が多くて残念です...
しかし、「k8s の Deployment, Namespace ごとに OpenTelemetry でメトリクスを収集したい」という要望はこちらの processor を使用することで満たせそうな雰囲気を感じました。

Discussion