Upgrades Basics
This page covers the mechanics of moving a cluster between versions: check the skew, upgrade the control plane first, then roll the node pools. Examples cover both managed clusters and self-managed kubeadm.
Prerequisites
kubectlwithin one minor of your API server (1.35, 1.36, or 1.37 for a 1.36 cluster).- Cluster admin access and, for cloud clusters, the provider CLI (
aws,gcloud, oraz). - For self-managed clusters:
kubeadm,kubelet, and SSH to each node. - A non-production cluster mirroring production add-ons to rehearse on.
Quick check of your tooling:
kubectl version
kubeadm versionBasic Examples
1. Read the current versions
See where the control plane and every node sit today.
kubectl version
kubectl get nodes -o widekubectl versionreports both the client and the API server version.get nodes -o wideshows each node'sKUBELET-VERSIONin its own column.- The highest version in the cluster should always be the API server.
- Confirm no node is already newer than the control plane.
2. Confirm you are within the skew policy
You may only lag nodes so far behind the control plane.
kubectl get nodes -o custom-columns=NAME:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion- The kubelet may be up to three minor versions older than the API server.
- The kubelet must never be newer than the API server.
- If any node is more than three minors behind, rotate it before bumping the control plane again.
kube-proxyon each node tracks that node's kubelet minor version.
3. Check for removed and deprecated APIs
A minor upgrade can delete an API your manifests still use.
kubectl get --raw /metrics | grep apiserver_requested_deprecated_apis- This metric surfaces deprecated API groups still being called.
- Cross-reference the target version's API removal list before upgrading.
- Fix manifests and Helm charts to the current API versions first.
- Never skip this step - removed APIs are the most common upgrade break.
4. Upgrade a managed control plane one minor
On managed platforms the provider replaces the control plane for you.
aws eks update-cluster-version \
--name prod \
--kubernetes-version 1.36- Managed control-plane upgrades move exactly one minor at a time.
- The equivalents are
gcloud container clusters upgrade --masterandaz aks upgrade --control-plane-only. - Nodes keep running on their old version during and after this step.
- Wait for the control plane to report healthy before touching nodes.
5. Upgrade a self-managed control plane with kubeadm
On kubeadm clusters you drive the control plane yourself.
# On the first control-plane node
sudo kubeadm upgrade plan
sudo kubeadm upgrade apply v1.36.2upgrade planprints the available target versions and component changes.upgrade applyupgrades the static control-plane pods on that node.- Run
kubeadm upgrade nodeon any additional control-plane nodes afterward. - Step through one minor at a time -
kubeadmrefuses to skip minors.
6. Upgrade kubelet and kubectl on control-plane nodes
The control-plane node's own kubelet must be updated after the components.
sudo apt-get install -y kubelet=1.36.2-* kubectl=1.36.2-*
sudo systemctl daemon-reload
sudo systemctl restart kubelet- Do this on each control-plane node after
kubeadm upgrade. - Restarting the kubelet reconnects it at the new version.
- Package names differ on
yum/dnfdistributions but the sequence is identical. - Confirm the node returns
Readywith the new kubelet version.
7. Roll a managed node pool
With the control plane on the new minor, bring nodes forward.
aws eks update-nodegroup-version \
--cluster-name prod \
--nodegroup-name workers \
--kubernetes-version 1.36- The provider replaces nodes with new ones running the target version.
- Old nodes are cordoned and drained automatically as replacements join.
gcloud container clusters upgradeandaz aks nodepool upgradedo the same.- Nodes are replaced, not patched, so they can jump minors safely.
Intermediate Examples
8. Drain a node manually during a self-managed upgrade
On kubeadm you cordon and drain each worker yourself before upgrading it.
kubectl cordon ip-10-0-1-15
kubectl drain ip-10-0-1-15 \
--ignore-daemonsets \
--delete-emptydir-datacordonmarks the node unschedulable so no new pods land on it.drainevicts existing pods, honoring PodDisruptionBudgets.--ignore-daemonsetsis required because DaemonSet pods cannot be evicted.- After upgrading and rejoining, run
kubectl uncordonto restore scheduling.
9. Scan manifests for deprecated APIs before you upgrade
Catch removed APIs in your own YAML, not just live traffic.
pluto detect-files -d ./manifestsplutoflags API versions removed or deprecated in target releases.- Run it against your Git manifests and rendered Helm output.
- Pair it with the live
apiserver_requested_deprecated_apismetric for full coverage. - Fix every finding before the control-plane bump.
10. Upgrade add-ons to match the new minor
Core add-ons have their own supported version matrix.
helm upgrade cilium cilium/cilium \
--namespace kube-system \
--reuse-values \
--version <chart-for-1.36>- CoreDNS, the CNI, CSI drivers, and metrics-server each track Kubernetes versions.
- Upgrade add-ons after the control plane and before or alongside the node roll.
--reuse-valuespreserves your existing configuration during the bump.- Verify each add-on reports healthy before moving on.
11. Verify the cluster after the roll
Confirm every component landed on the target version before declaring done.
kubectl get nodes -o wide
kubectl get pods -A --field-selector=status.phase!=Running- The first command confirms each node's kubelet version and
Readystatus. - The second surfaces any pod that did not reschedule cleanly after the drains.
- Also smoke-test ingress, DNS resolution, and a critical request path.
- Watch error rate and latency for a while - a clean end state is not the same as a clean rollout.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).