Originally published on kuryzhev.cloud
A platform team has a Friday maintenance window, a cluster still on 1.36, and a ticket that says "get us on current before the next audit." Planning the Kubernetes 1.37 upgrade is mostly about finding the three or four things in your cluster that the new minor version can break, not about the headline features.
Kubernetes 1.37 was released on 2026-08-26 (see the official releases page), and the latest patch is 1.37.2. That matters now because the first patches are out. Check whether your managed provider offers 1.37 in your channel yet. The ingress-nginx retirement is also already behind you, so any cluster still relying on it carries unpatched risk into this upgrade.
Short answer: upgrade one minor at a time (1.36 to 1.37, never skipping), and start with 1.37.2 rather than 1.37.0. Before touching anything, scan for deprecated API usage, take an etcd snapshot, and check that your add-ons support 1.37. If you run ingress-nginx, plan the move to Gateway API separately instead of bundling it into the same window.
What changed and who is affected
The 1.37 release announcement highlights two items that touch ops teams:
- DRA extended resources (GPUs) reached GA.
- Workload/PodGroup gang scheduling arrived in beta.
Read the full CHANGELOG and the deprecated API migration guide for removals. This article does not list them because they must be checked against your own manifests.
Who needs to care, by profile:
- GPU and AI platform teams: DRA extended resources are now GA. This is an opportunity, not an obligation. Existing device-plugin workloads keep working, so do not migrate them in the same window as the version bump.
- Batch and training teams: gang scheduling is beta. Verify in the docs whether the feature is on by default in your distribution before relying on it, and treat the API as still subject to change.
- Anyone on ingress-nginx: the project was retired in March 2026. No releases and no security fixes means it is a liability regardless of the Kubernetes version.
- Everyone: add-ons (CNI, CSI, cert-manager, metrics-server, GitOps controllers) each publish their own supported Kubernetes range. Check those before the control plane moves.
Before/after config
For a kubeadm cluster, the visible config change is the target version. With the v1beta4 config API, upgrade settings live in an UpgradeConfiguration, not in ClusterConfiguration. Keep the file in Git so the change is reviewable.
# kubeadm-upgrade.yaml # BEFORE: cluster running v1.36.<patch> # AFTER: apiVersion: kubeadm.k8s.io/v1beta4 kind: UpgradeConfiguration apply: kubernetesVersion: v1.37.2 # pin an exact patch, not a floating tag 🧪 Lab — try this in your browser
- Kubernetes Manifest Linter — Lint multi-document Kubernetes YAML for production readiness and security, with line:col diagnostics, severity grouping, a 0-100 score and fix hints.
The other "before/after" worth doing in the same quarter, if you still run ingress-nginx, is moving routing to Gateway API. Run the two in parallel and cut DNS over per hostname.
Here is the target shape. The gatewayClassName on the Gateway depends on the controller you choose. Because the route and the Gateway sit in different namespaces, the Gateway listener must permit it via allowedRoutes.namespaces.
# BEFORE: networking.k8s.io/v1 Ingress with ingressClassName: nginx # AFTER: Gateway API apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: web namespace: shop spec: parentRefs: - name: public-gw # a Gateway you manage separately namespace: infra # its listener must allow routes from "shop" hostnames: ["shop.example.com"] rules: - backendRefs: - name: web # existing Service port: 8080 The ingress2gateway tool can generate a first draft from existing Ingress objects. Review the output by hand, because annotation-driven behavior (rewrites, auth snippets) does not always translate. See the Gateway API docs.
Step-by-step migration
1. Find deprecated API usage. The API server counts calls to deprecated endpoints. Run this against the current cluster:
# Any non-empty result = a client still calling a deprecated API kubectl get --raw /metrics | grep apiserver_requested_deprecated_apis # Look at removed_release="..." in the labels to see what will break next Also scan manifests in Git with a tool such as pluto or kubent. The metric only shows what has been called recently, not what a rarely run CronJob will call next week.
2. Back up etcd. On a kubeadm control plane node, run the following. etcdctl is not always installed on the host, so you may need to run it inside the etcd static pod or install a matching client version.
sudo etcdctl snapshot save /var/backups/etcd-pre-1.37.db \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key Copy the file off the node. A snapshot that lives only on the machine you are about to modify is not a backup.
3. Upgrade the first control plane node. Switch the package repository to the v1.37 stream (pkgs.k8s.io uses one repo per minor), then:
sudo apt-get update sudo apt-mark unhold kubeadm && \ sudo apt-get install -y kubeadm='1.37.2-*' && \ sudo apt-mark hold kubeadm sudo kubeadm upgrade plan # review the proposed versions and add-on changes sudo kubeadm upgrade apply --config kubeadm-upgrade.yaml # equivalent without the file: sudo kubeadm upgrade apply v1.37.2 On additional control plane nodes, upgrade the kubeadm package the same way and run sudo kubeadm upgrade node. Then, one node at a time, follow this sequence:
- Drain the node.
- Unhold, install, and re-hold the
kubeletandkubectlpackages at1.37.2-*. - Run
sudo systemctl daemon-reload && sudo systemctl restart kubelet. - Uncordon the node.
4. Roll the workers. Work one node (or one small batch) at a time:
- Run
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data. - On the node, upgrade the kubeadm package and run
sudo kubeadm upgrade node. - Upgrade
kubelet, then restart it. - Run
kubectl uncordon <node>.
On managed services (EKS, GKE, AKS), use the provider's upgrade path instead and confirm 1.37 is available in your region and channel.
Verify and roll back
Verification is cheap, so do all of it before declaring the window closed:
-
kubectl versionshows serverv1.37.2, andkubectl get nodesshows every nodeReadyat the new kubelet version. -
kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeededreturns only pods that were already unhealthy before. - Watch API server 5xx rate and admission webhook latency in Prometheus for at least one full traffic cycle.
- Run a canary deploy and one scheduled Job end to end.
Be honest about rollback: Kubernetes does not support downgrading a control plane minor version. Your real options are:
- Workers last: keep some workers on 1.36 until the new control plane has proven itself. The version skew policy permits kubelets older than the API server, within documented limits (check the skew policy). This is your cheap rollback lane for workload problems.
- Control plane failure: restore the etcd snapshot and reinstall the old binaries on kubeadm clusters. This loses any state written after the snapshot and is a recovery procedure, not an undo button. Rehearse it on a staging cluster.
- Managed clusters: rollback is usually not offered. The fallback is a new cluster or node pool at the old version.
Gotchas after the switch
Watch out for admission webhooks. A webhook with failurePolicy: Fail whose backing pod is evicted during a drain can block creation of every resource it matches. With broad rules, that can be most of the cluster. A typical failure is a policy engine going down mid-upgrade and every Deployment rollout stalling.
Check kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations before the window. Make sure webhook backends have multiple replicas and PodDisruptionBudgets.
Watch out for PDBs that allow zero disruptions. Two common cases block every eviction:
- A PodDisruptionBudget with
maxUnavailable: 0. - A
minAvailable: 1PodDisruptionBudget on a single-replica workload.
In either case, kubectl drain keeps retrying until you interrupt it or a --timeout expires. Find these in advance with kubectl get pdb -A and look for ALLOWED DISRUPTIONS of 0.
Other post-upgrade items worth a calendar entry:
- Beta features are not production contracts. If you try gang scheduling, isolate it to a non-critical namespace and re-read the release notes at each minor, since beta APIs can change.
- Client versions: keep
kubectlwithin one minor of the server, and update CI images that bundle an old one. - Add-on drift: upgrade the CNI, CSI drivers and GitOps controllers to versions that list 1.37 support, then re-check dashboards for new error rates.
- Cost of waiting: each skipped minor makes the next upgrade bigger, and minors are supported only for a limited window. See the releases page for current end-of-life dates.
When not to upgrade right now: if your CNI or storage driver has no stated 1.37 support, or you cannot rehearse on staging, hold at 1.36 and fix those first. More runbooks like this live at kuryzhev.cloud.
