7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

kube-apiserverのトレースを取得・可視化してみた

Last updated at Posted at 2025-12-09

はじめに

この記事はNTTテクノクロス Advent Calendar 2025(シリーズ2)の10日目の記事です。

NTTテクノクロスの西園です。
久しぶりのアドベントカレンダー参加となりますが、よろしくお願いします。

みなさん、Kubernetes自体の可観測性(オブザーバビリティ)について関心ありますか?
メトリクスやログの収集ならやってるよという方はいると思いますが、トレース(分散トレーシング)も(!)という方はまだまだ少ないのではないでしょうか。
実は最近Kubernetesシステムコンポーネントのトレース取得がネイティブ対応1したらしく、公式ドキュメント2に取得方法の記載がありました。
とはいえ、実際に取得・可視化しているという記事やサンプルはなかなか見当たりません(2025/12初旬)。
そこで、Kubernetesシステムコンポーネントの1つであるkube-apiserverについて、トレースの取得・可視化に手探りでチャレンジしてみました。

可観測性(オブザーバビリティ)とは?

そもそもという話ではありますが。

  • 可観測性(オブザーバビリティ)って何?
  • 監視(モニタリング)との違いは?
  • トレース(分散トレーシング)って何?

上記のような疑問については以下を参考にしてみてください。

環境構成

今回Kubernetesクラスタは以下の環境構成としています。

  • Kubernetes構築環境: WSL
  • Kubernetes構築ツール: minikube
  • Kubernetesのバージョン: v1.34.0
  • ノード数: Control Plane 1台、Worker Node 2台

また、kube-apiserverのトレースを取得・可視化するために以下のソフトウェアを使用しています。

ソフトウェア バージョン 役割
OpenTelemetery Collector3 0.141.0 kube-apiserverから送信されるトレースを受信し、Grafana Tempoに格納する
Grafana Tempo4 2.9.0 OpenTelemetery Collectorに1度集約されたトレースを再受信し、ストレージに格納する
Prometheus5 v3.8.0 Grafana Tempoに格納されたトレースを統計処理する
Grafana6 12.3.0 Grafana Tempoに格納された、もしくは、Prometheusで集計処理したトレースを可視化する

ソフトウェア構成とトレースの流れ

ソフトウェア構成とトレースの流れは以下となっています。

kube-apiserverのトレースの取得・可視化

以下1~5の流れで環境構築します。
なお、事前にKubernetesクラスタを構築し、monitoring Namespaceを作成しておいてください。
また、今回はHelmを使用しません。
さらに、Grafana Tempo、Prometheus、Grafanaは、HostPathを使用してデータを永続化しています。
本番環境では推奨されないため、本番環境ではS3互換ストレージやPVCを使用してください。

  1. Grafana Tempoの構築
  2. OpenTelemetery Collectorの構築
  3. Prometheusの構築
  4. kube-apiserverについてトレースの送信設定追加
  5. Grafanaの構築
$ kubectl get node
NAME           STATUS   ROLES           AGE   VERSION
minikube       Ready    control-plane   41d   v1.34.0
minikube-m02   Ready    <none>          41d   v1.34.0
minikube-m03   Ready    <none>          41d   v1.34.0

$ kubectl create namespace monitoring

$ kubectl get namespaces monitoring 
NAME         STATUS   AGE
monitoring   Active   4d22h

1. Grafana Tempoの構築

以下を実現できるようにGrafana Tempoを構築します。

  1. Receiver: OTel Collectorからトレースを受信する
  2. Ingester/Storage: 受信した生トレースをHostPathをストレージとして格納する
  3. Metrics Generator: 格納されたトレース(スパン)をリアルタイムで処理し、REDメトリクス(リクエストレート、期間など)を生成する
  4. Exporter: 生成したメトリクスを、自身の /metrics エンドポイント(ポート3200)で公開する

適用したマニフェストは以下となります。

tempo.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: tempo-config
  namespace: monitoring
