Upgrading clusters and draining nodes

Upgrade a kubeadm cluster one minor version at a time: first the control plane with kubeadm upgrade apply, then every other node, drained, upgraded and put back to work.

40 min 2 hands-on tasksArchitecture · 25% of the exam

Key ideas in this lesson · tap one for a simple explanation

What you will be able to do

  • Say which versions may run side by side (the version skew rules) and why they decide the upgrade order.
  • Upgrade the first control plane with kubeadm upgrade plan and apply, then its kubelet and kubectl.
  • Drain, upgrade and uncordon nodes one at a time, and say what each drain flag is for.

An upgrade moves the cluster to a newer Kubernetes version, for example from 1.36 to 1.37. With it is a fixed list of about fifteen commands. The exam punishes small slips in that list: a package not pinned to a version, a never restarted, a left cordoned.

This lesson gives you the list and the reason for each step. Then you practice the part kind can do for real: draining a node and putting it back. It belongs to the exam topic Manage the lifecycle of Kubernetes clusters.

The upgrade order

First control plane

upgrades the control plane

kubeadm upgrade apply

Other control planes

only if you have more than one

kubeadm upgrade node

Each worker, one at a time

drain, upgrade, uncordon

kubeadm upgrade node

Nothing may be newer than the API server, so the control plane always goes first. Then one node at a time.

Version skew decides the order

During an upgrade, parts of the cluster run different versions for a while. This is called version skew. The rules say how far apart the versions may be, measured against the :

ComponentAllowed version, compared with kube-apiserver
kube-apiserver (if you run several)Within one minor version of each other. That is why you can upgrade nodes one at a time.
, , cloud-controller-managerNever newer. At most one minor version older.
kubeletNever newer. Up to three minor versions older.
Never newer. Up to three minor versions older.
One minor version older or newer.

Example: the API server runs 1.37. A kubelet on 1.34 is allowed. A kubelet on 1.38 is not. Two rules follow from the table:

  • The control plane goes first, because nothing may be newer than the API server.
  • One minor version per upgrade. 1.36 to 1.37 works. 1.35 straight to 1.37 is refused. Skipping patch releases is fine, for example 1.36.2 straight to 1.37.1.

One more thing: the VERSION column of kubectl get nodes shows each node's kubelet version. It changes only after you upgrade and restart the kubelet on that node. kubeadm upgrade apply alone never changes it.

Upgrade the first control plane

Run these on the first control plane node, as root. The example moves from v1.36 to v1.37.1. The same steps repeat on every node, so learn their shape once:

The steps on each node

1. Switch the repository

point apt at v1.37

2. New kubeadm

unhold, install the exact version, hold

3. kubeadm upgrade

apply on the first control plane, node everywhere else

4. New kubelet

drain, install kubelet and kubectl, restart, uncordon

First control plane node, as root
# exam: ssh <control-plane>; sudo -i
# root may need: export KUBECONFIG=/etc/kubernetes/admin.conf

# 1. Point apt at the new minor version and find the exact package
sed -i 's#/v1.36/#/v1.37/#' /etc/apt/sources.list.d/kubernetes.list
apt-get update
apt-cache madison kubeadm | head -3          # e.g. 1.37.1-1.1

# 2. kubeadm first
apt-mark unhold kubeadm
apt-get install -y kubeadm=1.37.1-1.1
apt-mark hold kubeadm
kubeadm version -o short                     # v1.37.1

# 3. Plan, then apply exactly the version you installed
kubeadm upgrade plan
kubeadm upgrade apply v1.37.1                # -y skips the confirmation prompt

# 4. kubelet and kubectl, with the node drained
kubectl drain <control-plane> --ignore-daemonsets
apt-mark unhold kubelet kubectl
apt-get install -y kubelet=1.37.1-1.1 kubectl=1.37.1-1.1
apt-mark hold kubelet kubectl
systemctl daemon-reload
systemctl restart kubelet
kubectl uncordon <control-plane>
kubectl get nodes

kubeadm upgrade apply upgrades the control plane , renews their certificates, and upgrades and kube-proxy. It does not touch the kubelet. That is step 4 in the block above.

Other control planes and the workers

Every other node gets the same package steps. The one difference: run kubeadm upgrade node instead of upgrade apply.

