🇧🇷 Leia a versão em português aqui
In previous parts, we prepared the environment (Part 1), installed containerd and Kubernetes (Part 2), and initialized the control-plane on the master server, including the pod network (Part 3). Now let's close the basic cluster lifecycle: join the worker servers to the master and confirm everything is working correctly.
Joining the worker nodes
On each worker server you want to add to the cluster, run the kubeadm join command generated at the end of the master initialization (seen in Part 3). It looks like this:
kubeadm join 10.0.10.100:6443 --token 9e0xeu.s0if3... --discovery-token-ca-cert-hash sha256:3a328e56729515d...
This command uses the token and the certificate hash to authenticate the worker with the control-plane and safely allow it to join the cluster.
What if I lost the token?
kubeadm tokens expire by default after 24 hours. If you didn't save the token (or it has already expired), no problem — you can generate a new join command at any time, from the master:
# kubeadm token create --print-join-command
This creates a new token and returns the full join command, ready to be copied and run on the workers.
Checking the cluster nodes
After running kubeadm join on all the workers, go back to the master and check whether the nodes were added correctly:
kubectl get nodes
Or, for a more detailed output (internal IP address, kernel version, container runtime, etc.):
kubectl get nodes -o wide
The
-o wideflag adds extra information to the standard output.
The output should look like this:
All nodes should show up with Ready status. If any worker shows up as NotReady, it's worth checking whether the pod network (CNI) was applied correctly and whether containerd is running without errors on that node.
Dealing with unwanted taints
In some cases, a node may end up marked with a taint that prevents new pods from being scheduled on it — for example, when Kubernetes detects disk pressure (disk-pressure). This is common in lab environments with small disks or little free space.
To check whether a specific node has this taint (in the example below, node3):
kubectl --kubeconfig=/home/celso/.kube/config.bagarote describe node node3 | grep Taint
If the disk-pressure taint is present and you want to manually remove that restriction (being aware that the node might be low on disk space), run:
kubectl --kubeconfig=/home/celso/.kube/config.bagarote taint node node3 node.kubernetes.io/disk-pressure:NoSchedule-
The trailing
-at the end of the command is what removes the taint — without it, the command would add a new taint instead of removing one.⚠️ Manually removing a
disk-pressuretaint is a stopgap measure. The right approach is to investigate and fix the root cause (free up disk space, add more storage, etc.), since Kubernetes applies this taint as a protection against failures caused by lack of space.
Closing the loop
With the workers joined and all nodes in Ready state, the on-premise Kubernetes cluster is officially up: control-plane running, pod network active, and workers ready to receive workloads.
This wraps up the foundation of this series — environment preparation, component installation, master initialization, and worker joining. From here, the cluster is ready for the natural next steps of any Kubernetes environment: application deployments, persistent storage configuration, ingress controllers, monitoring, and more.
If you've followed this series up to this point, you now have a functional on-premise Kubernetes cluster, built from scratch, without depending on any cloud provider.
Now, in Part 5 of this series, we will spin up containers and test whether everything is working and handling the workload normally.
Continued in Part 5.

Top comments (0)