Skip to main content

Kubernetes v1.37's Deprecation Sweep: The Migration Inventory a Cluster API Fleet Needs Before August 26

9 min readDora NodaDora Noda
Share
On this page

Kubernetes v1.37 is planned for Wednesday, August 26, 2026. The release team published its sneak-peek post on July 31 — which means that as of today, anyone running a self-hosted fleet has already burned a week of a roughly four-week head start. That window is the entire value of the sneak peek: it names every planned deprecation, removal, and breaking change while there is still time to audit and migrate calmly, instead of discovering them in the release notes the week you upgrade.

For a self-hosted PaaS fleet built on Cluster API — kubeadm-bootstrapped control planes, MachineDeployments rolling worker nodes on providers like Cluster API Provider Hetzner (CAPH) — here is the full inventory: everything v1.37 deprecates, removes, or changes in a breaking way, mapped against where a fleet like that actually hits it, with the audit command and the action for each.

The Inventory: Every v1.37 Change, Mapped to Your Fleet

ChangeStatus in v1.37Where a kubeadm/CAPH fleet hits itAction before upgrading
kubectl run --filename/-fDeprecatedCI scripts, runbooks, agent tooling that shells out to kubectl run -fGrep your repos; switch to kubectl apply -f
Static Pods referencing Secrets/ConfigMapsProhibited; PreventStaticPodAPIReferences opt-out gate removedHand-patched manifests in /etc/kubernetes/manifests on control-plane nodesGrep manifests for configMapRef/secretRef; refactor to files on disk
kube-proxy ipvs modeDeprecation warning at startup; default-off planned for v1.40, removal by v1.43Any cluster whose KubeProxyConfiguration still says mode: ipvsCheck the mode now; plan the move to nftables
cgroup v1 nodesKubelet refuses to start (failCgroupV1: true default since v1.35, removal planned per KEP-5573)Old node images in MachineDeployment templatesVerify every image is cgroup v2; rebuild stragglers
SELinuxMountGraduates to GA, enabled by default — a behavior changeOnly SELinux-enforcing node images (RHEL-family); Ubuntu/AppArmor fleets see no effectAudit shared volumes with mismatched SELinux labels; set seLinuxChangePolicy: Recursive where needed

Five rows. Two of them (kubectl run -f, static-pod refs) are ten-minute greps. One (ipvs) starts a multi-release clock. One (cgroup v1) can stop a kubelet from booting. One (SELinuxMount) is a no-op for most Hetzner-style fleets but a real pod-startup failure for the rest. The sections below walk each row with the exact commands.

The Quick Greps: kubectl run -f and Static Pod API References

The --filename/-f flag on kubectl run is being deprecated because the pod it generates is always built purely from CLI arguments like NAME and --image — the file never contributed what people assumed it did (kubernetes/kubernetes#138671). Nobody's production manifests depend on this, but automation does accumulate odd invocations. One search across your ops repos settles it:

bash
grep -rnE 'kubectl run .*(-f |--filename)' .

Anything that surfaces should have been kubectl apply -f all along.

The static-pod change is more interesting for a kubeadm fleet, because kubeadm control planes are static pods: etcd, kube-apiserver, kube-controller-manager, and kube-scheduler all run from manifests in /etc/kubernetes/manifests, materialized on disk by the bootstrap provider when Cluster API creates a control-plane machine. Static Pods were never meant to read API resources — they aren't created through the API server — but a bug allowed references to Secrets or ConfigMaps via fields like configMapRef or secretRef to slip through. v1.37 closes it strictly, and removes the PreventStaticPodAPIReferences feature gate that previously let you opt out (kubernetes/kubernetes#140226).

Stock kubeadm manifests don't use these fields, so an untouched CAPH control plane passes by default. The exposure is hand-patched manifests — an env-from-Secret bolted onto the apiserver manifest during an incident two years ago, or a custom static pod someone added for node-local tooling. Audit each control-plane node:

bash
grep -rn 'configMapRef\|secretRef' /etc/kubernetes/manifests/

With Cluster API, remember that anything you fixed by hand on a live node is one KubeadmControlPlane roll away from being recreated — the durable fix is in the KubeadmControlPlane patches or bootstrap config, not on the node. If a static pod genuinely needs configuration, put it in a file on the node's disk (via the bootstrap provider's file-writing mechanism) and mount it, rather than referencing API objects that were never supposed to resolve.

ipvs: The Deprecation Clock Starts Now

kube-proxy's ipvs mode arrived in v1.8 to escape iptables performance bottlenecks. The reason it's going away is structural, not cosmetic: the kernel ipvs API alone cannot fully implement Kubernetes Services, so ipvs mode has always used iptables underneath anyway — KEP-3866 put it memorably as "the ipvs mode of kube-proxy will not save us." The nftables backend is the sanctioned successor.

v1.37 itself breaks nothing. What it adds is a deprecation warning logged at kube-proxy startup for any cluster running mode: ipvs. The published timeline under KEP-5495:

  • v1.37 — deprecation warning on startup
  • v1.40ipvs mode expected to be disabled by default (still selectable via a feature gate)
  • v1.43 — support removed entirely

Three minor releases at Kubernetes's cadence is roughly a year. That is a comfortable migration window — if you start it now. Confirm what your fleet actually runs:

bash
kubectl -n kube-system get configmap kube-proxy \
  -o jsonpath='{.data.config\.conf}' | grep 'mode:'

