Cluster Upgrades#
The multi-node notes ended with a one-liner: “upgrade the control plane first, one minor version at a time”. That’s the policy - this is the practice.
Upgrading a cluster is the most common maintenance task you will ever run, and the most common way to break one. The rules that keep it boring:
- One minor version at a time.
1.30→1.31, never1.30→1.32. The version skew policy allows the apiserver to lead kubelets by one minor; anything more is unsupported and the upgrade will refuse - Control plane first, workers after. The new apiserver must understand old kubelets, not the reverse
- One node at a time. Drain → upgrade → uncordon → next
- Read the output.
kubeadm upgrade planexists precisely because upgrades are not a single command
The apt packages are version-pinned with
apt-mark holdfrom the install notes. Every upgrade starts withapt-mark unholdand ends withapt-mark holdagain - forgetting the re-hold is how a futureapt upgradesilently moves your cluster a minor version.
etcd Backup and Restore#
Before any upgrade - before any anything risky - take a backup. The multi-node notes covered snapshot save; this is the full cycle, because a backup you cannot restore is a hope, not a plan.
ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \
--key=/etc/kubernetes/pki/etcd/healthcheck-client.key \
snapshot save /var/backups/etcd-$(date +%F).dbVerify the snapshot is usable now, not on the day you need it:
etcdctl snapshot status /var/backups/etcd-2025-01-01.db -w tableDikkat: snapshot restore, çalışan etcd’nin mevcut verisinin üstüne backup’ı kopyalamak değildir.
Restore işlemi snapshot’tan yeni bir etcd data dir oluşturur; ardından etcd bu yeni dizin kullanılarak ayağa kaldırılır.
snapshot.db
↓
restore
↓
yeni data dir
↓
etcd bu dizinden başlarYani mevcut /var/lib/etcd dizininin içine restore yapmaya çalışma. Önce eski data dir’i güvenli şekilde kenara al, restore’u ayrı ve temiz bir dizine yap, ardından etcd’yi restore edilen dizine yönlendir.
Kısacası:
snapshot restore != mevcut etcd'nin üstüne yazmak
snapshot restore = snapshot'tan yeni bir etcd data dir üretmekEski data dir’i korumadan doğrudan restore işlemine girişmek, recovery yapmak yerine recovery senaryosunun kendisine dönüşebilir.
Certificate Management#
kubeadm clusters issue their own certificates with a one-year expiry. Nobody remembers this until the cluster stops serving traffic at 9am on a random Tuesday.
kubeadm certs check-expiration
kubeadm certs renew allRenewing writes new certs to /etc/kubernetes/pki - the static pods must then be restarted to pick them up. The fastest way is to touch the manifest files and let the kubelet re-create the pods:
mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/ && mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/Worker kubelet certs also expire.
kubeadm certs renew kubelet-client-currenton the node, or configure kubelet certificate rotation and never think about it again.
How to Upgrade the Control Plane#
- Take a backup and make sure you could restore it:
etcdctl ... snapshot save /var/backups/etcd-$(date +%F).db
etcdctl snapshot status /var/backups/etcd-$(date +%F).db -w table- See what you’re allowed to move to:
apt-mark unhold kubeadm
apt-get update && apt-get install -y kubeadm=1.31.*-1.1
kubeadm upgrade planupgrade plan prints the current and target versions and warns about anything unsupported. Read it.
- Apply the control-plane upgrade:
kubeadm upgrade apply v1.31.0On additional control-plane nodes (see ha-control-plane), use kubeadm upgrade node instead - only the first node runs apply.
- Upgrade kubelet and kubectl, then restart kubelet:
apt-mark unhold kubelet kubectl
apt-get install -y kubelet=1.31.*-1.1 kubectl=1.31.*-1.1
apt-mark hold kubelet kubectl kubeadm
systemctl daemon-reload && systemctl restart kubelet- Verify before touching workers:
kubectl get nodes
kubectl get pods -n kube-system
kubectl versionSTATUS = Ready, all kube-system pods running, no version skew surprises.
How to Upgrade Worker Nodes#
- Cordon and drain - the pod-priority-disruption notes explain why this can stall:
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data- On the node, same dance as the control plane, minus
upgrade apply:
apt-mark unhold kubeadm
apt-get update && apt-get install -y kubeadm=1.31.*-1.1
kubeadm upgrade node
apt-mark unhold kubelet
apt-get install -y kubelet=1.31.*-1.1
apt-mark hold kubelet kubeadm
systemctl daemon-reload && systemctl restart kubelet- Back on the control plane, uncordon and move to the next node:
kubectl uncordon <node-name>
kubectl get nodes- Repeat one node at a time. Never drain two at once unless your PDB and capacity say you can.
How to Restore etcd from a Snapshot#
The day the backup earns its keep:
- Stop the static pod from fighting you - move the apiserver and etcd manifests aside:
mv /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/kube-etcd.yaml /root/- Move the old data dir out of the way (do not delete it until the restore is verified):
mv /var/lib/etcd /var/lib/etcd.broken- Restore the snapshot into a fresh data dir:
ETCDCTL_API=3 etcdctl snapshot restore /var/backups/etcd-2025-01-01.db \
--data-dir=/var/lib/etcd \
--name=<node-name> \
--initial-cluster=<node-name>=https://<node-ip>:2380 \
--initial-advertise-peer-urls=https://<node-ip>:2380The --name and cluster URLs must match the member’s identity in the snapshot - restoring with the wrong name is how you end up with an etcd that answers but has no leader.
- Put the manifests back and watch the cluster return:
mv /root/kube-apiserver.yaml /root/kube-etcd.yaml /etc/kubernetes/manifests/
crictl ps
kubectl get nodes- Verify the data is the data you expected - a specific namespace, a specific deployment. Then and only then, remove
/var/lib/etcd.broken.
On a multi-member etcd cluster (HA notes), restoring one member from snapshot is a membership operation: the restored member rejoins the cluster and syncs, or you rebuild the member entirely.
etcdctl member listbefore you touch anything.
How to Renew Certificates#
- See what’s near expiry:
kubeadm certs check-expiration- Renew everything at once:
kubeadm certs renew all- Restart the static pods so they load the new certs - either touch the manifests as shown above, or reboot the node. Then confirm:
kubectl get --raw=/healthz
kubeadm certs check-expiration- Distribute the updated
admin.confto anyone holding an old kubeconfig, and update workers’ kubelet certs if you use the client cert flow. A renewed CA cert (only onkubeadm certs renew ca, which you should almost never do) invalidates every cert in the cluster - treat that as a disaster recovery scenario, not maintenance.
özetle - sıralama: backup → upgrade plan → upgrade apply (CP) → node’ları tek tek drain+upgrade+uncordon → certs check-expiration takvime not et. Bu sırayı bozmadığın sürece upgrade bir bakım işlemidir, zar atmak değil.
for more ha-control-plane