Upgrading clusters and draining nodes

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

Architecture · 25%40 min2 tasks

After this lesson

  • 01You can state the version skew rules and explain the upgrade order they force.
  • 02You can upgrade the first control plane with kubeadm upgrade plan and apply, then its kubelet and kubectl.
  • 03You can drain, upgrade and uncordon nodes one at a time, and you know what each drain flag is for.

Upgrades are the heart of the competency Manage the lifecycle of Kubernetes clusters. A kubeadm upgrade is a fixed sequence of about fifteen commands, and the exam punishes small slips in it: a package left unpinned, a kubelet never restarted, a node left cordoned. This lesson gives you the sequence and the reasoning behind it, then lets you practice the part kind can do for real: draining and returning a node.

Version skew decides the order

ComponentAllowed version relative to kube-apiserver
kube-apiserver (several instances)Within one minor version of each other. That is what lets you upgrade control plane nodes one at a time.
kube-controller-manager, kube-scheduler, cloud-controller-managerNever newer; at most one minor version older.
kubeletNever newer; up to three minor versions older.
kube-proxyNever newer; up to three minor versions older, and within three minor versions of the kubelet on the same node.
kubectlOne minor version older or newer.

Two rules fall out of that table. The control plane goes first, because nothing may be newer than the API server. And kubeadm moves one minor version per upgrade: 1.36 to 1.37 works, 1.35 straight to 1.37 is refused, while skipping patch releases is fine. Remember that 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, never because of kubeadm upgrade apply.

Upgrade the first control plane

First control plane node, as rootbash
# 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 plan checks that the cluster is healthy and lists the versions you can move to. kubeadm upgrade apply pulls the new images and rewrites the static Pod manifests one component at a time, keeping backups under /etc/kubernetes/tmp and rolling a component back if its new Pod does not come up. It also renews the control plane certificates and upgrades the CoreDNS and kube-proxy add-ons. It does not touch the kubelet binary; that is the package step that follows.

Other control planes and the workers

Every other node, one at a timebash
# 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 is the per-node half of the upgrade. On another control plane node it rewrites that node's static Pod manifests for the version the cluster now runs; on a worker it refreshes the kubelet configuration in /var/lib/kubelet/config.yaml. It takes no version argument because it reads the target from the cluster. Never run upgrade apply again on the other control planes. Do workers one at a time, or in batches small enough for your spare capacity and PodDisruptionBudgets.

cordon, drain, uncordon

Command or flagWhat it does
kubectl cordon <node>Sets spec.unschedulable: true; the node gets the node.kubernetes.io/unschedulable:NoSchedule taint. Running Pods stay where they are.
kubectl drain <node>Cordons, then evicts every Pod through the Eviction API, so PodDisruptionBudgets are respected, and waits until they are gone. Static Pods are left alone.
--ignore-daemonsetsNeeded whenever DaemonSet Pods run on the node, which is almost always. They stay, because their controller would put them straight back.
--delete-emptydir-dataAllows evicting Pods that use emptyDir volumes. That data is lost.
--forceAlso deletes Pods that no controller manages. Nothing recreates them anywhere.
--timeout, --grace-periodBound the total wait and each Pod's shutdown time. Without a timeout, a PDB that allows zero disruptions makes drain retry forever.
kubectl uncordon <node>Makes the node schedulable again. Evicted Pods do not move back on their own.

In the docs · the tab you get on exam day

Taskar-upgrade-1
Architecture8%~20 min

Cluster: a kubeadm-built cluster (VM lab). The kind lab cannot run this one.

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.

This one needs a cluster you built with kubeadm. Read it and the solution now; run it when you have the VM lab.

  1. 01

    Nothing to set up

    Start on a healthy lab. Check it with lab-check.sh if unsure.

  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-upgrade-1/verify | bash
    Counts toward your readiness score.
Taskar-upgrade-2
Architecture4%~5 min

context · kubectl config use-context kind-cka

Drain a node for maintenance

Node cka-worker2 is going down for maintenance. Make sure no new Pods can be scheduled onto it and move every Pod off it, 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. Leave the node unschedulable, since the maintenance team will return it to service themselves.
  1. 01

    Set up the scenario

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

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-upgrade-2/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

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

Finished reading?

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