data:
  tempo.yaml: |
    server:
      http_listen_port: 3200
      grpc_listen_port: 9095
    distributor:
      receivers:
        otlp:
          protocols:
            grpc:
              endpoint: 0.0.0.0:4317
            http:
              endpoint: 0.0.0.0:4318
    ingester:
      lifecycler:
        ring:
          kvstore:
            store: inmemory
    # トレースの統計情報を作成し、Prometheusに送信する
    metrics_generator:
      registry:
        external_labels:
          source: tempo
        collection_interval: 15s
      processor:
        service_graphs:
          histogram_buckets: [0.001, 0.005, 0.05, 0.5, 1.0, 3.0, 5.0, 10.0]
        span_metrics:
          histogram_buckets: [0.001, 0.005, 0.05, 0.5, 1.0, 3.0, 5.0, 10.0]
      storage:
        path: /tmp/tempo/generator/wal
        remote_write:
          - url: http://prometheus.monitoring.svc:9090/api/v1/write
            send_exemplars: true
    compactor:
      compaction:
        compaction_window: 1h
        max_block_bytes: 100_000_000
        block_retention: 1h
        compacted_block_retention: 10m
    storage:
      trace:
        backend: local
        local:
          path: /var/tempo/traces
        wal:
          path: /var/tempo/wal
    auth_enabled: false
    overrides:
      metrics_generator_processors: [ "service-graphs", "span-metrics" ]
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tempo
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app: tempo
  template:
    metadata:
      labels:
        app: tempo
    spec:
      nodeSelector:
        kubernetes.io/hostname: minikube-m02
      initContainers:
      - name: fix-permissions
        image: busybox:latest
        # Tempoのコンテナ実行ユーザー(UID 10001)に所有権を変更
        command: ["sh", "-c", "chown -R 10001:10001 /var/tempo/traces"]
        volumeMounts:
        - mountPath: /var/tempo/traces
          name: tempo-storage
      containers:
      - name: tempo
        image: grafana/tempo:2.9.0
        args:
          - "-config.file=/etc/tempo.yaml"
          - "-target=all"
          - "-storage.trace.backend=local"
          - "-storage.trace.local.path=/var/tempo/traces"
          - "-auth.enabled=false"
        ports:
          - containerPort: 4317 # OTLP gRPC receiver
        volumeMounts:
        - mountPath: /var/tempo/traces
          name: tempo-storage
        - mountPath: /etc/tempo.yaml
          subPath: tempo.yaml
          name: tempo-config-volume
      volumes:
      - name: tempo-storage
        hostPath:
          path: /var/lib/tempo-data
          type: DirectoryOrCreate
      - name: tempo-config-volume
        configMap:
          name: tempo-config

適用後、PodとServiceが正常に作成され、PodのIPアドレスとServiceが紐づけられている(=ENDPOINTSのIPアドレスとして登録されている)ことを確認します。

$ kubectl get -n monitoring pod tempo-576d5c6c-f7qzl -o wide
NAME                   READY   STATUS    RESTARTS   AGE     IP               NODE           NOMINATED NODE   READINESS GATES
tempo-576d5c6c-f7qzl   1/1     Running   0          5h42m   10.244.205.230   minikube-m02   <none>           <none>

$ kubectl get -n monitoring svc tempo 
NAME    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE
tempo   ClusterIP   10.105.187.246   <none>        4317/TCP,3200/TCP   2d23h

$ kubectl get -n monitoring endpointslices.discovery.k8s.io tempo-brlhb 
NAME          ADDRESSTYPE   PORTS       ENDPOINTS        AGE
tempo-brlhb   IPv4          4317,3200   10.244.205.230   3d7h

2. OpenTelemetery Collectorの構築

以下を実現できるようにOpenTelemetery Collectorを構築します。

  1. Receiver: Kube-apiserverからOTLP (gRPC) でトレースを受信する
  2. Processor: 受信したトレースをバッチ処理(一時的に集約)する
  3. Exporter: 処理したトレースを、Tempo ServiceのDNS名(またはClusterIP)のポート4317へOTLP (gRPC)で転送する

適用したマニフェストは以下となります。

otel-collector.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: otel-collector-conf
  namespace: monitoring
data:
  otel-collector-config.yaml: |
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
    processors:
      batch:
    exporters:
      otlp:
        endpoint: tempo:4317
        tls:
          insecure: true
    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [batch]
          exporters: [otlp]
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: otel-collector
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app: otel-collector
  template:
    metadata:
      labels:
        app: otel-collector
    spec:
      containers:
      - name: otel-collector
        image: otel/opentelemetry-collector:0.141.0
        args: ["--config=/conf/otel-collector-config.yaml"]
        volumeMounts:
        - mountPath: /conf
          name: otel-collector-conf-vol
      volumes:
      - name: otel-collector-conf-vol
        configMap:
          name: otel-collector-conf
          items:
            - key: otel-collector-config.yaml
              path: otel-collector-config.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: otel-collector
  namespace: monitoring