An empty or iptables result means this row costs you nothing. If it says ipvs, the migration target is nftables, and the honest gotchas are worth knowing before you schedule it: nftables mode needs Linux 5.13+ and has no selectable load-balancing scheduler — if your fleet chose ipvs specifically for wlc or source-hashing, that knob does not survive the move. In a Cluster API fleet the change belongs in the KubeadmConfigTemplate's KubeProxyConfiguration, rolled node-by-node like any other machine change — which is exactly why you want the year, not the week.

cgroup v1: The Row That Stops a Kubelet From Booting

This one is listed under "ongoing major changes" rather than new deprecations, but it's the row with the sharpest failure mode: since v1.35, the kubelet's failCgroupV1 setting has defaulted to true, meaning the kubelet refuses to initialize on any node still running cgroup v1. Not degraded — down. A stale node image in one MachineDeployment template turns a routine v1.37 roll into replacement nodes that never join the cluster.

The per-node check is one command:

bash
stat -fc %T /sys/fs/cgroup
# "cgroup2fs" → cgroup v2, fine
# "tmpfs"     → cgroup v1, this node will not survive the upgrade

For a fleet on modern images this is usually a non-event — Ubuntu has defaulted to the unified cgroup v2 hierarchy since 21.10, Debian since 11 — but "usually" is exactly what an inventory exists to verify. The place to look is not the nodes you can see but the template that builds the next one: the machine image referenced by each MachineDeployment and KubeadmControlPlane, including that one pool nobody has rolled since it was created.

There is an escape hatch, and the sneak peek is explicit that it's short-term:

yaml
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
failCgroupV1: false # temporary override

Treat it as a bridge for the week you need to rebuild an image, not a policy. Support for cgroup v1 is planned for full removal under KEP-5573, and the resource-management features a multi-tenant platform actually wants — In-Place Pod Resizing, tiered memory protection — depend entirely on cgroup v2. An override that pins you to v1 quietly costs you the features that make dense bin-packing safe.

SELinuxMount Goes GA: Breaking, But Probably Not for You

The one item the sneak peek files under "breaking changes" is a graduation: SELinuxMount reaches GA and is enabled by default. Where a volume's CSI driver opts in (CSIDriver with seLinuxMount: true), volumes are mounted with -o context=<label> instead of being recursively relabeled file-by-file — a big win for pod startup latency on large volumes.

The break: a single mount holds exactly one SELinux context. Two pods with different SELinux labels sharing a volume on the same node — which previously coexisted, slowly, under recursive relabeling — may now fail to start. The escape hatch is per-workload: seLinuxChangePolicy: Recursive in the Pod spec restores the old behavior.

Most self-hosted fleets on Hetzner-style commodity nodes run Ubuntu or Debian images, where the LSM is AppArmor and SELinux is not enforcing — the sneak peek is explicit that clusters without SELinux see no effect at all. This row is real only for RHEL-family node images (Rocky, Alma, RHEL) with SELinux enforcing. If that's you, the audit is: find RWX volumes shared across pods, compare the pods' SELinux labels, and set the recursive policy on the mismatches before the upgrade sets it for you by failing the pod.

What v1.37 Gives Back

The sweep is the migration work; the same release also ships reasons to want it. Briefly, the three the sneak peek features:

  • metrics.k8s.io goes GA (KEP-5207) — after nearly nine years in beta, the API behind kubectl top and the Horizontal Pod Autoscaler is Stable. No functional changes; v1beta1 keeps working during the transition.
  • Rootless kubelet reaches beta (KEP-2033) — the kubelet can run inside a user namespace as an unprivileged user on the host. For anyone renting out multi-tenant compute, shrinking what a node-component CVE can reach is a structural win.
  • Volume Health Monitor restarts at alpha (KEP-1432) — four new CSI RPCs feed machine-readable storage health (Inaccessible, Degraded, …) into PersistentVolumeClaim.status.healthStatus, Pod.status.volumeHealth, and CSINode.status.storageHealth. Storage failures stop being "cross-reference the vendor dashboard" and become fields a remediation controller — or an AI agent operating the platform — can actually act on.

26 Days Is the Whole Point

Here's the arithmetic that makes the sneak peek worth reading the day it ships. July 31 to August 26 is 26 days. A Cluster API fleet doesn't upgrade in place — it rolls: new machines with the new version join, old machines drain and leave, one MachineDeployment at a time, with soak time in between if you're careful. Add image rebuilds for anything the cgroup check flagged, a staging-cluster pass, and the ordinary queue of production changes, and 26 days is not slack — it's roughly the minimum for doing this without compressing anything.

The counterfactual just happened. v1.36 turned two long-warned deprecations into hard failures — gitRepo volumes and the previous stage of the kube-proxy transition — and the fleets that hurt were the ones that met those changes in the release notes during upgrade week, with the audit, the fix, and the roll all crammed into the same sprint. Same facts, same commands, entirely different experience — the only variable was when the operator learned them.

That's the discipline this inventory encodes: read the sneak peek the day it ships, run the five audits the same week, and the v1.37 upgrade becomes what it should be — a boring roll. Every command above runs read-only against a live fleet; the whole audit is an afternoon. The scramble version of the same work costs a lot more than an afternoon, and you pay it at the worst possible time.


Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on Kubernetes machines you own, managed by Cluster API. 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