On this page
How to Upgrade a Kubernetes Cluster Without Downtime
Upgrade a control plane and its nodes in the right order, one minor version at a time, with PodDisruptionBudgets and a tested rollback. Covers managed clusters and k3s.
Quick answer
- Upgrade one minor version at a time and read the release notes first — version skew rules make multi-version jumps unsupported and hard to unwind.
- The control plane goes first, then the nodes; never the reverse.
- Drain a node before upgrading it and make sure a PodDisruptionBudget protects the workloads it hosts.
- Managed clusters largely do the control plane for you and leave the node pools to you; self-hosted and k3s installations do both by hand.
- A rollback plan written before you start is the difference between a maintenance window and an incident.
Before you start
Four preparations remove most of the risk. First, read the release notes for every version you are crossing, because deprecations are the thing that breaks clusters — an API version removed in 1.32 fails at upgrade time, not at deploy time. Second, confirm your current version and upgrade one minor version at a time; Kubernetes only supports a limited version skew between the control plane and kubelets, and skipping versions puts you outside that. Third, make sure every manifest is in Git, since a node upgrade is a reboot of every pod on it and you want to be able to redeploy from a known state. Fourth, run the upgrade in a staging cluster first if you have one.
kubectl version # client vs server
kubectl get nodes -o wide # node versions, one row each
kubectl api-resources --verbs=list -o name | head # what your manifests rely on
kubectl get pdb -A # PodDisruptionBudgets that will gate drainingStep 1 — Protect workloads before touching a node
Draining a node evicts its pods, and without a PodDisruptionBudget the eviction takes down every replica of a workload at once if they all happen to be scheduled there. Before you drain anything, add a PDB to every workload that must stay available, and confirm you have spare capacity elsewhere — on a single-node cluster there is no elsewhere, which is why the whole cluster goes down during the upgrade and the window has to be planned.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api
spec:
minAvailable: 1
selector:
matchLabels:
app: apiStep 2 — Upgrade the control plane
On a managed service, select the new version on the cluster and let the provider roll the control plane; check that the API server is reachable and the nodes report healthy before moving on. On a self-managed cluster, kubeadm upgrade plan tells you what is available and what will change, and kubeadm upgrade apply performs it. On k3s, the control plane upgrade is the same install script with a pinned version, which restarts the service.
# Self-managed (kubeadm), control plane first
sudo kubeadm upgrade plan
sudo kubeadm upgrade apply v1.31.0
# Single-node or multi-node k3s: pin the target version explicitly
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.31.5+k3s1 sh -
k3s --versionStep 3 — Upgrade the nodes one at a time
Cordon the node so no new pods land on it, drain it so the existing ones move, upgrade the kubelet and container runtime on that machine, reboot if the kernel changed, then uncordon and wait for the node to report Ready and for the workloads to settle before starting the next one. Doing them one at a time is what keeps capacity available; doing them in parallel is how an upgrade becomes an outage. Watch for pods that will not evict because a PDB has no budget left — that is the signal to check the other replicas are actually healthy on another node.
kubectl cordon node-2
kubectl drain node-2 --ignore-daemonsets --delete-emptydir-data
# upgrade kubelet/runtime on node-2 via your package manager, then:
kubectl uncordon node-2
kubectl get pods -A -o wide --field-selector spec.nodeName=node-2Step 4 — Verify, then decide about rolling back
Verify in this order: every node is Ready and on the expected version, every workload is at its desired replica count, and the app answers real requests rather than just passing a readiness probe. Then keep the rollback option honest. Kubernetes supports a limited downgrade path that is rarely worth relying on — the practical rollback is redeploying the previous container images to a cluster that is still slightly ahead, or restoring the control plane from a snapshot. On k3s, reinstalling the previous pinned version is a genuine, tested path, so keep the old version string written down. On a managed platform, treat the control plane version as one-way and plan for it.
Where this bites vibecoders
Upgrades are the operation where AI assistants are least useful and most confidently wrong. Asked to upgrade a cluster, an assistant will produce a plausible command — often a version jump of two minors, or a kubeadm sequence run out of order, or a k3s install without the version pinned. Each of those applies without error and produces a cluster that is half-upgraded. The parts that actually protect you are procedural and unglamorous: read the release notes, one minor at a time, PDBs before draining, nodes one at a time, and a rollback you have written down before you begin.
Where AI coding assistants get this wrong
- Suggesting a jump of more than one minor version.
- Running node upgrades in parallel, or upgrading nodes before the control plane.
- Draining without a PodDisruptionBudget, so all replicas of a workload go down together.
- Installing k3s without pinning INSTALL_K3S_VERSION, so you get an unintended version.
- Ignoring deprecated APIs that the new control plane no longer serves.
- Assuming a downgrade path exists when it usually does not.
Checklist
- Read the release notes for every version you cross.
- Upgrade exactly one minor version at a time.
- Confirm all manifests and images are recoverable from Git.
- Add PodDisruptionBudgets before draining anything.
- Upgrade the control plane first, then nodes one at a time.
- Pin the target version explicitly on k3s.
- Verify nodes Ready, workloads at desired replicas, and real traffic served.
- Write down the rollback plan and the previous version before you start.
FAQ
Can I skip Kubernetes versions when upgrading?
Not safely. Kubernetes supports only a limited version skew between the control plane and the kubelets, and the upgrade tooling expects to move one minor version at a time. Jumping two or three minors means passing through intermediate versions that changed or removed APIs, which is how a cluster ends up with workloads that cannot be scheduled.
How much downtime does a Kubernetes upgrade cause?
On a multi-node cluster with PodDisruptionBudgets set and spare capacity, a node-by-node upgrade should cause none for correctly designed workloads. On a single-node cluster the node is the whole cluster, so every workload restarts — plan the window and tell your users instead of assuming zero downtime.
How do I upgrade k3s?
Re-run the official install script with INSTALL_K3S_VERSION set to the target version. The service restarts the control plane. For multi-node clusters the server nodes go first, one at a time, then the agents, and an automated system-upgrade-controller can handle the sequence for you.
Related topics
- Kubernetes Deployment vs StatefulSet: Which Does Your Service Need?
- Kubernetes Autoscaling Explained: HPA vs VPA vs Cluster Autoscaler
- Kubernetes for AI Builders: Run What Your Assistant Generates Without Wrecking Production
- How to Deploy an App to a Cheap VPS With k3s
- How to Set Up GitOps With Argo CD
- k3s for Vibecoders: Kubernetes on One Cheap Server, Without the Cloud Bill