Picture the alert that ruins a Friday: a shared node is sitting at 40% CPU utilization, memory requests are barely half of capacity, every dashboard is green — and two tenants are paging you about p99 latency spikes and stalled builds. You SSH in, run top, and see nothing obviously wrong. Utilization says the machine is idle. The workloads say it is on fire. Both are telling the truth, because utilization and contention are different things, and until this spring Kubernetes only showed you the first one.
Kubernetes v1.36 ("Haru," released April 22, 2026) graduated Pressure Stall Information (PSI) metrics to general availability, giving the kubelet a native way to report how much time tasks actually spend stalled waiting on CPU, memory, and I/O — at node, pod, and container level. The KubeletPSI feature gate is now locked to true and cannot be disabled. If you run a multi-tenant fleet where several apps share each node, this is the single most useful observability upgrade in the 1.36 release. Here is the verdict up front, with the evidence behind it in the sections below:
| Your situation | What PSI changes for you |
|---|---|
| Bin-packed nodes where tenants share CPUs and disks, and "utilization looks fine" incidents keep recurring | You finally get a contention signal: CPU/memory/I/O stall percentages per node, pod, and container via the Summary API and Prometheus |
A fleet still making eviction and autoscaling decisions purely from CPU percentages and memory.available thresholds | You can now layer stall-based early warnings in front of those thresholds — but PSI does not drive kubelet eviction or the scheduler by itself yet |
| Nodes on older kernels or cgroup v1 | You must meet the enablement checklist first (kernel 4.20+, CONFIG_PSI=y, cgroup v2), or the kubelet silently reports nothing |
The rest of this post shows exactly what PSI measures, what 1.36 changed, how to turn it on, and the three queries that earn their keep on a shared fleet.
The lie utilization tells
CPU utilization answers "how busy was the resource." PSI answers "how long did tasks wait for it." Those diverge exactly when bin-packing works as designed: several tenants share one node, each well under its requests, but their bursts correlate. Tenant A's compile job saturates disk I/O for ninety seconds; tenant B's web workers are not CPU-starved — plenty of idle cycles exist — but every request that needs a page-cache read stalls behind A's I/O queue. CPU graphs stay flat. Latency explodes. The old signals cannot distinguish "40% utilized and healthy" from "40% utilized and everybody is waiting in line," because they never measured the line.
Memory has the same blind spot with higher stakes. A node can report comfortable memory.available while pods spend a growing share of wall-clock time stalled in direct reclaim — the kernel desperately freeing pages before each allocation proceeds. By the time the kubelet's hard eviction threshold trips, tenants have already been degraded for minutes. PSI's memory full metric — the share of time all non-idle tasks were stalled simultaneously — is precisely the early warning that reclaim pressure exists before the eviction hammer falls.
PSI in five minutes
PSI has lived in the Linux kernel since 4.20 (2018, from Johannes Weiner's original patch series). With CONFIG_PSI=y, the kernel maintains /proc/pressure/cpu, /proc/pressure/memory, and /proc/pressure/io, and on cgroup v2 every cgroup gets cpu.pressure, memory.pressure, and io.pressure files that aggregate stalls for just that cgroup's tasks. Kubernetes 1.36 simply wires those per-cgroup files into the kubelet's existing telemetry. Two dimensions matter:
| Dimension | Values | What it means |
|---|---|---|
| Pressure type | some vs full | some: at least one task stalled — early contention signal. full: all non-idle tasks stalled at once — nothing is making progress, severe shortage |
| Window | avg10, avg60, avg300 + total | avg*: percent of wall-clock time stalled over 10s / 60s / 5min moving averages. total: cumulative stalled microseconds counter, good for rate math |
Note the asymmetry: CPU only reports some (there is always something runnable somewhere, so full is meaningless for CPU), while memory and I/O report both. Here is what the raw kernel interface looks like on a node under mild contention:
$ cat /proc/pressure/cpu /proc/pressure/memory
some avg10=2.04 avg60=0.75 avg300=0.40 total=157656722
some avg10=0.74 avg60=0.52 avg300=0.21 total=35232438
full avg10=0.00 avg60=0.00 avg300=0.00 total=539105The CPU line says tasks spent about 2% of the last 10 seconds waiting for a core; memory shows brief partial stalls but zero full stalls — nothing here is an emergency. A node in real trouble shows memory full avg10 climbing into double digits while utilization dashboards stay amber at worst. That gap — green utilization, red stall percentages — is the entire value proposition in one picture.
What 1.36 GA actually changed
PSI telemetry is not new in 1.36; it first appeared as an alpha feature in v1.33. Graduation to stable changed three things that matter operationally:
First, the KubeletPSI feature gate is locked to true. You cannot disable it, and if you explicitly set it Kubernetes ignores the value without erroring. One fewer gate to track across fleet upgrades.
Second, the kubelet now detects OS-level PSI support from cgroup configuration before reporting, so pressure metrics are only collected and emitted when the node actually supports them. Mixed fleets — some nodes on compliant kernels, some not — get clean data instead of zeros that look like health.
Third, the data lands in two places you already scrape. The kubelet Summary API exposes PSI at node, pod, and container granularity, and the kubelet's /metrics/cadvisor endpoint exposes the same signals in Prometheus format. No sidecar, no DaemonSet, no per-tenant instrumentation: the kernel was already tracking this, and now the kubelet repeats it on channels your monitoring already reads.
The enablement checklist
PSI has hard node prerequisites, and the silent failure mode is "no data," so verify each one:
- Kernel 4.20 or newer. Any distribution from the last several years qualifies, but check:
uname -r. CONFIG_PSI=ycompiled in. Most modern distributions enable it by default. Verify withzgrep CONFIG_PSI /proc/config.gz.psi=1on the kernel command line if your distro disables it by default. Some distributions compile PSI in but leave it off; without the boot parameter the/proc/pressurefiles stay empty.- cgroup v2 on every node. This one is non-negotiable — per-cgroup pressure files only exist on the unified hierarchy. Kubernetes moved cgroup v1 into maintenance mode back in v1.31, so if you have not migrated node images yet, PSI is one more reason on an already long list.
Then confirm end to end. On a node, cat /proc/pressure/cpu /proc/pressure/memory /proc/pressure/io should show live counters. Through the API server, pull one container's pressure block:
kubectl get --raw "/api/v1/nodes/<node>/proxy/stats/summary" \
| jq '.pods[].containers[] | select(.name=="<container>") | {name, cpu: .cpu.psi, memory: .memory.psi}'If the psi fields are absent, walk the checklist backwards — it is almost always cgroup v1 or a missing boot flag.
Three queries that earn their keep
Raw metrics are not insight. On a bin-packed fleet, these three patterns cover most of what PSI is for. Thresholds below are starting points from community practice — tune them against a week of your own avg60 baselines before paging anyone.
1. Catch the noisy neighbor. Alert when a node's CPU some avg60 stays elevated while per-pod CPU usage looks balanced — that combination means tenants are stealing time from each other rather than one tenant spiking. In PromQL against the cadvisor endpoint, watch the node-level CPU pressure average over five minutes and compare with the top-consuming container on that node. If pressure is high and no single container dominates, the problem is correlated bursts across tenants, and the fix is descheduling or spreading, not throttling one pod.
2. Early warning before memory eviction. The kubelet still evicts on memory.available thresholds — PSI does not change that path. What it buys is lead time: alert on memory full avg60 rising above a low single-digit percentage, well before memory.available hits your hard threshold. full means every non-idle task stalled at once, which is direct-reclaim territory; tenants feel it as tail latency minutes before the kubelet acts. Treat this alert as "investigate or migrate workloads now," with the existing eviction threshold as the backstop, not the first signal.
3. Explain the slow build. I/O some is the metric for "the disk is the bottleneck and CPU graphs will never show it." Shared build caches, image pulls landing on the same volume as tenant scratch space, and log-heavy neighbors all show up as I/O some avg10 spikes correlated with slowed pipelines. When a tenant reports a deploy that suddenly takes three times longer with no code change, I/O pressure on their node is now the second place to look after the registry.
The honest scope: what PSI does not do yet
Enthusiasm deserves a boundary. As of 1.36, PSI is an observability signal, not a control-plane input. The kubelet's node-pressure eviction still keys off memory.available, disk thresholds, and image filesystem signals — not stall percentages. The scheduler does not bin-pack, preempt, or deschedule based on PSI. The upstream KEP scope explicitly lists node conditions, taints, and eviction integration as future work to invest in, not shipped behavior.
So "wiring PSI into health checks and scheduling" today means building the loop yourself, outside the kubelet:
- Alert → runbook: Prometheus alerts on
avg60/avg300thresholds route to a playbook (cordon, drain, reschedule the noisy tenant) rather than an automated controller. This is where most small fleets should start. - Signal → autoscaler input: feed node-level pressure into your cluster-autoscaler or capacity policy as a scale-out hint alongside pending-pod counts — pressure with no pending pods still means the fleet is too tight.
- Signal → rightsizing: sustained per-container memory
fullis evidence a pod's limits are wrong, and pairs naturally with the in-place pod resize that went GA in 1.35: raise the limit without the restart, then watch the stall line fall.
Community tooling is already moving this way — node agents that poll the Summary API's PSI block each interval and shed or throttle load exist in the open, which validates the pattern without making it a platform default. Adopt the queries now; automate the loop only after your thresholds have survived a month of real incidents without false paging.
What to do this week
Upgrade one canary node pool to 1.36, walk the four-item checklist, scrape both endpoints, and set the three alerts in warning-only mode. After two weeks, compare every "utilization looks fine" incident against the stall graphs — the first time memory full predicts an eviction your old threshold missed, the feature has paid for itself. The kernel measured contention all along; now your fleet can finally see it.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.



