For this tutorial, we'll use a 3-node MicroK8s cluster with nodes k8s-1, k8s-2, and k8s-3.
microk8s kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-1 Ready <none> 142d v1.35.6
k8s-2 Ready <none> 142d v1.35.6
k8s-3 Ready <none> 142d v1.35.6
Upgrade the first node
To upgrade a node, we must cordon it first so new pods cannot be scheduled on it.
microk8s kubectl cordon k8s-1
node/k8s-1 cordoned
If we get the node list again, we can see that it now has pod scheduling disabled.
microk8s kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-1 Ready,SchedulingDisabled <none> 142d v1.35.6
k8s-2 Ready <none> 142d v1.35.6
k8s-3 Ready <none> 142d v1.35.6
Next, we must remove the remaining pods on this node by draining it.
microk8s kubectl drain k8s-1 --ignore-daemonsets --delete-emptydir-data
node/k8s-1 already cordoned
Warning: ignoring DaemonSet-managed Pods: ingress/traefik-zgvnx, kube-system/calico-node-2nlwf
evicting pod sertxudev/sertxudev-worker-86d556b9cd-f5nhw
evicting pod postgresql/postgresql-cluster-3
evicting pod sertxudev/sertxudev-app-7d46bdbbcc-977lf
evicting pod sertxudev/sertxudev-scheduler-5996645758-8fsht
pod/postgresql-cluster-3 evicted
We can check that our node no longer has pods, except from the required base ones.
microk8s kubectl get pods -A --field-selector spec.nodeName=k8s-1
NAMESPACE NAME READY STATUS RESTARTS AGE
ingress traefik-zgvnx 1/1 Running 0 142d
kube-system calico-node-2nlwf 1/1 Running 0 17d
Now that our node is empty, we can proceed with the upgrade. It's recommended to upgrade one minor version at a time. You can review the latest MicroK8s version on GitHub releases
.
Ensure you're connected to the node you want to upgrade. Previous commands can be run from any K8s node, but
snap refreshshould be run in the node you want to upgrade.
snap refresh microk8s --channel 1.36/stable
microk8s (1.36/stable) v1.36.2 from Canonical✓ refreshed
Once the MicroK8s snap has been upgraded, we can let K8s start creating pods in our node by uncordoning it.
microk8s kubectl uncordon k8s-1
node/k8s-1 uncordoned
If we check the cluster nodes again, we can see that our current node has been updated.
kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-1 Ready <none> 142d v1.36.2
k8s-2 Ready <none> 142d v1.35.6
k8s-3 Ready <none> 142d v1.35.6
Once the upgraded node is ready to receive pods again, we can begin to upgrade the next node. By running the same commands we've just seen for the next node until all nodes are upgraded.
Rebalance deployments
Once our cluster is up-to-date, we might want to rebalance our deployments so pods are spread among all nodes.
kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-1 Ready <none> 142d v1.36.2
k8s-2 Ready <none> 142d v1.36.2
k8s-3 Ready <none> 142d v1.36.2
First, we should get the deployments that our cluster hosts.
microk8s kubectl get deployments -A
NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE
cert-manager cert-manager 1/1 1 1 441d
cert-manager cert-manager-cainjector 1/1 1 1 441d
cert-manager cert-manager-webhook 1/1 1 1 441d
cnpg-system barman-cloud 1/1 1 1 333d
cnpg-system cnpg-controller-manager 1/1 1 1 333d
kube-system calico-kube-controllers 1/1 1 1 454d
kube-system coredns 1/1 1 1 454d
kube-system hostpath-provisioner 1/1 1 1 448d
kube-system metrics-server 1/1 1 1 444d
sertxudev sertxudev-app 2/2 2 2 200d
sertxudev sertxudev-scheduler 1/1 1 1 420d
sertxudev sertxudev-worker 1/1 1 1 420d
Next, we restart the rollout of each deployment we want to rebalance.
kubectl rollout restart deployment -n sertxudev sertxudev-app
kubectl rollout restart deployment -n sertxudev sertxudev-scheduler
kubectl rollout restart deployment -n sertxudev sertxudev-worker
deployment.apps/sertxudev-app restarted
deployment.apps/sertxudev-scheduler restarted
deployment.apps/sertxudev-worker restarted
Now we have an up-to-date MicroK8s cluster ready to continue serving traffic as usual.
Top comments (1)
This is a clear rolling-upgrade walkthrough, and the one-node-at-a-time flow matches the recommended MicroK8s approach.
I would add a few production safeguards around the commands, though. Before starting, verify a restorable cluster-database and application backup, review both Kubernetes and MicroK8s release notes, and confirm PodDisruptionBudgets and spare capacity. I would also avoid presenting --delete-emptydir-data as a default flag, since it explicitly permits local emptyDir data loss. After snap refresh, I would not uncordon immediately. First wait for MicroK8s to become ready, verify node and control-plane health, CNI/DNS, system pods, storage, and a small workload smoke test. It would also be useful to document microk8s inspect and sudo snap revert microk8s as the rollback path. One nuance on the final “rebalance” step: restarting deployments recreates pods, but it does not guarantee an even distribution unless the workloads define topology spread constraints or pod anti-affinity. Otherwise the scheduler may place the replacements unevenly again. Nice practical guide overall; adding preflight, validation and rollback gates would turn it into a stronger production runbook.