spec:
  ports:
  - name: otlp-grpc
    port: 4317
    protocol: TCP
    targetPort: 4317
  selector:
    app: otel-collector

適用後、PodとServiceが正常に作成され、PodのIPアドレスとServiceが紐づけられている(=ENDPOINTSのIPアドレスとして登録されている)ことを確認します。

$ kubectl get -n monitoring pod otel-collector-7944475d96-mbbpt -o wide
NAME                              READY   STATUS    RESTARTS   AGE    IP               NODE           NOMINATED NODE   READINESS GATES
otel-collector-7944475d96-mbbpt   1/1     Running   0          3d6h   10.244.205.212   minikube-m02   <none>           <none>

$ kubectl get -n monitoring svc otel-collector 
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
otel-collector   ClusterIP   10.111.150.39   <none>        4317/TCP   2d23h

$ kubectl get -n monitoring endpointslices.discovery.k8s.io otel-collector-rfkhb 
NAME                   ADDRESSTYPE   PORTS   ENDPOINTS        AGE
otel-collector-rfkhb   IPv4          4317    10.244.205.212   2d23h

3. Prometheusの構築

以下を実現できるようにPrometheusを構築します。

  1. Scraper: Service Discovery機能と設定(prometheus.yml)に基づき、Grafana Tempo の /metrics エンドポイント(ポート3200)に定期的にアクセスし、メトリクスを収集・時系列データベースに格納する

適用したマニフェストは以下となります。

prometheus.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: prometheus
  namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: prometheus-k8s-discovery
  namespace: monitoring
