Kubernetes v1.37 ("Garhwal") shipped on 26 August 2026 and swapped the kubelet's embedded cAdvisor for the leaner github.com/google/cadvisor/lib module in PR #139870. Eighteen cAdvisor flags went with it, --housekeeping-interval survived, and a kubelet handed any of the other eighteen exits at argument parsing with failed to parse kubelet flag: unknown flag: --containerd. Three metric families disappeared in the same change, and a separate fix inverted what eventRecordQPS: 0 means.

Most upgrade advice still names the older removals, --network-plugin (gone in 1.24) and --pod-infra-container-image (gone in 1.34), which is a different flag set. The cAdvisor list exists in the 1.37 changelog and the PR body, and almost nowhere else yet. These eleven checks are for whoever owns node images, kubeadm-flags.env, a Cluster API KubeadmConfigTemplate, or a systemd drop-in. If a node in your fleet already refuses to start and the journal blames cgroup v1 on a v2 host, settle that first, because the flag failure below leaves a completely different line in the log. This is also only the boot half of the upgrade: the admission half, static pods with secretRef no longer being admitted, hits after the node is already up.

Stale flag in kubeadm-flags.envor systemd drop-inkubelet 1.37 parses argvFlag in theremoved set?Node registersfailed to parse kubelet flag:unknown flagkubelet exits, no Node objectis ever createdNotReady alert never firesautoscaler replaces the instance"no""yes"

The checks

  1. Grep for the actual eighteen, not the flags the upgrade posts list. PR #139870 removes --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 seven --storage-driver-* flags. Run this against a live node before you run it against the repo, since the repo does not know what the image bakes in:
   grep -rnE -- '--(application-metrics-count-limit|boot-id-file|container-hints|containerd|containerd-namespace|enable-load-reader|event-storage-(age|event)-limit|global-housekeeping-interval|log-cadvisor-usage|machine-id-file|storage-driver-[a-z-]+)' \
     /var/lib/kubelet/kubeadm-flags.env /etc/systemd/system/kubelet.service.d/ /etc/default/kubelet
  1. Read argv from /proc, because the unit file is one of three inputs. systemd composes the packaged unit, the drop-in and kubeadm-flags.env into one command line, so reading kubelet.service alone under-reports what the process actually receives. The running process answers without ambiguity:
   tr '\0' '\n' < /proc/"$(pgrep -x kubelet)"/cmdline
  1. Diff that argv against the 1.37 binary's own --help and let the shell name the fatal flags. No memory of which release dropped what is involved, and the same three commands keep working in 1.38:
   kubelet --help 2>&1 | grep -oE '^ +--[a-z0-9-]+' | tr -d ' ' | sort -u > /tmp/flags-137
   tr '\0' '\n' < /proc/"$(pgrep -x kubelet)"/cmdline | grep -oE '^--[a-z0-9-]+' | sort -u > /tmp/flags-node
   comm -23 /tmp/flags-node /tmp/flags-137

Empty output means this node boots. Anything printed is a guaranteed unknown flag on upgrade day.

  1. Keep --housekeeping-interval. It is the single cAdvisor flag the PR retains, and a bulk "delete every cAdvisor flag" sweep takes it along with the dead ones. Dropping it does not fail the node, it returns container stats collection to the default cadence, which moves CPU and memory sampling under everything that scrapes /metrics/cadvisor.
  2. Do not "fix" --containerd by pointing it at the CRI endpoint. cAdvisor's --containerd and --containerd-namespace aimed cAdvisor's own stats client at a socket; the kubelet's runtime has always been --container-runtime-endpoint / containerRuntimeEndpoint. Moving the old value across converts a node that fails loudly at boot into one that fails later at pod sandbox creation, which costs you an afternoon instead of a minute. Delete the flag and change nothing else.
  3. Set eventRecordQPS explicitly, whatever it currently is. PR #117119 landed in 1.37 and made 0 mean unlimited, matching the field's documentation for the first time; before 1.37 the zero value fell back to the default. The changelog's instruction is blunt: if you set it to 0 and want the old behaviour, set it to 50. Anyone who wrote 0 believing it disabled event recording now gets an unthrottled event writer on every node, pointed at the API server.
   grep -rn 'eventRecordQPS' /var/lib/kubelet/config.yaml
  1. Treat the three deleted metric families as lost alert coverage. container_cpu_load_average_10s, container_cpu_load_d_average_10s and container_tasks_state no longer come out of /metrics/cadvisor. A PromQL rule over a series that stopped existing does not error, it returns no data and never fires again, which is the failure mode you discover months later. Find the references first, then give each surviving rule an absent() companion:
   grep -rn 'container_cpu_load_average_10s\|container_cpu_load_d_average_10s\|container_tasks_state' \
     ./prometheus-rules/ ./grafana-dashboards/
  1. Audit anything that reads userDefinedMetrics from /stats/summary. The same PR removed cAdvisor's application and custom metrics surface, so the container_application_* families and the userDefinedMetrics field are gone. Custom autoscalers and chargeback exporters that walk /stats/summary will parse a response missing a key they assume is always present, and that usually arrives as a nil dereference rather than a message naming the field.
  2. Migrate kubeadm config with a 1.36 binary, before 1.37 is anywhere near the machine. PR #136016 removed the v1beta3 API, deprecated since v1.31, along with the PublicKeysECDSA feature gate that existed only for v1beta3 compatibility. The 1.37 binary cannot read the file it would need to convert, so the conversion has to run on the older one:
   kubeadm version          # must still be 1.36.x
   kubeadm config migrate --old-config kubeadm.yaml --new-config kubeadm-v1beta4.yaml

Stored cluster config counts as well as the file in git. External-etcd clusters carrying a kubeadm.k8s.io/v1beta3 document are a documented 1.37 breakage.

  1. Rename the scheduling feature gates in the same pass. PR #139520 removed GangScheduling and WorkloadAwarePreemption and folded both into GenericWorkload. An unrecognized gate is fatal to the component reading it, so a control-plane static manifest still carrying the old name produces an API server or scheduler that will not start, which beats losing one node by a wide margin:
    grep -rn 'GangScheduling\|WorkloadAwarePreemption\|AnyVolumeDataSource' /etc/kubernetes/manifests/

AnyVolumeDataSource belongs in that grep for the same reason: the 1.37 changelog lists its removal under PR #135336, after it had been locked on since v1.33.

  1. Canary on a pool you can afford to lose, and alarm on instance churn instead of node conditions. A kubelet that exits at flag parsing never creates a Node object, so NotReady never appears, no node-condition alert fires, and the autoscaler keeps replacing the instance in a loop that looks healthy from the control plane. Machine count is the signal that moves, and the journal on one held instance is the confirmation:
    journalctl -u kubelet -b --no-pager | grep -m1 'failed to parse kubelet flag'

On Cluster API, roll a new KubeadmConfigTemplate rather than editing the existing one, and hold the first machine with a cluster.x-k8s.io/paused annotation so there is something left to read.

Wrap-up

Delete these tripwires when every pool reports 1.37 and comm -23 returns empty, which is a condition rather than a date. The habit worth keeping past that point is check 3. I would not trust any published list of removed flags, this one included, over a binary sitting on disk that answers the question directly: kubelet --help on the version you are about to ship costs one command and stays correct through 1.38.

Two other 1.37 defaults bite without any config change on your side. SELinuxMount going GA stalls shared PVCs, and the release announcement for Kubernetes v1.37 is worth a read for the enabled-by-default list before you schedule the window.