Skip to main content
DevOps Insightsguides & answers

Kubernetes upgrades, 1.34 to 1.37: what breaks

The official deprecation guide lists removed APIs up to v1.32, and API checkers look at apiVersions. Since then, upgrades break on kubelet flags, node settings and changed behaviour, which an API checker cannot see. This page lists each one, with the check to run before you upgrade.

Checked against release notes, changelogs and source · 19 October 2026

On this page · 6 sections

How to use this page

Read every section after the version you run, up to the one you are going to. Kubernetes upgrades one minor version at a time, so going from 1.33 to 1.36 means meeting everything listed for 1.34, 1.35 and 1.36.

Changes by release and how much they hurt
Upgrading tokubelet will not startworkloads failno error, wrong resultwarning now, removal later
1.342–––
1.352––1
1.36–111
1.371111

What “checked” means here. Every change, error message and command below was traced to a Kubernetes release post, a changelog entry, the documentation or the source code at the release tag, each linked. None of it was run on a cluster for this page, so where an exact message could only be predicted from the source, the page says so. Error text keeps the source’s placeholders, such as %s, where Kubernetes fills in a value.

Upgrading to 1.34

kubelet will not start

Static Pods that reference API objects are refused

Affects: Anyone with static Pod manifests, which includes every kubeadm control plane, if a manifest uses a Secret, ConfigMap, ServiceAccount or similar.

From 1.34 the kubelet denies admission to static Pods that reference API objects, so they can no longer run silently while their mirror Pod fails. 1.37 removed the PreventStaticPodAPIReferences feature gate that let you opt out. If a control-plane manifest in /etc/kubernetes/manifests is affected, that component does not come back after the kubelet upgrade.

What you see

static pods may not reference %s

From kubelet source, pkg/kubelet/config/common.go at v1.37.0; %s is the kind, for example configmaps (via configmap volumes).

Check before upgrading

