Upgrading to Kubernetes 1.37: What Breaks and What to Check First

Quick answer
Kubernetes 1.37 turns SELinux volume relabeling on by default, refuses to start the kubelet if it finds deprecated cAdvisor flags, and drops scheduling.k8s.io/v1alpha2 outright — a change you have to make before the upgrade, not after. Here's the pre-upgrade audit, ordered by blast radius, with the commands to find out whether you're affected.
- 1. SELinuxMount is GA and enabled by default
- 2. The kubelet now refuses to start on deprecated cAdvisor flags
- 3. scheduling.k8s.io/v1alpha2 is removed — clean up first
- 4. eventRecordQPS: 0 now means unlimited
- 5. kube-proxy: the mode warning arrives, and IPVS gets its gate
12 min read · Kubernetes
Kubernetes 1.37 shipped upstream on August 26, 2026. The release notes open with an unusually blunt heading — "(No, really, you MUST read this before you upgrade)" — and four entries under it. That section is the whole story of this upgrade.
Two of those four can take a node or a workload down, and one of them has to be dealt with before you upgrade, because the API version it removes stops being reachable the moment the control plane rolls. This post is the pre-upgrade audit, ordered by blast radius.
Run these five checks before you schedule anything:
1# 1. Is SELinux enforcing on your nodes? (SELinuxMount is now GA and on by default)
2kubectl get nodes -o name | while read -r node; do
3 echo "== $node"
4 kubectl debug "$node" -it --image=busybox --profile=sysadmin -- \
5 chroot /host getenforce 2>/dev/null || echo " (could not read)"
6done
7
8# 2. Any deprecated cAdvisor flags in your kubelet config? (the kubelet now refuses to start)
9grep -rE -- '--(application-metrics-count-limit|boot-id-file|container-hints|containerd|containerd-namespace|enable-load-reader|event-storage-age-limit|event-storage-event-limit|global-housekeeping-interval|log-cadvisor-usage|machine-id-file|storage-driver-[a-z]+)' \
10 /etc/systemd/system/kubelet.service.d/ /var/lib/kubelet/ 2>/dev/null
11
12# 3. Any scheduling.k8s.io/v1alpha2 objects left? (must be gone BEFORE you upgrade)
13kubectl get --raw /apis/scheduling.k8s.io/v1alpha2 2>/dev/null \
14 && echo "v1alpha2 still served — enumerate and delete its objects first"
15
16# 4. Are you relying on eventRecordQPS: 0 to mean "rate limited"? (it now means unlimited)
17grep -r "eventRecordQPS" /var/lib/kubelet/config.yaml 2>/dev/null
18
19# 5. Is kube-proxy running without an explicit mode? (it will start warning)
20kubectl -n kube-system get configmap kube-proxy -o yaml | grep -A1 "mode:"If all five come back empty or unsurprising, this upgrade is boring — the best kind. If not, here is what each hit means.
1. SELinuxMount is GA and enabled by default
This is the one that can break running workloads, and it only affects you if SELinux is enforcing on your nodes — which it is by default on RHEL, Fedora CoreOS, and several hardened node images.
The short version: Kubernetes now applies SELinux labels to volume mounts using -o context= mount options far more broadly than before, instead of recursively relabeling file contents. That is a large performance win on big volumes, and it is also a behaviour change. Where two Pods with different SELinux contexts share the same volume, the mount-option approach cannot satisfy both, and the second Pod will fail to start rather than silently getting the wrong label.
The failure is loud, which is the good news — you get an event on the Pod rather than a mysterious permission error inside the container. The bad news is that you find out at scheduling time, in production, unless you look first.
Upstream published a dedicated guide to identifying affected workloads while still on 1.36, which is the right time to read it. The pattern to hunt for is any volume mounted by two Pods on the same node whose securityContext.seLinuxOptions differ — or where some Pods set seLinuxOptions and others do not. Note that this is not only a ReadWriteMany problem: a ReadWriteOnce volume shared by Pods co-located on one node hits it too, and that is the more common shape. If you were using subPath to let differently-labelled Pods share a volume, that workaround is exactly what stops working.
If you find affected workloads and cannot fix them before the upgrade window, the feature gate is still there to opt out — but it is GA, so treat that as a stay of execution rather than a plan.
Clusters running without SELinux can ignore this entirely.
2. The kubelet now refuses to start on deprecated cAdvisor flags
This one is easy to miss because it is filed under "Dependency" rather than in the urgent section, and its blast radius is a node that does not come back.
The kubelet's embedded cAdvisor moved to a leaner module, and the long-deprecated flags that came with the old one are no longer merely ignored — the kubelet fails to start if any are set. Only --housekeeping-interval survives. The removed set includes --containerd, --containerd-namespace, --boot-id-file, --machine-id-file, --container-hints, --global-housekeeping-interval, --enable-load-reader, --log-cadvisor-usage, --application-metrics-count-limit, --event-storage-age-limit, --event-storage-event-limit, and the whole --storage-driver-* family.
These flags have been dead weight for years, which is exactly why they survive in node configuration nobody has read recently — golden AMIs, Ansible roles, launch templates copied forward across three cluster generations. Check the config, not your memory of it. Check 2 in the audit above greps the usual locations.
Because this is a node-level failure, it surfaces as nodes going NotReady one at a time as you roll them, and it will happily take out an entire node group if you let the rollout continue. Upgrade one node first and watch it come back before proceeding.
The same change also removes three metric series and one stats field:
container_cpu_load_average_10scontainer_cpu_load_d_average_10scontainer_tasks_stateuserDefinedMetricsin/stats/summary, and the customcontainer_application_*families in/metrics/cadvisor
Nothing breaks if you scrape these — the series simply stop existing, which means dashboards go blank and any alert built on them stops firing rather than firing. A silent alert is worse than a broken one. Grep your recording rules and alert definitions for those names before you upgrade, not after someone notices a panel is empty.
3. scheduling.k8s.io/v1alpha2 is removed — clean up first
The scheduling.k8s.io API group moved from v1alpha2 to v1alpha3, and v1alpha2 was dropped entirely rather than deprecated. The release note is explicit: remove all v1alpha2 objects from the API server before performing the update.
This is the ordering trap. Once the control plane is on 1.37, the old version is no longer served, and objects stored under it are awkward to reach — you are into etcdctl territory, not kubectl. Five minutes of cleanup beforehand replaces an unpleasant afternoon afterwards.
The underlying API change is that DisruptionMode went from an enum field to a struct, so future options can be added without another breaking change. If you are using alpha scheduling APIs at all you are almost certainly doing so deliberately, and you know where those objects live. If check 3 above returns nothing, you are clear.
Kubernetes Production Readiness Checklist
The pre-launch checks we run before calling a cluster production-ready — probes, resources, RBAC, upgrades, and backups. Plain Markdown you can commit to your repo.
Free. Instant download. You'll also get the occasional deep-dive from the newsletter — unsubscribe anytime.
4. eventRecordQPS: 0 now means unlimited
A quiet one that inverts a default. The kubelet's eventRecordQPS setting historically treated 0 as "use the built-in rate limit". It now treats 0 as unlimited, matching what the documentation always claimed.
If you explicitly set eventRecordQPS: 0 believing it was a conservative choice, you have just removed the rate limit on event creation from every kubelet in the fleet. On a large or flapping cluster, that is a lot of writes pointed at the API server and etcd at exactly the moment you least want them.
The fix is to set a real number. Upstream suggests 50 as a starting point, which matches the old default behaviour closely enough for most clusters.
5. kube-proxy: the mode warning arrives, and IPVS gets its gate
Two related changes, neither breaking in 1.37.
kube-proxy now warns when started without an explicitly specified mode, because the default on Linux will switch from iptables to nftables in a future release. kubeadm correspondingly sets mode: iptables explicitly when you have not chosen one. Nothing changes yet — but the warning is the notice period, and the cluster that "just uses the default" is the one that gets surprised when the default moves. Set the mode explicitly now and the future switch becomes a decision you make rather than one that happens to you.
The KubeProxyIPVS feature gate landed, exactly on the schedule we described when 1.36 shipped. KEP-5495 sets out the rest of it precisely: the gate arrives in 1.37 as Default: true; in 1.40 it flips to Default: false, and from then on starting kube-proxy in ipvs mode without explicitly overriding the gate makes it exit with an error listing the valid modes; in 1.43 the gate locks and pkg/proxy/ipvs is deleted outright; the gate itself is removed in 1.46. IPVS mode has been deprecated since 1.35 and nftables has been GA since 1.33, so on any modern kernel the migration target is nftables.
If you are still on IPVS, nothing breaks on this upgrade. It is simply the last release where you can defer the question cheaply. There is also a genuine IPVS performance fix in 1.37 — syncProxyRules no longer issues one netlink dump per interface, which took tens of seconds on clusters with many Services — so the mode you are trying to leave got faster on the way out.
6. New defaults worth knowing (not breaking, but check)
- HPA scale-to-zero is on by default.
minReplicas: 0now works without enabling a feature gate — the other half of the 1.36 prediction. If you run KEDA or a similar autoscaler alongside HPA, be deliberate about which one owns scaling to zero, because both now can. - Pod Certificates went GA, with
PodCertificateRequestenabled by default — workload identity issued by the cluster, without a sidecar. - ClusterTrustBundle and ClusterTrustBundleProjection went GA and are enabled by default, which is the distribution half of the same story.
- Pod hostname overrides went GA and the gate is locked on.
- StorageVersionMigration went GA;
storagemigration.k8s.io/v1is served by default. MaxUnavailableStatefulSetis now enabled by default — faster StatefulSet rollouts, and a behaviour change if you were relying on strictly one-at-a-time updates.kubectl get -o kyamlis stable. A YAML output mode designed to be unambiguous about strings and numbers, which is more useful than it sounds the first time a version string gets parsed as a float.kubectl run --filename/-fis deprecated. It never did anything; it is now formally on the way out.- etcd's default version is now v3.7.0. Worth noting if you pin etcd separately from the control plane.
The upgrade itself: nothing new, same discipline
Control plane before nodes, one minor version at a time, node groups in waves with a real soak between them. 1.37 does not change the supported version skew, and none of the changes above alter the sequencing — they change what you audit beforehand.
The one adjustment specific to this release: because the cAdvisor flag change is a node start failure, put a single canary node through the upgrade and confirm it returns Ready before you let a node group roll. That check costs ten minutes and is the difference between one failed node and a drained node group with nothing to schedule onto.
Quick reference
| Change | Breaks | When to act |
|---|---|---|
SELinuxMount GA, default on | Pods sharing volumes across differing SELinux contexts | Audit on 1.36, before upgrading |
| cAdvisor flags rejected | kubelet fails to start | Before rolling any node |
| cAdvisor metrics removed | Dashboards and alerts silently go blank | Before upgrading |
scheduling.k8s.io/v1alpha2 removed | Objects unreachable after control plane upgrade | Before the control plane upgrade |
eventRecordQPS: 0 = unlimited | API server and etcd write load | Before upgrading |
| kube-proxy mode warning | Nothing yet — default changes later | Set mode explicitly now |
KubeProxyIPVS gate added | Nothing until 1.40 | Plan the nftables migration |
| HPA scale-to-zero default on | Autoscaler ownership overlap | Review if you also run KEDA |
Frequently Asked Questions
Do I have to do anything about SELinux if my nodes do not use it?
No. If SELinux is not enabled on your nodes, the SELinuxMount graduation has no effect on you and you can skip that entire section. Check 1 in the audit above tells you which case you are in.
Will the kubelet really fail to start, or just log a warning?
Fail to start. The deprecated cAdvisor flags used to be accepted and ignored; in 1.37 they are rejected. This is why it is worth upgrading a single node and watching it rejoin before rolling a whole group.
Can I clean up scheduling.k8s.io/v1alpha2 objects after upgrading?
Not easily, which is why the release note says to do it first. Once 1.37 stops serving v1alpha2, those objects are no longer reachable through kubectl and you are working directly against etcd to remove them.
Is IPVS mode removed in 1.37?
No. 1.37 only adds the KubeProxyIPVS gate, defaulted on. The mode still works and is still selectable. In 1.40 the gate defaults off — kube-proxy will refuse to start in ipvs mode unless you override it — and in 1.43 the implementation is deleted. The deadline is real but not immediate.
Does 1.37 change the supported version skew or upgrade path?
No. Standard skew rules apply: upgrade the control plane first, keep kubelets within the supported window, and move one minor version at a time.
See also
- Upgrading to Kubernetes 1.36: What Breaks and What to Check First — the previous release's audit, and where the IPVS clock started
- Kubernetes Upgrade Strategy: Zero-Downtime Cluster Upgrades — the process this checklist feeds into
- Zero-Downtime Deployments with Rolling Updates and Readiness Probes — the workload-level half of surviving a node roll
- Hardening Pods with Seccomp and AppArmor Profiles — the neighbouring controls to the SELinux change above
Planning a fleet-wide Kubernetes upgrade and want a second pair of eyes on the sequencing? Get in touch.
Official References
- CHANGELOG-1.37 — the authoritative list of what actually changed, including the urgent upgrade notes quoted above
- Breaking changes in SELinux volume labeling — how to find affected workloads while still on 1.36
- KEP-5495: kube-proxy IPVS deprecation — the removal schedule
- Virtual IPs and Service Proxies — what the proxy modes actually do
- Deprecated API migration guide — which APIs disappear in which release
Was this article helpful?
Be the first to rate this article
Related Topics
Found this useful? Share it.


