Sign in
← Kubernetes: observability
6 min · medium · Lesson 2 of 3

kubectl top: CPU and memory right now

Where kubectl top gets its numbers, how to read millicores, and why the command doesn't work without metrics-server.

Why

A Pod is lagging, a node is under load, and the first thing you want to know is who's eating CPU and memory right now. kubectl top gives the fastest answer. It's also the first thing that trips people up: on a fresh cluster the command often fails, and it's worth understanding why.

Concepts

kubectl is the command-line tool for managing Kubernetes. A Pod is the smallest unit you run, and one or more containers run inside it. A node is a machine in the cluster that runs Pods.

kubelet is the Kubernetes agent on a node that runs containers and knows how many resources they're using. metrics-server is the component that collects current CPU and memory metrics from every kubelet and serves them through the Kubernetes API.

The kubectl top command shows current CPU and memory consumption by Pod and by node. It's a snapshot of the current moment: there are no charts, no history, no aggregation here. But it's the fastest way to answer "who's eating memory" or "how loaded is this node".

How it works

kubectl itself doesn't compute anything. It makes a GET request to the Kubernetes API at /apis/metrics.k8s.io/v1beta1/ and formats the response. metrics-server sits behind that path.

kubectl top
  └─> Kubernetes API  /apis/metrics.k8s.io/v1beta1/
        └─> metrics-server   every 15 s
              └─> kubelet  /metrics/resource

metrics-server polls every node's kubelet every 15 seconds through the /metrics/resource endpoint. kubelet returns consumption by container, Pod and node. The interval is set by the --metric-resolution flag: the official manifest sets it to 15s, and without the flag metrics-server polls every 60 seconds.

Without metrics-server, kubectl top doesn't work at all:

kubectl top pod
error: Metrics API not available

metrics-server isn't part of Kubernetes, and many clusters don't have it installed by default. Some distributions and managed providers install it out of the box, others leave it to the administrator. kind, for example, doesn't have it. The error Metrics API not available on a fresh cluster most often means exactly this, not a broken cluster. You can confirm it like this:

kubectl get apiservice v1beta1.metrics.k8s.io
Error from server (NotFound): apiservices.apiregistration.k8s.io "v1beta1.metrics.k8s.io" not found

metrics-server is installed as a separate manifest or Helm chart from the kubernetes-sigs/metrics-server repository. It's best to pin the version explicitly:

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.9.0/components.yaml

Main commands

# All Pods in the current namespace.
kubectl top pod

# Across the whole cluster.
kubectl top pod -A

# Sorting.
kubectl top pod --sort-by=cpu
kubectl top pod --sort-by=memory

# Per-container breakdown within a Pod.
kubectl top pod --containers

# Nodes.
kubectl top node
kubectl top node --sort-by=cpu

Empty output or an error right after installing metrics-server doesn't mean it's broken. It takes 30-60 seconds to collect the first reading.

The --containers flag matters for Pods with a sidecar. A sidecar is a helper container alongside the main one. With this flag you see each container's consumption separately; without it, you get the Pod's total.

Units

CPU is measured in millicores. 100m is 0.1 of a core, 1000m is one full core. The same unit is used in the requests and limits fields, so the values are easy to compare without converting to percentages.

CPU in top is the average load over the last interval. The Linux kernel only exposes a cumulative counter: how many seconds of CPU time a container has used since it started. metrics-server takes two readings and divides the difference by the time between them.

Memory is measured in bytes with a Mi or Gi suffix. 1Gi equals 1024 MiB: Kubernetes uses binary prefixes. top shows the working set. The working set is the container memory the kernel can't reclaim under memory pressure. It includes both process memory and files written to tmpfs (for example, an emptyDir with medium: Memory).

The output of top node has a percentage column. That's the share of the node's allocatable capacity. Allocatable is the amount of node resources available for Pods. This percentage is what you use to decide whether it's time to add a node.

Don't confuse consumption with requests and limits. requests is the amount a Pod asked the scheduler for, limits is the ceiling. A Pod can request 400Mi and use 1Mi, or request 64Mi and hold 120Mi in memory. In top you only see the second number from each pair: what's actually in use.

Direct access to the metrics API

# demo here is the namespace name, use your own
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/namespaces/demo/pods"
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes"

This is the same data top shows, but in raw JSON. Handy for scripts and automation. If you have jq, add | jq to make the JSON readable.

If there's no metrics-server

Sometimes you can't or don't have time to install metrics-server: someone else's cluster, no permissions, you need an answer in five minutes. Then you can ask kubelet directly, through the same Kubernetes API. This is exactly the endpoint metrics-server polls:

kubectl get nodes
kubectl get --raw "/api/v1/nodes/<node name>/proxy/metrics/resource"

The response is in Prometheus text format: one line per metric, labels in curly braces, then the value and the measurement time in milliseconds. You need two metrics:

container_cpu_usage_seconds_total{container="app",namespace="demo",pod="web"} 12.48 1760270000000
container_memory_working_set_bytes{container="app",namespace="demo",pod="web"} 2.1004288e+07 1760270000000

Memory you can read right away: 2.1004288e+07 bytes is about 20Mi. CPU is trickier. container_cpu_usage_seconds_total is that same cumulative counter of seconds since the container started. A large value could mean the container is loading the CPU right now, or that it's simply been running for a long time. To get the load, take two readings with a pause and compute the difference:

kubectl get --raw "/api/v1/nodes/<node name>/proxy/metrics/resource" | grep '^container_cpu_usage_seconds_total' | grep 'namespace="demo"'

If the counter grew by 2.5 over 10 seconds, the container was using 0.25 of a core on average, that is 250m. This is exactly how metrics-server computes it too.

Have several nodes? Each node has its own endpoint, so poll them one by one. You need nodes/proxy permissions, which a cluster administrator has.

What top can't do

top answers "what's happening right now", not "what happened last night".

Relation to HPA and requests

metrics-server is the foundation for HPA based on CPU and memory metrics. HPA is a controller that changes the number of replicas based on load. For a target like "60% CPU load", it takes the Pod's current consumption, divides it by requests, and gets a percentage.

If the Pod's containers have no requests for that resource, the percentage is undefined, and HPA won't scale on that target. A target in absolute units (averageValue, for example 200m) works even without requests.

Common mistakes

In short

What you'll need for the tasks

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 1 lab for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson k8s-observability-02-kubectl-top