sudo grep -nE 'serviceAccountName|secretKeyRef|configMapKeyRef|secretRef|configMapRef|configMap:|secret:|persistentVolumeClaim:|projected:|csi:|ephemeral:|resourceClaims' /etc/kubernetes/manifests/*.yaml

Any match. These are the references the kubelet checks: service accounts, Secrets and ConfigMaps (env or volumes), PVCs, CSI and ephemeral volumes, projected volumes and resource claims. Use your kubelet's staticPodPath if it is not the kubeadm default.

Fix

  • Move the referenced data onto the node and mount it with hostPath, as kubeadm's own manifests do for certificates and kubeconfigs.
  • Or run the workload as a normal Pod, DaemonSet or Deployment, which may reference API objects.

Sources: CHANGELOG-1.34: static pods that reference API objects denied (#131837) · Kubernetes v1.37 release post: PreventStaticPodAPIReferences removed

kubelet will not start

kubelet flag --cloud-config removed

Affects: Nodes whose kubelet is still started with --cloud-config, usually from the in-tree cloud provider days.

The deprecated flag was removed from the kubelet command line. A kubelet started with it exits instead of ignoring it.

What you see

failed to parse kubelet flag: %w

From kubelet source, cmd/kubelet/app/server.go at v1.35.0, wrapping pflag's "unknown flag: --%s". Expected combined output, not captured: failed to parse kubelet flag: unknown flag: --cloud-config.

Check before upgrading

ps -o args= -C kubelet | tr ' ' '\n' | grep -- '--cloud-config'

Any output: the running kubelet has the flag. Remove it from the unit file, drop-in or env file it came from before upgrading.

Fix

  • Delete the flag. Cloud integration belongs to the external cloud-controller-manager.

Source: CHANGELOG-1.34: kubelet --cloud-config removed (#130161)

Upgrading to 1.35

kubelet will not start

The kubelet refuses to start on cgroup v1 nodes

Affects: Nodes on older distributions, or with cgroup v1 forced on the kernel command line. Common on long-lived VMs and some edge devices.

failCgroupV1 defaults to true from 1.35, so the kubelet stops on a cgroup v1 host unless you set it to false. kubeadm's SystemVerification preflight check also turns this into an error for kubelet 1.35 and newer. The 1.37 release post confirms the default still applies.

What you see

kubelet is configured to not run on a host using cgroup v1. cgroup v1 support is unsupported and will be removed in a future release

From kubelet source, pkg/kubelet/apis/config/validation/validation_linux.go at v1.35.0.

Check before upgrading

stat -fc %T /sys/fs/cgroup/

cgroup2fs means cgroup v2 and nothing to do. tmpfs means cgroup v1: this node will not run a 1.35 kubelet as configured.

Fix

  • Move the node to cgroup v2. This is the fix that lasts, because cgroup v1 support is being removed.
  • To keep cgroup v1 for now, set failCgroupV1: false in the kubelet configuration.
  • With kubeadm, the 1.35 changelog says to do two things: edit the kube-system/kubelet-config ConfigMap and add failCgroupV1: false before upgrading, and ignore the SystemVerification preflight error.

Sources: Kubernetes docs: About cgroup v2 (how to check the version) · CHANGELOG-1.35: failCgroupV1 and the kubeadm steps (#134744) · Kubernetes v1.35 release post

kubelet will not start

kubelet flag --pod-infra-container-image removed

Affects: Clusters that ever passed this flag to the kubelet, which kubeadm used to do by default.

The flag was removed (marked ACTION REQUIRED). kubeadm stopped writing it and tries to remove it from /var/lib/kubelet/kubeadm-flags.env during upgrade. It does not remove it from extraArgs you passed yourself, and on non-kubeadm nodes it is yours to remove.

What you see

failed to parse kubelet flag: %w

From kubelet source, cmd/kubelet/app/server.go at v1.35.0, wrapping pflag's "unknown flag: --%s". Expected combined output, not captured: failed to parse kubelet flag: unknown flag: --pod-infra-container-image.

Check before upgrading

sudo grep -rn -- '--pod-infra-container-image' /var/lib/kubelet/kubeadm-flags.env /etc/default/kubelet /etc/sysconfig/kubelet /etc/systemd/system/kubelet.service.d/ 2>/dev/null

Any match is a kubelet that will not start on 1.35. Also check kubeletExtraArgs in the kubeadm configuration you upgrade with.

Fix

  • Remove the flag from every place it is set, then upgrade the kubelet. The sandbox image is configured in the container runtime now.

Source: CHANGELOG-1.35: --pod-infra-container-image removed (#133779) and kubeadm cleanup (#133778)

warning now, removal later

kube-proxy ipvs mode is deprecated

Affects: Clusters running kube-proxy in ipvs mode.

1.35 deprecated ipvs mode in favour of nftables. As of 1.37 the warning gives dates: disabled by default in 1.40, removed in 1.43. Nothing breaks yet; the migration is the work.

What you see

The ipvs proxier has been deprecated and will be disabled by default in Kubernetes 1.40 and removed in Kubernetes 1.43. Migrate to the 'nftables' proxier instead.

From kube-proxy source, cmd/kube-proxy/app/server_linux.go at v1.37.0 (1.35 and 1.36 print an earlier wording).

Check before upgrading

kubectl -n kube-system get configmap kube-proxy -o yaml | grep -n 'mode:'

mode: ipvs (kubeadm-managed kube-proxy). An empty mode means the default, which is not ipvs.

Fix

  • Plan the move to mode: nftables and test it on one cluster first. The 1.35 release post links KEP-5495 for the reasoning.

Sources: Kubernetes v1.35 release post: deprecation of ipvs mode · Kubernetes v1.37 release post: the timeline to 1.43

Upgrading to 1.36

less urgent than it looks

containerd 1.7 still works on 1.36 and 1.37

Affects: Anyone holding back an upgrade because the release posts said containerd 1.x support ends in 1.36.

The 1.34 and 1.35 posts said 1.35 was the last release to support containerd 1.x. The changelogs then moved the deadline twice: 1.36 deferred the related kubelet removal to 1.37 (#136846), and 1.37 deferred it to 1.38 (#139121), "to align with containerd v1.7 support". Upgrading containerd is still the right plan; it is not yet a blocker for 1.36 or 1.37.

What you see

CRI implementation should be updated to support RuntimeConfig. Falling back to using cgroupDriver from kubelet config.

From kubelet source, pkg/kubelet/kubelet.go at v1.36.0 and v1.37.0, logged at info level. The kubelet metric kubelet_cri_losing_support also reports the version in which your runtime loses support..

Check before upgrading

kubectl get nodes -o wide

The CONTAINER-RUNTIME column. containerd://1.7.x means the warning above applies; plan containerd 2.x before 1.38.

Fix

  • Upgrade containerd to 2.x on your own schedule, before 1.38, and re-check the 1.38 notes when they are published.

Sources: CHANGELOG-1.36: deferral to 1.37 (#136846) · CHANGELOG-1.37: deferral to 1.38 (#139121)

workloads fail

gitRepo volumes are gone, and the error gives advice that no longer works

Affects: Pods that mount a gitRepo volume. Rare, but nothing warns you at apply time.

1.33 disabled the gitRepo driver by default; from 1.36 it is permanently disabled and cannot be turned back on, and the docs say 1.37 does not include it at all. The API server still accepts such Pods, so they fail on the node, not at kubectl apply. The kubelet's message still says to enable the GitRepoVolumeDriver feature gate, which 1.36 locked off.

What you see

git-repo volume plugin has been disabled; if necessary, it may be re-enabled by enabling the feature-gate `GitRepoVolumeDriver`

From kubelet source, pkg/volume/git_repo/git_repo.go at v1.36.0. On 1.36 and later the advice in it cannot be followed..

Check before upgrading

kubectl get pods -A -o json | jq -r '.items[] | select(any(.spec.volumes[]?; has("gitRepo"))) | "\(.metadata.namespace)/\(.metadata.name)"'

Any Pod listed (needs jq). Check the templates of Deployments, StatefulSets and CronJobs too, since a scaled-to-zero workload has no Pods to find.

Fix

  • Do what the docs suggest: mount an emptyDir into an init container that clones the repository with git, then mount the same emptyDir into the app container.
  • To stop new ones, the docs give this CEL expression for a ValidatingAdmissionPolicy: !has(object.spec.volumes) || !object.spec.volumes.exists(v, has(v.gitRepo))

Sources: Kubernetes docs: Volumes, gitRepo (disabled) · Kubernetes v1.36 release post: removal of the gitRepo volume driver

no error, wrong result

A controller-manager metric was renamed

Affects: Anyone with a dashboard or alert on volume operation errors.

kube-controller-manager's volume_operation_total_errors became volume_operation_errors_total (marked ACTION REQUIRED). An alert on the old name does not error; it simply never fires again.

Check before upgrading

grep -rn 'volume_operation_total_errors' <your dashboards and alert rules>

Any use of the old name. Replace it with volume_operation_errors_total.

Fix

  • Rename the metric in queries, recording rules and alerts, and check that the alert can still fire.

Source: CHANGELOG-1.36: metric renamed (#136399)

warning now, removal later

Service spec.externalIPs is deprecated

Affects: Services that set spec.externalIPs.

Every apply of such a Service now prints a warning. The deprecation post says kube-proxy will later disable externalIPs with a way to opt back in, and that support goes completely no earlier than v1.43.

What you see

spec.externalIPs is deprecated and may no longer be implemented in some clusters

From API server source, pkg/api/service/warnings.go at v1.36.0.

Check before upgrading

kubectl get svc -A -o jsonpath='{range .items[?(@.spec.externalIPs)]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}'

Any Service listed.

Fix

  • Move those Services to type LoadBalancer or a Gateway, as the deprecation post describes.

Source: Kubernetes blog: deprecation and removal of Service externalIPs

Upgrading to 1.37

kubelet will not start

Deprecated cAdvisor flags stop the kubelet

Affects: Kubelets still started with old cAdvisor tuning flags.

The kubelet no longer accepts these flags and fails to start if any is set. Only --housekeeping-interval is kept. The list: --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 and the --storage-driver-* flags.

Check before upgrading

ps -o args= -C kubelet | tr ' ' '\n' | grep -E -- '--(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-)'

Any output. Remove the flag where it is set before upgrading the kubelet.

Fix

  • Delete the flags. They have done nothing useful for several releases.

Source: CHANGELOG-1.37: deprecated cAdvisor flags no longer accepted

workloads fail

SELinuxMount is on: Pods that share a volume with different SELinux labels conflict

Affects: Clusters whose nodes run SELinux in enforcing mode (common on RHEL-family nodes) with volumes shared by Pods that have different SELinux labels.

SELinuxMount went GA and is on by default (marked ACTION REQUIRED). Volumes are mounted with the Pod's SELinux context instead of being relabelled file by file. Two Pods with different labels sharing a volume on one node can no longer both use it, so one waits in ContainerCreating.

What you see

conflicting SELinux labels of volume %s: %q and %q

From kubelet source, pkg/kubelet/volumemanager/cache/desired_state_of_world.go at v1.37.0.

Check before upgrading

--controllers=*,selinux-warning-controller

Add this to kube-controller-manager's flags while still on 1.36. Then read the metric selinux_warning_controller_selinux_volume_conflict: it lists every conflicting pair. The kubelet metric volume_manager_selinux_volume_context_mismatch_warnings_total counts Pods that would really fail.

Fix

  • For each conflicting workload, set spec.securityContext.seLinuxChangePolicy: Recursive to keep the old behaviour, or remove the conflict.
  • The official post walks through this path step by step; follow it on 1.36 before upgrading.

Sources: Kubernetes blog: breaking changes in SELinux volume labeling · CHANGELOG-1.37: SELinuxMount GA (#139956)

no error, wrong result

eventRecordQPS: 0 now means unlimited

Affects: Kubelets configured with eventRecordQPS: 0.

0 is now treated as no rate limit, matching the documentation (marked ACTION REQUIRED). A kubelet that relied on the old behaviour can now send far more events to the API server.

Check before upgrading

sudo grep -n 'eventRecordQPS' /var/lib/kubelet/config.yaml

eventRecordQPS: 0. The changelog suggests an explicit value such as 50 if you want the previous behaviour.

Fix

  • Set an explicit non-zero value, for example 50, before upgrading.

Source: CHANGELOG-1.37: eventRecordQPS handling (#117119)

warning now, removal later

kube-dns is deprecated

Affects: Clusters still running kube-dns instead of CoreDNS.

CoreDNS has been the default since 1.13. The 1.37 post deprecates kube-dns and says no new packages are expected after v1.40. Nothing stops working on 1.37.

Check before upgrading

kubectl -n kube-system get deploy kube-dns coredns

A Deployment named kube-dns means you are on kube-dns. A Service named kube-dns proves nothing: kubeadm keeps that name for CoreDNS too.

Fix

  • Migrate to CoreDNS.

Source: Kubernetes v1.37 release post: deprecation of kube-dns

Whatever your version

warning now, removal later

Ingress NGINX was retired on 24 March 2026

Affects: Any cluster running the ingress-nginx controller, whatever its Kubernetes version.

SIG Network and the Security Response Committee retired the project: no further releases, no bug fixes and no security updates. Existing installs keep working, and charts and images stay available. The risk is the next CVE, and the migration itself: other controllers handle regex, rewrites, trailing slashes and URL normalisation differently.

Check before upgrading

kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginx

Any Pod listed (the command the retirement announcement gives; run it with cluster-admin rights).

Fix

  • Migrate to Gateway API or another Ingress controller. Read the official "Before You Migrate" post first: its five behaviours are where migrated routes start returning 404s or redirecting.

Sources: Kubernetes blog: Ingress NGINX retirement · Kubernetes blog: five surprising Ingress-NGINX behaviors · Kubernetes v1.36 release post: retired on March 24, 2026

Upgraded and something is crash-looping anyway? Start with CrashLoopBackOff, or check a manifest with the Kubernetes manifest analyzer. Still on Ingress NGINX, which was retired in March 2026? Read migrating off Ingress NGINX before you move.

Sources

Every item above links its own sources; this is the full list.

Did this get you to an answer?

No text box on purpose — please do not paste production logs anywhere

Something wrong or out of date? Report it