LinkMesh

Search docs, blog and changelog

ENDE

Snippets · Sources

Collect node, pod and container metrics from the kubelet

Each kubelet already knows the resource usage of its node, pods and containers. The kubelet_stats receiver, one per node as a DaemonSet, reads it from the kubelet's stats API on port 10250 with the pod's service account. The only RBAC it needs is get on nodes/stats: in our kind cluster that alone produced 219 metrics per scrape, with no errors.

Signals
Metrics
Category
CollectionKubernetes
Source
Kubernetes kubelet (stats API)
Components
batchmemory_limiterotlphttp
Destination
OTLP/HTTP endpoint

OpenTelemetry Collector

OpenTelemetry Collector YAML
receivers:
  kubelet_stats:
    collection_interval: 30s
    auth_type: serviceAccount
    endpoint: https://${env:K8S_NODE_NAME}:10250
    # Kubelet serving certificates are often self-signed; set this to false
    # where the kubelet has a CA-signed certificate.
    insecure_skip_verify: true
    metric_groups: [node, pod, container]

processors:
  memory_limiter:
    check_interval: 1s
    limit_percentage: 80
    spike_limit_percentage: 25
  batch:
    timeout: 5s

exporters:
  otlphttp:
    endpoint: https://your-backend:4318

service:
  pipelines:
    metrics:
      receivers: [kubelet_stats]
      processors: [memory_limiter, batch]
      exporters: [otlphttp]

The collector needs this to run in a cluster: a ServiceAccount, the RBAC it uses and the workload. Create the config first with kubectl -n observability create configmap kubeletstats --from-file=config.yaml.

Kubernetes manifest
apiVersion: v1
kind: ServiceAccount
metadata:
  name: kubeletstats
  namespace: observability
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: kubeletstats
rules:
  - apiGroups: [""]
    resources: [nodes/stats]
    verbs: [get]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kubeletstats
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: kubeletstats
subjects:
  - kind: ServiceAccount
    name: kubeletstats
    namespace: observability
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kubeletstats
  namespace: observability
spec:
  selector:
    matchLabels:
      app: kubeletstats
  template:
    metadata:
      labels:
        app: kubeletstats
    spec:
      serviceAccountName: kubeletstats
      containers:
        - name: otelcol
          image: otel/opentelemetry-collector-contrib:0.161.0
          args: ["--config=/conf/config.yaml"]
          env:
            - name: K8S_NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
          volumeMounts:
            - name: config
              mountPath: /conf
      volumes:
        - name: config
          configMap:
            name: kubeletstats

LinkMesh

Library › Sources › Kubernetes Node Metrics (kubeletstats)

Collection Interval
20s
Auth Type
serviceAccount
Kubelet Endpoint
${env:K8S_NODE_NAME}:10250
Skip TLS Verify
true
Metric Groups
["node","pod","container"]

What LinkMesh then pushes to the collector, under receivers:

Pushed to the collector
kubeletstats/input_src_00000501:
  auth_type: serviceAccount
  collection_interval: 20s
  endpoint: ${env:K8S_NODE_NAME}:10250
  insecure_skip_verify: true
  metric_groups:
    - node
    - pod
    - container

The same, as an API call (see automation recipes):

API call
curl -X POST "$LINKMESH/sources" \
  -H "Authorization: Bearer $LMSAT" \
  -H "Content-Type: application/json" \
  -d '{
  "defaultConfig": {
    "auth_type": "serviceAccount",
    "collection_interval": "20s",
    "endpoint": "${env:K8S_NODE_NAME}:10250",
    "insecure_skip_verify": true,
    "metric_groups": [
      "node",
      "pod",
      "container"
    ]
  },
  "name": "Kubernetes Node Metrics (kubeletstats)",
  "receiverType": "kubeletstats"
}'

The rest of the collector config above (kubelet_stats, memory_limiter, batch, otlphttp) is not part of this template: in LinkMesh those are separate sources, processors and destinations on the route.

Generated from LinkMesh's template catalogue by linkmesh-server snippet-render 1.464.3, not written by hand.

Download config.yamlValidated with otelcol-contrib v0.161.0 on 2026-10-26

Complete config files, each checked with the collector's own validate command (otelcol-contrib v0.161.0). Replace https://your-backend:4318 with your own OTLP endpoint before you run it.

The gotcha

Kubelet serving certificates are usually self-signed. In our test, without insecure_skip_verify, every scrape failed with "tls: failed to verify certificate: x509: certificate signed by unknown authority" and no metric arrived. Keep insecure_skip_verify: true unless your kubelets have CA-signed serving certificates.

Manage this across a fleet

LinkMesh manages collector configuration across a fleet from one self-hosted control plane, so a change like this is made once rather than host by host. The first 25 Collectors are free after a no-card registration in the OpenSight Customer Portal (5 without one).

Doing this in LinkMesh: Install LinkMesh on Kubernetes