Scraping Container Metrics via Kubelet cAdvisor in Kubernetes

Overview
This guide provides a comprehensive walkthrough for collecting container resource metrics using the cAdvisor endpoint exposed by Kubelet on Kubernetes nodes. These metrics provide deep visibility into container performance, including CPU, memory, disk I/O, and network usage—essential for robust monitoring and troubleshooting.
Accessing cAdvisor Metrics from Kubelet
Kubelet exposes cAdvisor(Container Advisor) metrics on the /metrics/cadvisor endpoint, accessible over port 10250 (TLS) or 10255 (non-TLS).
The endpoint looks like this:
https://<node-ip>:10250/metrics/cadvisor
To verify:
$ kubectl -n kube-system get service kubelet -o wide
# Output
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubelet ClusterIP None <none> 10250/TCP,10255/TCP,4194/TCP 26m <none>
For details on the kubelet service, you can run the following command:
$ kubectl -n kube-system get service kubelet -o yaml
Sample output:
apiVersion: v1
kind: Service
metadata:
creationTimestamp: "2025-06-06T20:32:07Z"
labels:
app.kubernetes.io/managed-by: prometheus-operator
app.kubernetes.io/name: kubelet
k8s-app: kubelet
name: kubelet
namespace: kube-system
resourceVersion: "29241"
uid: 57fe6d64-5a0f-496d-a900-2942ba59d79a
spec:
clusterIP: None
clusterIPs:
- None
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
- IPv6
ipFamilyPolicy: RequireDualStack
ports:
- name: https-metrics
port: 10250
protocol: TCP
targetPort: 10250
- name: http-metrics
port: 10255
protocol: TCP
targetPort: 10255
- name: cadvisor
port: 4194
protocol: TCP
targetPort: 4194
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
The port name https-metrics
is used for secure metrics scraping, while http-metrics
is for insecure metrics. Using 'https-metrics' is recommended for security reasons.
- NOTE
-
Port 10250 requires authentication via bearer token or client certificates.
- TIP
-
If the kubelet service is missing, install Prometheus Operator via Helm. It will provision the required service automatically.
RBAC Configuration for Metrics Access
To scrape Kubelet cAdvisor metrics, you need to ensure that the Prometheus service account has the necessary permissions. You can create a ClusterRole and ClusterRoleBinding to grant access to the Kubelet metrics.
# kubelet-scraper-sa.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: kubelet-scraper
namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: otel-targetallocator-rb-monitoring
subjects:
- kind: ServiceAccount
name: kubelet-scraper
namespace: monitoring
roleRef:
kind: ClusterRole
name: otel-targetallocator-role
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: otel-targetallocator-cr-rb-monitoring
subjects:
- kind: ServiceAccount
name: kubelet-scraper
namespace: monitoring
roleRef:
kind: ClusterRole
name: otel-targetallocator-cr-role
apiGroup: rbac.authorization.k8s.io
The Cluster Roles otel-targetallocator-role
and otel-targetallocator-cr-role
was covered in the previous documentation on Collecting Metrics using OpenTelemetry Collector and Visualizing them using Prometheus and Grafana on Kubernetes
Apply it with:
$ kubectl create namespace monitoring
$ kubectl apply -f kubelet-cadvisor-rbac.yaml
Verifying Access to Kubelet cAdvisor Metrics
To access the Kubelet cAdvisor metrics, you can use the following command to retrieve the metrics directly from the Kubelet API. This requires that you have access to the Kubernetes cluster and the necessary permissions to read the Kubelet metrics.
Get Node IP addresses:
$ kubectl get nodes -o wide
Use the internal IP address of one of the nodes to access the Kubelet metrics. The Kubelet API is typically available at https://<node-ip>:10250/metrics/cadvisor
.
Use busybox pod to access the Kubelet metrics. First, create a pod with the kubelet-scraper
service account in the monitoring
namespace. This pod will have the necessary permissions to access the Kubelet metrics.
$ kubectl run -i -t --rm busybox --image=busybox --restart=Never --overrides='{"spec": { "serviceAccountName": "kubelet-scraper" } }' -n monitoring
Now, you are inside the busybox pod. First, retrieve the bearer token for authentication:
# TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# echo $TOKEN
Now, you can use wget
to access the Kubelet cAdvisor metrics endpoint. Replace <node-ip>
with the actual Internal IP address of your Kubernetes node:
$ wget --no-check-certificate --header="Authorization: Bearer $TOKEN" https://192.168.39.48:10250/metrics/cadvisor -O -
Example Output:
# HELP cadvisor_version_info A metric with a constant '1' value labeled by kernel version, OS version, docker version, cadvisor version & cadvisor revision.
# TYPE cadvisor_version_info gauge
cadvisor_version_info{cadvisorRevision="",cadvisorVersion="",dockerVersion="",kernelVersion="5.10.236-228.935.amzn2.x86_64",osVersion="Amazon Linux 2"} 1
// Kubelet cAdvisor metrics - omitted for brevity
# TYPE machine_swap_bytes gauge
machine_swap_bytes{boot_id="810cc739-14e6-4fd5-855b-545e7a2b7c48",machine_id="ec2333bbe85a7b8da257530bee8bab25",system_uuid="ec2333bb-e85a-7b8d-a257-530bee8bab25"} 0
- 100% |*********************************************************************************************************************************************| 498k 0:00:00 ETA
written to stdout
It returns a large number of metrics related to container resource usage, such as CPU, memory, disk I/O, and network statistics. You can use these metrics to monitor the performance of your containers and troubleshoot issues.
In this example, we will use metrics filter to narrow down the metrics to a specific container, such as otel-spring-example
, react-o11y-app
, or 'cassandra'.
OpenTelemetry Collector Configuration
This OpenTelemetry Collector configuration focuses on scraping Kubelet cAdvisor metrics. It includes the necessary receivers, processors, exporters, and service settings to collect and export Kubelet metrics.
Configure the collector to scrape and filter Kubelet metrics:
apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
name: kubelet-metrics
namespace: monitoring
spec:
image: otel/opentelemetry-collector-contrib:0.127.0 # 0.127.0 or later. latest is not recommended for production use.
mode: statefulset
serviceAccount: kubelet-scraper
replicas: 1
targetAllocator:
enabled: true
serviceAccount: kubelet-scraper
prometheusCR:
enabled: true
serviceMonitorSelector:
matchLabels:
otel-collector: kubelet-metrics
#podMonitorSelector: {}
config:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
prometheus:
config:
#(1)
scrape_configs:
- job_name: 'kubectl-metrics-collector-job'
scrape_interval: 30s
static_configs:
- targets: ['0.0.0.0:8888']
processors:
#(2)
filter/metrics:
metrics:
include:
match_type: regexp
metric_names:
- "^container_cpu.*"
- "^container_memory_.*"
# expressions:
# - 'attributes["container"] == "react-o11y-app"'
# - 'attributes["container"] == "otel-spring-example"'
# - 'resource.container == "react-o11y-app"'
# - 'resource.container == "otel-spring-example"'
#(3)
filter/containers:
error_mode: propagate
metrics:
datapoint:
- |
not ( attributes["container"] == "react-o11y-app"
or attributes["container"] == "otel-spring-example"
or attributes["container"] == "cassandra")
# - 'Not HasAttrOnDatapoint("container", "react-o11y-app") or HasAttrOnDatapoint("container", "otel-spring-example")' # Filter by container attribute
# - 'not (resource.attributes["container"] == "react-o11y-app")' # Exclude kubelet metrics
# - 'resource.attributes["namespace"] =="kube-system"' # Filter by namespace
# - 'resource.attributes["namespace"] =="default"' # Filter by namespace
# - 'resource.attributes["namespace"] =="monitoring"' # Filter by namespace
# - 'resource.attributes["namespace"] =="cert-manager"'
# - 'resource.attributes["namespace"] =="keycloak"' # Filter by namespace
# - 'resource.attributes["namespace"] =="kube-public"' # Filter by namespace
# - 'resource.attributes["namespace"] =="kube-node-lease"' # Filter by namespace
# - 'resource.attributes["namespace"] =="opentelemetry-operator-system"' # Filter by namespace
# - 'resource.attributes["namespace"] =="service-foundry"'
# - 'resource.attributes["namespace"] =="traefik"'# Filter by namespace and container
# include:
# match_type: expr
# expressions:
# - 'Label("container") == "react-o11y-app"' # Use OR within a single expression
# - 'attributes["container"] == "react-o11y-app" or attributes["container"] == "otel-spring-example"' # Use OR within a single expression
# - 'resource.container == "kubelet"'
# - 'resource.container == "kube-proxy"'
exporters:
debug:
verbosity: detailed
prometheus:
endpoint: "0.0.0.0:8889"
service:
pipelines:
metrics:
receivers: [otlp, prometheus]
processors: [filter/metrics, filter/containers]
exporters: [debug, prometheus]
1 | Scrapes cAdvisor metrics via Prometheus receiver. |
2 | Applies filtering to reduce data volume. |
3 | Targets specific containers of interest. |
For details on the OpenTelemetry Collector Filter processor, refer to the Filter Processor documentation.
Deploy with:
$ kubectl apply -f kubelet-metrics-collector.yaml
Setting up a ServiceMonitor
Create a ServiceMonitor to instruct the OpenTelemetry Target Allocator:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: kubelet-cadvisor
namespace: kube-system
labels:
otel-collector: kubelet-metrics
metrics-unit: monitoring
release: otel # should match your OTEL Target Allocator or Prometheus selector
spec:
jobLabel: k8s-app
selector:
matchLabels:
k8s-app: kubelet # must match the label of the kubelet service
namespaceSelector:
matchNames:
- kube-system
endpoints:
#(1)
- port: https-metrics
scheme: https
interval: 30s
#(2)
path: /metrics/cadvisor
tlsConfig:
insecureSkipVerify: true
#(3)
bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
1 | The https-metrics port is used for secure scraping of Kubelet metrics, while the http-metrics port is used for insecure scraping. The cadvisor port is specifically for cAdvisor metrics. |
2 | The path is set to /metrics/cadvisor , which is the endpoint for Kubelet cAdvisor metrics. |
3 | The kubelet-scraper service account is used to authenticate the scraping of Kubelet metrics. This service account should have the necessary permissions to access the Kubelet metrics endpoint. |
Apply it:
$ kubectl apply -f kubelet-cadvisor-servicemonitor.yaml
Merging Metrics into Prometheus
To forward these metrics to Prometheus:
apiVersion: monitoring.coreos.com/v1alpha1
kind: ScrapeConfig
metadata:
name: kubelet-metrics-scrape-config
namespace: o11y
# namespace: monitoring
labels:
#(1)
prometheus: o11y-prometheus
spec:
staticConfigs:
- labels:
job: kubelet-metrics-scrape-job
targets:
# - kubelet-metrics-collector:8889
#(2)
- kubelet-metrics-collector.monitoring.svc:8889
1 | This label selector is used to identify the ScrapeConfig that can be used by Prometheus to scrape metrics from the Kubelet cAdvisor endpoint. |
2 | The endpoint is the Prometheus exporter endpoint of the OpenTelemetry Collector that is scraping Kubelet cAdvisor metrics. |
Apply the scraping configuration to Prometheus:
$ kubectl apply -f kubelet-metrics-scrape-config.yaml
Visualizing with PromQL in Grafana
Sample PromQL:
container_memory_usage_bytes

Note that the metrics include metrics related to containers including otel-spring-example
, react-o11y-app
, and 'cassandra'.
filter/containers:
error_mode: propagate
metrics:
datapoint:
- |
not ( attributes["container"] == "react-o11y-app"
or attributes["container"] == "otel-spring-example"
or attributes["container"] == "cassandra")
You can modify the queries to specify containers of interest.
container_memory_usage_bytes{container="otel-spring-example"} OR container_memory_usage_bytes{container="react-o11y-app"}
The following query can be used to see the File System Read Bytes for containers:
container_fs_reads_bytes_total
- NOTE
-
Metrics like container_fs_reads_bytes_total may be filtered out. To retain them, adjust the filter processor in the OpenTelemetry Collector config:
filter/metrics:
metrics:
include:
match_type: regexp
metric_names:
- "^container_cpu.*"
- "^container_memory_.*"
You can see the result of the query in Grafana:

Conclusion
By integrating cAdvisor metrics exposed by Kubelet into your observability stack via OpenTelemetry Collector and Prometheus, you gain powerful insights into container-level performance across your Kubernetes nodes. This guide helps you configure access, scrape securely, filter meaningfully, and visualize effectively using Grafana.
📘 View the web version: