Asterrr's Handbook

Cluster upgrades

Version skew rules, switching the pkgs.k8s.io repo, kubeadm upgrade plan, apply and node, draining and uncordoning, and renewing kubeadm certificates.

Exam tasks: 1.4 (manage the lifecycle of Kubernetes clusters)

The decision: in what order do you upgrade which packages on which nodes, so the cluster moves up one minor version without breaking the version skew policy or dropping workloads?

Order of operations

  • Control plane first, then workers. The kubelet must never be newer than the API server.
  • One minor at a time. Going from 1.33 to 1.35 is two upgrades: 1.33 to 1.34, then 1.34 to 1.35. Patch versions within a minor can be skipped.
  • kubeadm upgrade upgrades the static Pods (API server, controller manager, scheduler, etcd), CoreDNS and kube-proxy. It never upgrades the kubelet package or your CNI plugin.

Version skew you must respect

ComponentAllowed relative to kube-apiserver
Other kube-apiserver instances (HA)Within one minor version of each other
kube-controller-manager, kube-schedulerSame minor or one older, never newer
kubeletUp to three minors older, never newer
kube-proxyUp to three minors older, never newer
kubectlOne minor newer or older

Exam signal

"Upgrade the control plane only" means the workers keep their old kubelets, which is fine within three minors. "Upgrade the whole cluster" means every node's kubelet too, and k get nodes should show the new version on each one when you're done.

First control plane node

ssh cp-a
# 1. switch the repo to the target minor (repeat on every node before its turn)
sudo sed -i 's#/v1.34/#/v1.35/#' /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
apt-cache madison kubeadm | head -3                  # find the exact package version, e.g. 1.35.2-1.1

# 2. kubeadm first
sudo apt-mark unhold kubeadm
sudo apt-get install -y kubeadm='1.35.2-1.1'
sudo apt-mark hold kubeadm
kubeadm version -o short

# 3. plan and apply
sudo kubeadm upgrade plan
sudo kubeadm upgrade apply v1.35.2

# 4. kubelet and kubectl, with the node drained
k drain cp-a --ignore-daemonsets
sudo apt-mark unhold kubelet kubectl
sudo apt-get install -y kubelet='1.35.2-1.1' kubectl='1.35.2-1.1'
sudo apt-mark hold kubelet kubectl
sudo systemctl daemon-reload && sudo systemctl restart kubelet
k uncordon cp-a
  • upgrade plan checks that the cluster is healthy and lists the versions you can move to. Read it; it also tells you if a component config needs manual attention.
  • upgrade apply renews the certificates kubeadm manages on that node unless you pass --certificate-renewal=false.
  • Additional control plane nodes run sudo kubeadm upgrade node instead of apply, then the same kubelet steps. Do them one at a time; addons such as CoreDNS are upgraded after the last control plane finishes.

apt-get upgrade or forgetting the hold

apt-get upgrade on a node with unheld packages can jump the kubelet ahead of the control plane, and a version that isn't in the repo you configured simply isn't found. Upgrade with pinned versions, and re-hold the packages so the next routine patch run doesn't change them behind your back.

Worker nodes

ssh node-b
sudo sed -i 's#/v1.34/#/v1.35/#' /etc/apt/sources.list.d/kubernetes.list && sudo apt-get update
sudo apt-mark unhold kubeadm && sudo apt-get install -y kubeadm='1.35.2-1.1' && sudo apt-mark hold kubeadm
sudo kubeadm upgrade node                  # refreshes the local kubelet configuration
exit

k drain node-b --ignore-daemonsets --delete-emptydir-data   # from wherever kubectl works

ssh node-b
sudo apt-mark unhold kubelet kubectl
sudo apt-get install -y kubelet='1.35.2-1.1' kubectl='1.35.2-1.1'
sudo apt-mark hold kubelet kubectl
sudo systemctl daemon-reload && sudo systemctl restart kubelet
exit

k uncordon node-b
k get nodes                                 # node-b now shows v1.35.2

Drain, cordon and uncordon

CommandEffect
k cordon node-bMarks the node unschedulable. Running Pods stay
k drain node-b --ignore-daemonsetsCordons, then evicts Pods through the Eviction API, honouring PodDisruptionBudgets
--delete-emptydir-dataNeeded when Pods use emptyDir; that data is lost
--forceAlso deletes bare Pods not owned by a controller. They don't come back
k uncordon node-bSchedulable again. Existing Pods don't move back on their own
  • A drain that hangs is usually blocked by a PodDisruptionBudget that can't be satisfied, for example a single-replica Deployment with minAvailable: 1. Scale it up or wait, don't delete the PDB unless the task says so.
  • DaemonSet Pods are never evicted, which is why --ignore-daemonsets is required.

Certificates

kubeadm-issued certificates are valid for one year. Upgrading at least once a year renews them automatically.

sudo kubeadm certs check-expiration
sudo kubeadm certs renew all            # or renew apiserver, renew admin.conf, ...
# static Pods don't reload certificates on their own: restart them
sudo mkdir -p /root/mf-hold
sudo mv /etc/kubernetes/manifests/*.yaml /root/mf-hold/ && sleep 20
sudo mv /root/mf-hold/*.yaml /etc/kubernetes/manifests/

renew all also rewrites admin.conf; copy it to ~/.kube/config again if you use that.

Moving the manifests out makes the kubelet stop those static Pods; moving them back starts them with the new certificates. Use an empty folder outside /etc/kubernetes/manifests, and on an HA cluster do one node at a time.

1 minor
Largest jump per kubeadm upgrade.
3 minors
How far a kubelet may lag behind the API server.
1 year
kubeadm certificate validity, renewed by every upgrade.
~1 year
Patch support for each Kubernetes minor release. Three minors are maintained at a time.

Legacy: use per-minor pkgs.k8s.io repos instead

Older guides upgrade with apt-get install kubeadm=1.2x.y-00 from a single kubernetes-xenial repo. That repo is gone. With pkgs.k8s.io you first change the minor version in the repo URL, and package versions look like 1.35.2-1.1.

Scenarios

Scenario
A kubeadm cluster runs v1.33.4 everywhere. The task: upgrade it to v1.35.1. Which plan is valid?
Scenario
During a worker upgrade, `kubectl drain node-c --ignore-daemonsets` keeps printing that it cannot evict a Pod because it would violate the Pod's disruption budget. The Pod belongs to a one-replica Deployment named ledger whose PDB has minAvailable: 1. What's the safest way to let the drain finish?

Drill

kubectl config use-context cka-up

The cluster has control plane up-cp and worker up-w1, both on v1.34. Upgrade the control plane node only (control plane components, kubelet and kubectl) to the latest v1.35 patch available in the v1.35 package repo. Leave up-w1 untouched. Make sure up-cp is schedulable again at the end.

Further reading

On this page