Every other node, one at a time
# Additional control plane nodes: the same package steps, but instead of "upgrade apply":
kubeadm upgrade node

# Each worker. On the worker (ssh <worker>; sudo -i):
sed -i 's#/v1.36/#/v1.37/#' /etc/apt/sources.list.d/kubernetes.list
apt-get update
apt-mark unhold kubeadm && apt-get install -y kubeadm=1.37.1-1.1 && apt-mark hold kubeadm
kubeadm upgrade node

# Wherever kubectl has admin credentials:
kubectl drain <worker> --ignore-daemonsets --delete-emptydir-data

# Back on the worker:
apt-mark unhold kubelet kubectl && apt-get install -y kubelet=1.37.1-1.1 kubectl=1.37.1-1.1 && apt-mark hold kubelet kubectl
systemctl daemon-reload && systemctl restart kubelet

# And from the admin side again:
kubectl uncordon <worker>
kubectl get nodes

kubeadm upgrade node reads the target version from the cluster, so it takes no version number. Never run upgrade apply a second time on the other control planes. Upgrade the nodes one at a time, so the rest of the cluster keeps your apps running.

cordon, drain, uncordon

Before you restart a node's kubelet, move its Pods somewhere else. Example: cka-worker2 runs three web Pods. kubectl drain cka-worker2 evicts them, and their starts new copies on the other workers. After the upgrade, kubectl uncordon cka-worker2 opens the node again.

Command or flagWhat it does
kubectl cordon <node>Marks the node unschedulable (spec.unschedulable: true), so no new Pods land on it. Pods already running there stay.
kubectl drain <node>Cordons the node, then evicts every Pod on it and waits until they are gone. Eviction respects (see Probes and disruption budgets). Static Pods are left alone.
--ignore-daemonsetsNeeded on almost every node, because Pods run everywhere. They stay, because their controller would put them straight back.
--delete-emptydir-dataAllows evicting Pods that use emptyDir volumes. That scratch data is lost.
--forceAlso deletes Pods that no controller manages. Nothing recreates them anywhere.
--timeout, --grace-period--timeout limits how long waits in total. --grace-period limits how long each Pod gets to shut down. Without a timeout, a blocked drain can retry forever.
kubectl uncordon <node>Makes the node schedulable again. Evicted Pods do not move back on their own.

In the official docs· open on exam day too

Hands-on task 1 of 2ar-upgrade-1
~20 min8% weightArchitecture

Upgrade a kubeadm cluster by one minor version

The cluster has control plane node cp1 and worker w1, both on Kubernetes v1.36 and installed from pkgs.k8s.io. Upgrade the control plane components, kubelets and kubectl on both nodes to the newest v1.37 patch release available. Upgrade one node at a time, drain each node before upgrading its kubelet, and leave both nodes schedulable. You work from cp1; ssh w1 reaches the worker.

Runs on a kubeadm cluster (VM lab), not on the kind lab.

This one needs a cluster built with kubeadm. Read it and study the solution now, then run it on your VM lab.
  1. 1

    Start from a healthy lab

    No setup needed for this one.

  2. 2

    Solve it in your terminal

    Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under /tmp/cka/.

  3. 3

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-upgrade-1/verify | bash
    Got FAIL? Read which check failed, fix it, run it again.
Hands-on task 2 of 2ar-upgrade-2
~5 min4% weightArchitecture

Drain a node for maintenance

Node cka-worker2 is going down for maintenance. Make sure no new Pods can be scheduled onto it, and empty it of every Pod except those managed by DaemonSets. The Pods of Deployment web in namespace ar-drain keep scratch data in an emptyDir volume; that data may be discarded. Any Pod on the node that no controller manages is a leftover test Pod and may be deleted. Leave the node unschedulable, since the maintenance team will return it to service themselves.

Cluster:kubectl config use-context kind-cka

This task changes a node (labels, taints or scheduling). Run the clean-up step when you finish so later tasks start from a clean lab.
  1. 1

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-upgrade-2/setup | bash
  2. 2

    Solve it in your terminal

    Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under /tmp/cka/.

  3. 3

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-upgrade-2/verify | bash
    Got FAIL? Read which check failed, fix it, run it again.
  4. 4

    Clean up (optional)

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-upgrade-2/cleanup | bash

Finished this lesson?

0 of 2 tasks passed. Your readiness counts tasks, so run them before you move on.