rules:
  - apiGroups: [""]
    resources: ["endpoints", "pods", "services"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["discovery.k8s.io"]
    resources: ["endpointslices"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: prometheus-k8s-discovery
  namespace: monitoring
subjects:
  - kind: ServiceAccount
    name: prometheus
    namespace: monitoring
roleRef:
   kind: Role
   name: prometheus-k8s-discovery
   apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: monitoring
data:
  prometheus.yml: |
    global:
      scrape_interval: 15s
      evaluation_interval: 15s
    scrape_configs:
      - job_name: 'kubernetes-service-endpoints'
        kubernetes_sd_configs:
          - role: endpoints
            namespaces:
              names: ['monitoring']
        relabel_configs:
          - source_labels: [__meta_kubernetes_service_name]
            regex: tempo
            action: keep
          - source_labels: [__meta_kubernetes_endpoint_port_name]
            regex: http-query
            #action: keep
            action: replace
            target_label: __address__
            replacement: tempo.monitoring.svc.cluster.local:3200
          - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
            regex: "true"
            action: keep
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus
  namespace: monitoring
  labels:
    app: prometheus
spec:
  replicas: 1
  selector:
    matchLabels:
      app: prometheus
  template:
    metadata:
      labels:
        app: prometheus
    spec:
      serviceAccountName: prometheus
      nodeSelector:
        kubernetes.io/hostname: minikube-m03
      initContainers:
      - name: fix-permissions
        image: busybox:latest
        # PrometheusコンテナユーザーのUID 65534に所有権を変更
        # HostPathのマウント先が /prometheus です。
        command: ["sh", "-c", "chown -R 65534:65534 /prometheus && chmod 775 /prometheus"]
        securityContext:
          runAsUser: 0 # busyboxをrootで実行し、所有権変更を許可
        volumeMounts:
        # Prometheusのデータボリュームをマウント
        - mountPath: /prometheus
          name: prometheus-storage
      containers:
      - name: prometheus
        image: prom/prometheus:v3.8.0
        args:
          - "--config.file=/etc/prometheus/prometheus.yml"
          - "--storage.tsdb.path=/prometheus"
          # Grafana Tempoからトレースの統計情報を受信する
          - "--web.enable-remote-write-receiver"
          - "--enable-feature=exemplar-storage"
        ports:
        - containerPort: 9090
        volumeMounts:
        - name: config-volume
          mountPath: /etc/prometheus
        - name: prometheus-storage
          mountPath: /prometheus
      volumes:
      - name: config-volume
        configMap:
          name: prometheus-config
      - name: prometheus-storage
        hostPath:
          path: /var/lib/prometheus-data
          type: DirectoryOrCreate

適用後、PodとServiceが正常に作成され、PodのIPアドレスとServiceが紐づけられている(=ENDPOINTSのIPアドレスとして登録されている)ことを確認します。

$ kubectl get -n monitoring pod prometheus-66846c6c48-gzg46 -o wide
NAME                          READY   STATUS    RESTARTS   AGE     IP              NODE           NOMINATED NODE   READINESS GATES
prometheus-66846c6c48-gzg46   1/1     Running   0          5h26m   10.244.151.44   minikube-m03   <none>           <none>

$ kubectl get -n monitoring svc prometheus 
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
prometheus   LoadBalancer   10.100.159.34   127.0.0.1     9090:30580/TCP   79m

$ kubectl get -n monitoring endpointslices.discovery.k8s.io prometheus-gkr8d 
NAME               ADDRESSTYPE   PORTS   ENDPOINTS       AGE
prometheus-gkr8d   IPv4          9090    10.244.151.44   9h

minikubeの機能を使用して、WSLのWindowsホストからLoadBalancer経由でPrometheusにアクセスし、PrometheusがGrafana Tempoのメトリクスをスクレイピングしていることを確認します。

# トンネル作成、以降当該プロセスは起動したままとする
$ minikube tunnel
✅  Tunnel successfully started

📌  NOTE: Please do not close this terminal as this process must stay alive for the tunnel to be accessible ...

🏃  Starting tunnel for service grafana.
🏃  Starting tunnel for service prometheus.-

promehteus.jpg

4. kube-apiserverについてトレースの送信設定追加

kube-apiserverで以下を実現できるように設定変更します。

  1. APIリクエストを処理する際に、内部処理の開始・終了(スパン)を記録し、トレースを生成する
  2. TracingConfiguration で設定された OTLP(OpenTelemetry Protocol)を使用し、Collector用ServiceのClusterIP7:4317ポートへgRPCでトレースを送信する

Control Planeノードにssh接続し、以下を実施します。

$ minikube ssh --node minikube
docker@minikube:~$
  • kube-apiserver-tracing.yamlの作成と配置

    /etc/kubernetes/kube-apiserver-tracing.yaml
    apiVersion: apiserver.config.k8s.io/v1
    kind: TracingConfiguration
    samplingRatePerMillion: 10000
    # endpoint: "otel-collector.monitoring.svc.cluster.local:4317"
    # otel-collector ServiceのClusterIPを指定、環境によってClusterIPは変わるため、自身の環境に合わせること
    endpoint: "10.111.150.39:4317"
    
  • kube-apiserverのマニフェストの変更

    /etc/kubernetes/manifests/kube-apiserver.yaml
    <前略>
    spec:
      containers:
      - command:
        - kube-apiserver
        - --advertise-address=192.168.49.2
    <中略>
        # 1行追加
        - --tracing-config-file=/etc/kubernetes/kube-apiserver-tracing.yaml
    <中略>
        volumeMounts:
        - mountPath: /etc/ssl/certs
          name: ca-certs
          readOnly: true
    <中略>
        # 3行追加
        - mountPath: /etc/kubernetes/kube-apiserver-tracing.yaml
          name: tracing-config
          readOnly: true
    <中略>
      volumes:
      - hostPath:
          path: /etc/ssl/certs
          type: DirectoryOrCreate
        name: ca-certs
    <中略>
      # 3行追加
      - hostPath:
          path: /etc/kubernetes/kube-apiserver-tracing.yaml
          type: File
        name: tracing-config
    

変更後、kube-apiserver Podが再起動され、アクセス可能であることを確認します。

docker@minikube:~$ exit
logout

$ kubectl get node
NAME           STATUS   ROLES           AGE   VERSION
minikube       Ready    control-plane   41d   v1.34.0
minikube-m02   Ready    <none>          41d   v1.34.0
minikube-m03   Ready    <none>          41d   v1.34.0

5. Grafanaの構築

以下を実現できるようにGrafanaを構築します。

  1. Tempoデータソース: TraceQLを使用してTempoから生トレースリストを直接取得し、詳細なトレース分析(Trace List、Trace View)を可能にする
  2. Prometheusデータソース: Prometheusから時系列メトリクスを取得し、グラフ化する

適用したマニフェストは以下となります。

grafana.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: grafana-datasources-config
  namespace: monitoring
data:
  tempo-datasource.yaml: |
    apiVersion: 1
    datasources:
    - name: Tempo
      type: tempo
      access: proxy
      # Tempo Serviceを指定
      url: http://tempo.monitoring.svc.cluster.local:3200
      isDefault: false
  prometheus-datasource.yaml: |
    apiVersion: 1
    datasources:
    - name: Prometheus
      type: prometheus
      uid: prometheus-internal-metrics
      # Prometheus Serviceを指定
      url: http://prometheus.monitoring.svc.cluster.local:9090 
      access: proxy
      isDefault: true
      editable: true
      jsonData:
        # Tempoとの連携設定
        spanMetrics:
          datasourceUid: tempo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grafana
  namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: grafana
  template:
    metadata:
      labels:
        app: grafana
    spec:
      nodeSelector:
        kubernetes.io/hostname: minikube-m03
      initContainers:
      - name: fix-permissions
        image: busybox:latest
        # GrafanaコンテナユーザーのUID 472 に所有権を変更
        command: ["sh", "-c", "chown -R 472:472 /var/lib/grafana && chmod 770 /var/lib/grafana"]
        securityContext:
          runAsUser: 0 # busyboxをrootで実行し、所有権変更を許可
        volumeMounts:
        - mountPath: /var/lib/grafana
          name: grafana-storage
      containers:
      - name: grafana
        image: grafana/grafana:12.3.0
        env:
          - name: GF_SECURITY_ADMIN_USER
            value: admin
          - name: GF_SECURITY_ADMIN_PASSWORD
            value: admin
          - name: GF_PATHS_DATA
            value: /var/lib/grafana
        ports:
        - containerPort: 3000
          name: http
        volumeMounts:
        - name: grafana-data-source
          mountPath: /etc/grafana/provisioning/datasources/
          readOnly: true
        - name: grafana-storage
          mountPath: /var/lib/grafana
      volumes:
      - name: grafana-data-source
        configMap:
          name: grafana-datasources-config
          items:
            - key: tempo-datasource.yaml
              path: tempo-datasource.yaml
            - key: prometheus-datasource.yaml
              path: prometheus-datasource.yaml
      - name: grafana-storage
        hostPath:
          path: /var/lib/grafana-data
          type: DirectoryOrCreate
---
apiVersion: v1
kind: Service
metadata:
  name: grafana
  namespace: monitoring
spec:
  # minikubeの機能を使用してWSLのWindowsホストからLoadBalancerでアクセス可能とする
  type: LoadBalancer
  ports:
  - port: 8080
    targetPort: 3000
    protocol: TCP
    name: http
  selector:
    app: grafana

適用後、PodとServiceが正常に作成され、PodのIPアドレスとServiceが紐づけられている(=ENDPOINTSのIPアドレスとして登録されている)ことを確認します。

$ kubectl get -n monitoring pod grafana-78fcfbcccc-t9j6h -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP              NODE           NOMINATED NODE   READINESS GATES
grafana-78fcfbcccc-t9j6h   1/1     Running   0          8h    10.244.151.27   minikube-m03   <none>           <none>

$ kubectl get -n monitoring svc grafana 
NAME      TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
grafana   LoadBalancer   10.110.39.106   127.0.0.1     8080:30080/TCP   2d23h

$ kubectl get -n monitoring endpointslices.discovery.k8s.io grafana-xsm9c 
NAME            ADDRESSTYPE   PORTS   ENDPOINTS       AGE
grafana-xsm9c   IPv4          3000    10.244.151.27   2d23h

minikubeの機能を使用して、WSLのWindowsホストからLoadBalancer経由でGrafanaにアクセスし、以下を確認します。

  • Tempoデータソースからkube-apiserverの生トレースリストを直接取得し、詳細なトレース分析が可能であること

grafana-tempo.jpg

  • Prometheusデータソースからkube-apiserverのトレースの時系列メトリクスを取得し、グラフ化可能であること

grafana-prometheus.jpg

おわりに

規模の大きなKubernetesクラスタを構築しがっつり使用するまでは、Kubernetes自体のトレースの必要性はあまり感じないかもしれません。
しかし、Kubernetes運用者にとって可観測性に関する技術はぜひとも押さえておくべき重要な技術と考えています。
本記事がKubernetes運用者の方々の一助となれば幸いです。

引き続き、NTTテクノクロス Advent Calendar 2025 をお楽しみください!

  1. kube-apiserverはv1.34時点でベータ、kubeletはGAとなっています

  2. Traces For Kubernetes System Components

  3. What is OpenTelemetry?OpenTelemetry Collector

  4. Grafana Tempo

  5. Prometheus

  6. Grafana

  7. kube-apiserverはStatic Podかつ、hostNetworkで構築しているため、ServiceのFQDNで名前解決ができません。そのため、CluserIPのIPアドレスを直接指定しています

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?