Skip to main content

Kubernetes 1.37's Stable Metrics API: What Actually Changes for Metrics Server, HPA, and kubectl top

9 min readDora NodaDora Noda
Share
On this page

Nine years is a long time to live in beta. The metrics.k8s.io API — the two-word answer behind every kubectl top invocation and every CPU-based HorizontalPodAutoscaler decision — shipped as v1beta1 back in the Kubernetes 1.8 era and never moved. Until now: Kubernetes 1.37 "Garhwal," released August 26, 2026 with 67 enhancements (16 graduating to stable), finally promotes the resource-metrics API to stable v1 under KEP-5207.

Here is the verdict up front: this is a contract graduation, not a feature delivery. Nothing gets faster, nothing new becomes measurable, and nobody's metrics-server DaemonSet can be deleted. What you get is a durable API promise — v1 means the shape of NodeMetrics and PodMetrics is now covered by Kubernetes' deprecation policy instead of living on borrowed beta time. If you run a self-hosted fleet, the work is a compatibility audit, not a migration.

Fleet operator in a hurry? Jump to the upgrade checklist. The two rules that matter: register and verify both v1 and v1beta1 APIService endpoints before touching anything, and do not retire the beta endpoint until metrics-server, kubectl top, and your HPAs have each provably moved.

What shipped in 1.37 (and what "stable" actually means)

The 1.37 release takes the API definition that has been frozen in practice since ~2017 and declares it frozen in policy. Under Kubernetes' rules, a stable API cannot be removed or incompatibly changed without a full deprecation cycle; a beta API technically can. For nine years the entire autoscaling stack floated on an endpoint the project was merely choosing not to break. Now the promise is written down.

Concretely, the API server can now serve metrics.k8s.io/v1 alongside the long-standing metrics.k8s.io/v1beta1. The beta endpoint continues to be served — graduation deprecates it, it does not delete it — so nothing breaks on upgrade day. Removal, if it ever comes, follows the standard policy: at minimum a full release cycle of deprecation warnings after a removal is announced, which means v1beta1 is safe through the 1.37 support window and well beyond.

The part teams routinely misread is what did not ship. The table below is the whole story in one glance:

Changed in 1.37Unchanged in 1.37
metrics.k8s.io/v1 is now a stable, served API versionThe API is still aggregator-backed — no built-in implementation
v1beta1 is deprecated but still servedYou still need a provider (metrics-server or equivalent) installed
Clients can negotiate the v1 contractMetrics are still point-in-time, in-memory, with no history
Tooling can start preferring v1 with fallbackResource metrics still cover only CPU and memory

Read the right column twice. Every "does stable metrics mean I can drop X" question resolves to no: no dropping metrics-server, no dropping Prometheus, no autoscaling on new signals. The left column is still worth having — a platform that builds tenant-facing autoscaling on top of this API (as any git-push PaaS does) finally stands on a contract instead of a habit.

Compatibility audit, part 1: metrics-server and the APIService

Metrics-server is the component most fleets actually run: a lightweight in-memory aggregator that scrapes kubelets and re-exposes the numbers through the metrics.k8s.io API. The published 0.7.x line serves metrics.k8s.io/v1beta1, and the 1.37 graduation does not change what your installed metrics-server serves. The apiserver can offer v1, but with no provider registered behind it, v1 answers nothing.

That gives the audit its first concrete step: after upgrading the control plane, check which versions are actually registered and healthy, not which versions the release notes mention:

bash
kubectl get apiservice | grep metrics.k8s.io
kubectl get apiservice v1beta1.metrics.k8s.io -o yaml | grep -A3 status
kubectl get apiservice v1.metrics.k8s.io -o yaml | grep -A3 status

What you want to see is both endpoints Available: True. If v1.metrics.k8s.io is missing or unhealthy, your metrics-server version predates v1 support — check its release notes for a version that registers the v1 APIService, upgrade it, and re-verify. Until that endpoint reports available, every client in the cluster is still living on beta, whatever the control-plane version says.

For a Cluster API fleet this has one extra wrinkle worth naming: metrics-server is typically deployed per workload cluster (as an addon or a clusterctl-adjacent manifest), not once on the management cluster. The audit above has to run against each workload cluster, and the metrics-server version pinned in your cluster templates is the thing to bump — upgrading the management cluster alone fixes exactly one cluster, usually the one with no tenants on it.

Compatibility audit, part 2: kubectl top

kubectl top node and kubectl top pod are thin clients over this exact API, which makes them the cheapest canary in the building. Post-upgrade behavior to expect: a current kubectl negotiates the preferred version (v1) and falls back to v1beta1 when the server does not offer it. In practice that means kubectl top keeps working through every intermediate state of your rollout — old kubectl against new cluster, new kubectl against a cluster whose metrics-server you have not upgraded yet.

Verify it explicitly rather than assuming the negotiation saved you:

bash
kubectl top nodes
kubectl top pods -A --sort-by=cpu | head
kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes | head -c 300
kubectl get --raw /apis/metrics.k8s.io/v1/nodes | head -c 300

The third command should succeed everywhere (beta still served). The fourth succeeds only where a v1-capable provider is registered — it is the direct probe of part 1's APIService check, from the client's side. If top works but the v1 raw query 404s, you are on fallback: fine for today, and precisely the state the checklist below tells you to clear before declaring victory.

