Skip to main content

Nine Years in Beta Is Over: What Kubernetes 1.37's Stable Metrics API Means for Your Autoscaler

10 min readDora NodaDora Noda
Share
On this page

Every kubectl top you have ever run, and every CPU-based HPA decision your clusters have ever made, flowed through an API that Kubernetes itself labeled provisional. The resource Metrics API went beta in Kubernetes v1.8 — September 2017 — and then sat there, unchanged and unlabeled-stable, for nearly nine years. Kubernetes v1.37 "Garhwal," released August 26, 2026, finally promotes metrics.k8s.io to stable (v1). Nothing about the data changed: same two resource types, same fields, same CPU and memory numbers. What changed is the promise behind them — and if you operate autoscaling, dashboards, or usage metering on clusters you own, that promise is worth more than a feature.

The short version up front: metrics.k8s.io/v1 is byte-for-byte the API you already know, now carrying GA stability guarantees instead of beta's "could still change" asterisk. kubectl top already prefers it. The one gotcha: the HPA controller in 1.37 still speaks only v1beta1, so keep both versions served. And "stable" describes the contract, not the coverage — the API still returns point-in-time CPU/memory only, with no history and no custom metrics. The checklist and boundary table below are the whole operational story; the rest of the post is the reasoning behind each row.

The news in 30 seconds

The timeline is the story. The resource Metrics API debuted as alpha in v1.6, graduated to beta in v1.8, and then froze — not abandoned, just never formally graduated, while the entire ecosystem (HPA, VPA, kubectl top, every dashboard that ever polled /apis/metrics.k8s.io/v1beta1) built on it as if it were stable. The 1.37 release announcement frames the graduation explicitly as cleanup: it "follows the Kubernetes project's goal of avoiding permanent Beta APIs," the prevent-permabeta push that says an API must either graduate or be deprecated.

KEP-5207, the proposal behind the move, makes the rationale concrete: a beta label carries an implicit instability signal, and clients that bet production behavior on a beta API carry a risk nobody ever prices — until the day a beta gets removed on schedule and someone's autoscaler is on the wrong side of it. The graduation removes that risk for the most-consumed metrics surface in the ecosystem.

Three facts to file before the implications:

  • The v1 surface is identical to v1beta1. No renamed fields, no new fields, no changed meaning for the CPU and memory values. This is a version promotion, not a data change — anything that parses v1beta1 responses parses v1 responses.
  • The API stays deliberately small. Two resource types — NodeMetrics and PodMetrics, the latter with a per-container breakdown — serving CPU and memory usage for autoscaling and basic inspection. The announcement post says it plainly: this is not a replacement for a full monitoring pipeline or the custom metrics API.
  • No feature gate, but the serving side is yours. metrics.k8s.io is served through the aggregation layer by an implementation you run — almost always metrics-server — so v1 exists in your cluster only once your implementation serves it and the v1.metrics.k8s.io APIService is registered.

What "stable" actually guarantees

Beta APIs in Kubernetes are removable on a schedule: once deprecated, a beta gets at least 9 months (or 3 releases) before removal. Nobody expected v1beta1.metrics.k8s.io to disappear — half the ecosystem would break — but "nobody expects it" is not a contract, and platform teams building multi-year roadmaps on it were technically building on an API the project reserved the right to sunset.

GA changes the legal shape of that bet. Under the project's deprecation policy, a stable API cannot be removed until a successor exists, and even a deprecated GA API must be served for at least 12 months (or 3 releases). For an API whose payload hasn't changed in nine years, the practical effect is simple: the thing you read today is the thing you will read in three years, and any future change arrives as a new version with a migration window, not a quiet beta removal.

That matters most for three consumers that sit downstream of every self-hosted PaaS:

  • Custom autoscalers. If you wrote your own scaler — per-tenant concurrency limits, queue-depth-plus-CPU composite decisions, scale logic HPA's model can't express — and it polls metrics.k8s.io for the resource half of its inputs, the "the API could still change" line item drops out of your risk register. Pin v1, with v1beta1 fallback during the transition, and stop thinking about it.
  • Dashboards and tenant-facing usage meters. Any per-pod or per-node CPU/memory readout you show tenants, or feed into showback/chargeback math, is now built on a frozen contract. The numbers were always right; now the schema carrying them is guaranteed.
  • VPA and rightsizing loops. Vertical autoscaling recommendations consume the same signal. A stable API means the recommendation pipeline's input contract is versioned, so upgrades to 1.37+ clusters stop carrying "does the metrics shape still parse" as an open question.

None of these consumers get new data. They get something rarer in infrastructure: one fewer thing that can surprise them.

What to do on your fleet this month

The migration is small enough to be a checklist rather than a project. In order:

  1. See what your clusters serve. kubectl get --raw /apis/metrics.k8s.io/ | jq . lists the served versions. If you see only v1beta1, your metrics-server (or alternative implementation) predates v1 support.
  2. Upgrade the implementation, then verify the APIService. Once your implementation serves v1, confirm the aggregation registration is healthy: kubectl get apiservice v1.metrics.k8s.io should report available. No feature gate to flip — availability is purely a function of the serving implementation plus registration.
  3. Serve both versions through the transition. The official guidance is explicit: keep v1beta1 available alongside v1 for older clients. This costs nothing — same data, two group versions — and skipping it is the only way to break things.
  4. Point new clients at v1 with fallback. New dashboards, meters, and custom scalers should prefer v1 and fall back to v1beta1, mirroring what kubectl top already does automatically.

And now the gotcha that justifies step 3: the HPA controller in Kubernetes 1.37 supports only v1beta1. Discovery-based selection between v1 and v1beta1 is planned but did not ship in 1.37. So the single most important consumer of the Metrics API — the reason the API exists — cannot use the stable version yet. If you got enthusiastic and dropped v1beta1 the day you upgraded, every CPU/memory HPA in the fleet would stop scaling. Keep both served until a future release teaches the HPA controller to discover v1, and treat "HPA speaks v1" as the release note to watch for, not this one.

What stable does NOT change: the boundary table

Here is where "stable" gets misread. The graduation freezes the contract; it does not widen the coverage. Every limitation below survives 1.37 intact, and each row names the system that actually fills the gap:

NeedMetrics API (v1)What fills it instead
Current CPU/memory per pod and nodeYes — this is the whole API
History ("what did this pod use Tuesday?")No — latest values only, held in memoryPrometheus (or any TSDB) scraping kubelets / cAdvisor
Custom/business metrics (queue depth, latency, requests/sec)No — resource metrics onlycustom.metrics.k8s.io via Prometheus Adapter, or KEDA scalers
External metrics (cloud queue length, SaaS webhooks)Noexternal.metrics.k8s.io via adapter, or KEDA
Scale-to-zero on resource metricsNo — HPA needs a running pod to measureKEDA / external-metric HPAs (1.37 also lets external/object-metric HPAs set minReplicas: 0)
Sub-minute freshnessPartially — metrics-server scrapes on --metric-resolution (default 60s; don't go below the kubelet's 15s floor)Direct Prometheus scrapes for tighter loops

Three rows deserve emphasis because they bite self-hosted operators specifically:

No history means no usage metering from this API alone. metrics-server keeps the latest scrape in memory and forgets the rest; it was tested to 5,000 nodes at 30 pods per node precisely because it stores nothing. Tenant-facing "your app used N CPU-hours this week" math needs a TSDB behind it. The stable API gives you a guaranteed-fresh current reading to feed that pipeline — it is not the pipeline.

No custom metrics means the Prometheus side of your stack is untouched. If your autoscaling story involves anything beyond CPU and memory — request latency, queue depth, GPU utilization — you still need the adapter (or KEDA) serving custom.metrics.k8s.io / external.metrics.k8s.io, both of which remain beta. The graduation covers the resource signal only; the custom-metric surfaces are still carrying the permabeta asterisk the resource API just shed.

Freshness is a floor, not a knob you turn freely. The scrape interval defaults to 60 seconds and the kubelet computes at 15-second resolution, so setting --metric-resolution below 15s buys nothing. HPA reads on its own 15-second loop on top of that, which means a load spike can take the better part of a minute to become visible to a scaling decision. For bursty tenant workloads, that two-loop delay is the number to design around — stable API or not.

What this unlocks for a self-hosted PaaS

Put the guarantee and the boundary together and the roadmap consequence is concrete: the resource-metrics half of a platform's autoscaling and observability story just became foundation you can build on without hedging, while the custom-metrics half stays exactly where it was.

Concretely, three platform decisions get easier:

  • Ship tenant usage meters on metrics.k8s.io directly. Per-pod CPU/memory for live dashboards and per-tenant current-usage views no longer need an abstraction layer "in case the API changes." Keep Prometheus behind it for history; read the live number from the stable endpoint.
  • Write the custom scaler you've been postponing. Composite autoscaling (CPU plus queue depth, per-tenant caps, cost-aware scale-down) typically reads resource metrics for one input and a custom source for the other. The resource input is now a frozen contract — one less version matrix to maintain across fleet upgrades.
  • Simplify the upgrade runbook. "Verify metrics parsing after upgrade" drops from a test-everything item to a check-the-APIService item. On a Cluster-API-managed fleet where control-plane and metrics-server versions move together, that's one fewer cross-component interaction to verify per release.

The honest framing: this graduation pays down risk, not toil. Nobody's pager gets quieter because of v1. But the class of incident where a version skew silently breaks metrics consumption — the kind that pages at 3am during a fleet upgrade — just lost its most likely trigger.

Watch the next shoe

Two things to keep on the radar. First, no removal timeline for v1beta1 has been announced — it remains fully served in 1.37, and given that HPA itself still depends on it, removal can't happen until the controller learns discovery. When that release note lands, that's your signal to start the real migration clock.

Second, notice what didn't graduate: custom.metrics.k8s.io and external.metrics.k8s.io are still beta. The resource API just demonstrated the project's willingness to graduate a nine-year-old beta rather than deprecate it, which is encouraging precedent — but until those surfaces get their own KEP-5207 moment, every Prometheus Adapter and KEDA deployment in your fleet still reads from a provisional contract. The permabeta cleanup has begun; it isn't finished.

Nine years is a long time to label something "beta" while the world treats it as load-bearing. Kubernetes finally updated the label to match reality. Take the win: serve both versions, point new code at v1, and spend the risk budget you just got back on the custom-metrics surfaces that still need it.

Running your own PaaS on Kubernetes? 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.

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