Kubernetes 1.37: The 10 Changes I’d Actually Care About (and a Pre-Upgrade Checklist)
I read through the 1.37 release notes and the KEPs behind them so you don’t have to. Here’s what matters if you run clusters for a living, with the YAML and the GitHub links.
Kubernetes 1.37 (“Garhwal”) came out on 26 August 2026. It has 67 enhancements: 16 went Stable, 23 went Beta, 27 are new Alpha features, and there’s 1 deprecation.
Most of those won’t change your week. A few of them will, and a couple can break an upgrade if you don’t check first. I sorted them the way I’d sort them for my own team: things you get for free, things you can start using now, and things to check before you upgrade.
Every section links to the KEP on GitHub. If you only have five minutes, jump to the checklist at the end.
Here’s what a few of these look like on a real 1.37 cluster:
1. HPA can finally scale to zero (Beta, on by default)
KEP-2021 · kubernetes/enhancements/keps/sig-autoscaling/2021-scale-from-zero
This one has been around since 1.16 behind the HPAScaleToZero gate. In 1.37 it is Beta and enabled by default. You set minReplicas: 0 and the HPA is allowed to take the workload all the way down to zero, then bring it back when the metric moves again.
The catch is in the metric type. It only works with Object or External metrics. CPU and memory metrics come from running Pods, so with zero Pods there is nothing to measure and nothing to wake up on. In practice this is for queue consumers, batch workers and GPU inference services that sit idle at night.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-worker
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-worker
minReplicas: 0
maxReplicas: 20
metrics:
- type: External
external:
metric:
name: queue_messages_ready
selector:
matchLabels:
queue: orders
target:
type: AverageValue
averageValue: "30"One detail I liked: while the HPA holds the workload at zero, it sets a ScaledToZero=True condition. That's how it tells "I parked this" apart from "a human set replicas to 0 on purpose". When it scales back up, the condition flips to False with reason NotScaledToZero. That's a handy thing to alert on.
If you already use KEDA for this, nothing forces you to switch. But for simple “queue length → replicas” cases you may not need an extra operator any more.
2. kube-apiserver restarts should hurt a lot less
Three changes land on the same code path, which is how the watch cache fills itself after an API server restart:
- Resilient watch cache initialization goes Stable. KEP-4568
- etcd RangeStream (Beta, on by default). KEP-5966
- Concurrent watch object decode (now on by default). KEP-6178
Anyone who has watched a big cluster’s control plane come back after an upgrade knows the problem. The cache warms up with huge LIST calls against etcd. The whole response gets built in memory at once. Then every event is decoded on a single goroutine. If you have CRDs with conversion webhooks, that serial decode can take long enough that etcd compacts the revision you started from, and the init starts over again. It can loop like that for a long time.
In 1.37:
- etcd 3.7+ can stream the range in chunks, all pinned to one revision, so the API server doesn’t have to hold the whole list in memory.
- Decoding runs on 10 workers and the events are put back in order before delivery.
- While the cache warms up, the API server sends bounded requests through and rejects the rest with HTTP 429, so they don’t pile up.
The upstream benchmark (150k Pods) shows cache init about 40% faster from concurrent decode alone, and about 55% faster combined with RangeStream. I haven’t reproduced that myself, so treat it as the project’s number, not mine.
Two things to do here:
- If you write controllers or operators, make sure they handle
429properly: respectRetry-Afterand use exponential backoff. client-go does this already. Hand-rolled HTTP clients often don't. - If you have a conversion webhook that caps concurrency below 10, expect more parallel calls during cache init. The total number of calls stays the same, but more of them arrive at once.
On etcd older than 3.7 the API server notices Unimplemented and falls back to the old unary Range, so nothing breaks.
3. Gang scheduling reaches Beta
KEP-4671 · gang-scheduling · plus workload-aware preemption KEP-5710 · workload-aware-preemption
If you run distributed training on Kubernetes, you’ve probably hit this. An 8-worker job gets 6 Pods scheduled, 2 sit Pending, and those 6 GPUs do nothing until the last 2 fit. Add a second job doing the same thing and both can get stuck waiting for each other.
The default scheduler now understands a PodGroup through the Workload API. It's all-or-nothing: the group gets scheduled only when the whole group fits.
The Beta also brings two things that matter in real clusters:
- Workload-aware preemption. The scheduler won’t evict a few Pods from a group if that doesn’t actually let the waiting workload run.
- PodGroup queueing, plus fixes for livelock when several gangs compete for the same capacity.
There is also new Alpha work around this: a spec.scheduling field on batch/v1 Job (KEP-5547) and a hierarchical CompositePodGroup API. If you've been running Volcano or Kueue only to get gang semantics, it's worth re-evaluating over the next couple of releases.
4. Pod certificates are Stable. Native mTLS identity without sidecars
KEP-4317 · pod-certificates · KEP-3257 · cluster-trust-bundles
To me this is the most underrated GA in 1.37. Your Pod asks for a certificate with a projected volume. The kubelet generates the private key on the node, creates a PodCertificateRequest, and your signer controller issues the cert. The kubelet writes it into the Pod and renews it before it expires.
volumes:
- name: workload-identity
projected:
sources:
- podCertificate:
signerName: "mysigner.example"
keyType: ED25519
credentialBundlePath: credentialbundle.pemKubernetes doesn’t ship a signer for your workloads. You need a controller that watches requests for your signerName. That's on purpose: it's what lets you plug in your own CA, SPIFFE IDs, or a cert-manager based issuer. ClusterTrustBundle (also GA) is how the matching trust anchors get to the Pods that need to verify those certs.
If you currently mount a cert from a Secret that a CronJob rotates “most of the time”, this is the cleaner replacement.
5. Admission policies that load from disk (Beta)
KEP-5793 · manifest-based-admission-control-config
Admission webhooks and ValidatingAdmissionPolicies have always lived in the API. That leaves two gaps. They aren’t enforced during bootstrap, before the objects exist. And anyone with enough RBAC can delete them.
1.37 lets you point the API server at a directory of manifests:
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ValidatingAdmissionPolicy
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: ValidatingAdmissionPolicyConfiguration
staticManifestsDir: "/etc/kubernetes/admission/policies/"Policies loaded this way are enforced from the first second the API server is up, keep working while etcd is down, and can be used to protect the API-based admission objects themselves. Files are watched. A valid change is reloaded, and a broken file leaves the last good config in place.
For platform teams that hand out cluster-admin-ish access to tenants, this is the “guardrail that can’t be kubectl deleted" that we've wanted for a long time.
6. Memory QoS goes Beta, and the defaults are careful
KEP-2570 · memory-qos
The MemoryQoS gate is now on by default on cgroup v2 nodes. Don't panic: on its own it changes nothing. I had to read the KEP to confirm this.
memoryThrottlingFactornow defaults to unset. Nomemory.highgets written, so no surprise throttling.memoryReservationPolicydefaults toNone. Nomemory.minormemory.lowprotection.
You opt in per node:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
memoryThrottlingFactor: 0.9
memoryReservationPolicy: TieredReservationThe detail worth knowing before you flip it on: with TieredReservation, a Guaranteed Pod gets memory.min equal to memory.max. The kernel then can't reclaim that Pod's page cache at all. Cache-heavy workloads like JVMs, Elasticsearch or Postgres can OOM earlier than they used to. The KEP says this plainly. Give those Pods some headroom in the limit, or keep the policy at None on nodes that run them.
7. Storage Version Migration is in-tree and Stable
KEP-4192 · svm-in-tree
When a CRD moves its storage version from v1beta1 to v1, or you rotate your encryption-at-rest key, the objects already in etcd don't rewrite themselves. Until now that meant a kubectl get | kubectl replace loop, or running the out-of-tree migrator.
Get Vivek Raj’s stories in your inbox
Join Medium for free to get updates from this writer.
Now it’s a built-in API with a controller behind it:
apiVersion: storagemigration.k8s.io/v1
kind: StorageVersionMigration
metadata:
name: secrets-reencrypt-2026-10
spec:
resource:
group: ""
resource: secretsWatch .status.conditions for Running, Succeeded or Failed. If you maintain operators, this is the step to run at the end of a CRD version bump.
Here it is in action, rewriting every Secret in one go:
8. “Which PVCs are just costing us money?” (Beta)
KEP-5541 · pvc-last-used-time-status-field
Every cluster I’ve seen has orphaned PVCs: a StatefulSet got deleted and its volumes stayed. 1.37 enables (by default) a new Unused condition on PVCs. It's True once no non-terminated Pod references the claim, and the lastTransitionTime is effectively an "unused since" timestamp.
kubectl get pvc -A -o json | jq -r '
.items[]
| select(any(.status.conditions[]?; .type=="Unused" and .status=="True"))
| [.metadata.namespace, .metadata.name,
(.status.conditions[] | select(.type=="Unused") | .lastTransitionTime)]
| @tsv'Kubernetes deliberately doesn’t delete anything. Deciding what to clean up is still up to you. The timestamp is when the controller noticed the PVC was unused, so it may slightly under-report idle time, but it never over-reports it.
Here’s how it looks. Watch the reason flip from PodUsingPVC to NoPodsUsingPVC once the Pod is gone:
9. Native histograms for control-plane metrics (Beta)
KEP-5808 · native-histogram-support
This one is close to home for me. Control-plane histograms like apiserver_request_duration_seconds_bucket are among the biggest sources of series in most Prometheus setups. Classic histograms make you pick fixed buckets up front, so you trade accuracy against cardinality.
With the NativeHistograms gate on, components expose both classic and native histograms when the scraper negotiates the protobuf format. Your existing dashboards and alerts keep working, and you can move queries over gradually.
Things to check on your side:
- Your scraper has to request the protobuf exposition format. In Prometheus 3.x that’s a scrape config setting, so read the docs for your version.
- Your long-term storage has to support native histograms too. Not every remote-write backend does, so check yours before turning it on.
- Expect the series count to go down after you migrate queries and drop the classic
_bucketseries. That's the whole point.
10. Pod-level checkpoint and restore (Alpha)
KEP-5823 · pod-level-checkpoint-restore
Container checkpointing (forensic, single-container) has existed since 1.25. 1.37 adds CheckpointPod and RestorePod to the CRI, so the kubelet can snapshot a whole Pod and restore it.
It’s Alpha and your container runtime has to implement the new RPCs, so this isn’t for production yet. But look at where it’s heading: live migration of long-running jobs, faster cold starts for heavy inference Pods, and “pause the GPU job, drain the node, resume elsewhere”. It’s worth watching.
Also worth a look
- Scheduler preemption for in-place Pod resize (Alpha, KEP-5836). Before this, a resize that didn’t fit just sat in
Deferred. Now the scheduler can preempt lower-priority Pods to make room. - Node lifecycle conditions (Alpha, KEP-5683):
DrainInProgress,Drained,MaintenancePlannedand friends. Finally one standard place to say "this node is being worked on". - StatefulSet
Recreatestrategy (Alpha, KEP-3541). - KYAML (KEP-5295) and the metrics.k8s.io API (KEP-5207) both reach Stable.
kubectl get -o kyamlis handy when you're tired of YAML's "Norway problem".
Before you upgrade: my pre-flight checklist
SELinuxMount is on by default (KEP-1710). On SELinux-enforcing nodes, volumes from CSI drivers that set seLinuxMount: true are now mounted with -o context=... instead of being relabeled recursively. That's much faster, but a mount can carry only one label. Two Pods with different SELinux labels sharing a volume on the same node can now fail to start. Fix it per Pod with securityContext.seLinuxChangePolicy: Recursive. If you don't run SELinux, this doesn't affect you.
Static Pods can no longer reference Secrets or ConfigMaps (kubernetes/kubernetes#140226). This only ever worked because of a bug, and the opt-out gate is gone. Grep your manifests directory before you upgrade the kubelet:
grep -rnE "secretRef|configMapRef|secretKeyRef|configMapKeyRef" /etc/kubernetes/manifests/cgroup v1 (KEP-5573). Since 1.35 the kubelet refuses to start on cgroup v1 unless you set failCgroupV1: false. Memory QoS and memory-backed volume resize only work on v2 anyway. Check your node images:
stat -fc %T /sys/fs/cgroup # expect: cgroup2fskube-proxy IPVS mode is deprecated (KEP-5495). You’ll get a warning on startup. The plan is off-by-default around 1.40 and removal around 1.43. nftables is the way forward, and it got faster in this release (kube-proxy now talks netlink directly instead of shelling out to nft).
kubectl -n kube-system get configmap kube-proxy \
-o jsonpath='{.data.config\.conf}' | grep 'mode:'kube-dns is deprecated. No new packages are expected after 1.40. Move to CoreDNS if you somehow haven’t.
kubectl run -f is deprecated (kubernetes/kubernetes#138671). It never did anything useful, but it may still be in old scripts.
Three of these checks take seconds:
My take
1.37 isn’t a flashy release. It’s a release for people who carry the pager. The API server is harder to knock over, HPA can scale to zero without extra tooling, Pods get real certificate identity, and gang scheduling is close to being something you can count on.
If I had to pick one thing to do this week, it would be the static Pod grep and the SELinux check. They take five minutes and they’re the two most likely ways for this upgrade to go wrong.
References
- Kubernetes v1.37 release announcement: https://kubernetes.io/blog/2026/08/26/kubernetes-v1-37-release/
- v1.37 CHANGELOG: https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.37.md
- Enhancements repo (all KEPs): https://github.com/kubernetes/enhancements/tree/master/keps
- KEP-2021 HPA scale to zero: https://github.com/kubernetes/enhancements/tree/master/keps/sig-autoscaling/2021-scale-from-zero
- KEP-4568 Resilient watch cache init: https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/4568-resilient-watchcache-initialization
- KEP-5966 etcd RangeStream: https://github.com/kubernetes/enhancements/tree/master/keps/sig-etcd/5966-etcd-range-stream
- KEP-6178 Concurrent watch decode: https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/6178-concurrent-watch-object-decode
- KEP-4671 Gang scheduling: https://github.com/kubernetes/enhancements/tree/master/keps/sig-scheduling/4671-gang-scheduling
- KEP-5710 Workload-aware preemption: https://github.com/kubernetes/enhancements/tree/master/keps/sig-scheduling/5710-workload-aware-preemption
- KEP-4317 Pod certificates: https://github.com/kubernetes/enhancements/tree/master/keps/sig-auth/4317-pod-certificates
- KEP-3257 ClusterTrustBundles: https://github.com/kubernetes/enhancements/tree/master/keps/sig-auth/3257-cluster-trust-bundles
- KEP-5793 Manifest-based admission: https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/5793-manifest-based-admission-control-config
- KEP-2570 Memory QoS: https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2570-memory-qos
- KEP-4192 Storage Version Migration: https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/4192-svm-in-tree
- KEP-5541 PVC last-used time: https://github.com/kubernetes/enhancements/tree/master/keps/sig-storage/5541-pvc-last-used-time-status-field
- KEP-5808 Native histograms: https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/5808-native-histogram-support
- KEP-5823 Pod checkpoint/restore: https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/5823-pod-level-checkpoint-restore
- KEP-1710 SELinux mount: https://github.com/kubernetes/enhancements/tree/master/keps/sig-storage/1710-selinux-relabeling
- KEP-5495 IPVS deprecation: https://github.com/kubernetes/enhancements/tree/master/keps/sig-network/5495-deprecate-ipvs-mode-in-kube-proxy
- KEP-5573 cgroup v1 removal: https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/5573-remove-cgroup-v1
If this saved you a read through 60+ KEPs, a clap or a follow helps. And if you hit something during your 1.37 upgrade that isn’t on this list, tell me in the comments. I’ll add it.