One operational note for platform teams: if you ship a constrained support shell, an agent sandbox, or docs that pin a kubectl version for tenants, the fallback behavior is what keeps those working. Nothing to change there — just do not "clean up" by forcing a minimum kubectl version until the server side is uniformly v1-capable.

Compatibility audit, part 3: HPA

The HorizontalPodAutoscaler is the highest-stakes consumer of this API, and the one where the 1.37 story needs the most careful reading. Resource-based HPA targets (type: Resource on cpu/memory) resolve through metrics.k8s.io, and the HPA controller's established behavior is built around the v1beta1 representation. A stable v1 existing does not rewire the controller in the same release — expect the controller to keep reading what it has always read while the ecosystem converges, and verify rather than assume.

The check that matters is behavioral, not version-string inspection. Pick a non-critical HPA-backed workload and confirm the full loop still closes after the upgrade:

bash
kubectl get hpa -A
kubectl describe hpa <name> -n <namespace> | grep -iE "metrics|condition|event" 

You want ScalingActive: True and fresh events showing successful metric reads — not FailedGetResourceMetric, not <unknown> targets lingering past a metrics-server restart. <unknown> immediately after upgrading metrics-server is normal (its window is ~60 seconds of scrapes before it serves); <unknown> persisting for ten minutes is the signal that the HPA-to-metrics path is broken, and the APIService checks from part 1 are where you go look.

Two adjacent 1.37 facts belong here so nobody conflates them with this graduation. First, HPA scale-to-zero went beta in 1.37 (external and object metrics may set minReplicas: 0), which is genuinely new autoscaling behavior — but it concerns which metrics can idle a workload to zero, not the resource-metrics API version, and CPU/memory scaling to zero remains unsupported. Second, nothing about v1 widens HPA's resource signals beyond CPU and memory. Teams that need requests-per-second, queue depth, or p95 latency still need the custom/external metrics path (Prometheus adapter or KEDA) exactly as before — the next section says this once, plainly, so it stops being re-learned per upgrade.

The CAPH fleet upgrade checklist

Everything above compresses into one ordered procedure for a Cluster API fleet on Hetzner (or any infrastructure provider — nothing here is Hetzner-specific except where noted). Do it per workload cluster, staging first:

  1. Snapshot the before-state. Record kubectl get apiservice | grep metrics, kubectl top nodes, and kubectl get hpa -A output. If HPA targets already show <unknown>, fix that first — you cannot audit a change against a broken baseline.
  2. Upgrade the control plane, leave workloads alone. Let kubelet, apiserver, and controller-manager settle. Confirm v1beta1.metrics.k8s.io still Available: True — the beta endpoint surviving the upgrade is expected, not a problem.
  3. Upgrade metrics-server via your cluster template. Bump the pinned version to one whose notes claim metrics.k8s.io/v1 support, roll it through your normal template-to-cluster pipeline, and confirm both APIServices report available.
  4. Probe both API versions directly. Run the two kubectl get --raw queries from part 2. Beta must answer; v1 must now answer too. If v1 404s, stop — the provider upgrade did not take, and everything downstream still runs on beta.
  5. Confirm the consumers. kubectl top nodes and top pods -A return fresh numbers; every HPA shows real targets and ScalingActive: True within ten minutes. Watch for FailedGetResourceMetric events.
  6. Soak, then decide about beta — which means: do nothing yet. There is no prize for removing v1beta1.metrics.k8s.io early and real downside (older agents, pinned clients, and controllers that still prefer it). Leave both registered until a future metrics-server/controller combination documents beta removal, then re-run this checklist in reverse: confirm zero beta traffic, then retire.

Step 6 is deliberately anticlimactic. The most common self-inflicted outage in API graduations is not the upgrade — it is the premature cleanup, weeks later, by someone who read "deprecated" as "dead." Beta deprecation here is a signal to prefer v1 in anything new you build, not a work order to delete the endpoint your current HPA reads.

The one misconception to retire with this release

Stable resource metrics invite a tempting simplification: if CPU and memory now have a first-class, stable API, can the platform autoscale tenants on that alone and skip the Prometheus/custom-metrics pipeline? The honest answer is the same after 1.37 as before, and it is worth stating as the close because it determines real roadmap spending:

SignalAPIWhat it tells youGood default for
CPU / memorymetrics.k8s.io (now stable v1)The container is busyFirst-line tenant autoscaling, kubectl top debugging
Requests in flight, queue depth, p95custom.metrics.k8s.io via adapterThe service is saturatedWeb/API workloads where CPU lags demand
Queue length, stream lag, cron schedulesexternal.metrics.k8s.io / KEDAWork exists outside any podWorkers, consumers, scale-to-zero

CPU is a lagging, often misleading proxy for web saturation — a service can be queueing requests while CPU looks merely warm. The 1.37 graduation makes the first row's contract durable; it does not promote the first row to cover the other two. Budget accordingly: keep the metrics-server path healthy for what it is, and keep the adapter/KEDA path on the roadmap for everything it alone can see.

Nine years in beta ends not with new powers but with a promise kept — the least glamorous and most load-bearing kind of Kubernetes news. If self-hosting the platform behind your apps (rather than renting someone else's control plane) is the direction you are headed, Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex