A comprehensive guide to setting up a Kubernetes cluster with master and worker nodes using kubeadm.
- π Kubernetes Starter Guide
This guide provides a complete walkthrough for setting up a production-ready Kubernetes cluster using kubeadm. You'll learn how to configure both master (control-plane) and worker nodes step by step.
- Control Plane Node: Manages the cluster, runs API server, scheduler, and controller manager
- Worker Nodes: Run your application pods and workloads
- Network Plugin: Calico CNI for pod networking and network policies
Before starting, ensure you have:
| Requirement | Description |
|---|---|
| OS | Ubuntu 20.04/22.04 LTS |
| RAM | Minimum 2GB (4GB+ recommended) |
| CPU | 2+ cores |
| Network | Private network connectivity between nodes |
| Privileges | sudo access on all nodes |
β οΈ Important: Disable swap on all nodes as Kubernetes requires it to be off.
π― Run on: Master + Worker nodes
# Disable swap temporarily
sudo swapoff -a
# Make it permanent by commenting out swap entries
sudo sed -i '/ swap / s/^/#/' /etc/fstab
# Verify swap is disabled
free -h# Load required kernel modules
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# Verify modules are loaded
lsmod | grep br_netfilter
lsmod | grep overlay# Set sysctl parameters for Kubernetes networking
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
# Apply changes without reboot
sudo sysctl --system
# Verify the configuration
sudo sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forwardπ― Run on: Master + Worker nodes
# Update package index
sudo apt-get update
# Install dependencies
sudo apt-get install -y ca-certificates curl gnupg lsb-release
# Add Docker GPG key
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add Docker repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Update package index and install containerd
sudo apt-get update
sudo apt-get install -y containerd.io# Generate default configuration
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
# Enable systemd cgroups
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# Restart and enable containerd
sudo systemctl restart containerd
sudo systemctl enable containerd
# Verify containerd is running
sudo systemctl status containerdπ― Run on: Master + Worker nodes
# Install required packages
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
# Add Kubernetes GPG key
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | \
sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
# Add Kubernetes repository
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] \
https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /" | \
sudo tee /etc/apt/sources.list.d/kubernetes.list > /dev/null# Update package index
sudo apt-get update
# Install kubelet, kubeadm, and kubectl
sudo apt-get install -y kubelet kubeadm kubectl
# Hold packages to prevent automatic updates
sudo apt-mark hold kubelet kubeadm kubectl
# Enable kubelet service
sudo systemctl enable kubeletπ― Run on: Master node only
# Initialize the cluster
sudo kubeadm init --pod-network-cidr=192.168.0.0/16
# π‘ Save the join command that appears at the end!
# It will look like: kubeadm join <IP>:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash># Set up kubeconfig for the current user
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# Test kubectl access
kubectl get nodes# Install Calico CNI
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.0/manifests/calico.yaml
# Wait for pods to be ready (this may take a few minutes)
kubectl get pods -n kube-system
# Check node status
kubectl get nodes# Generate a new join command for worker nodes
kubeadm token create --print-join-commandπ― Run on: Worker nodes only
# Reset the node if it was previously part of a cluster
sudo kubeadm reset
# Clean up
sudo rm -rf /etc/cni/net.d
sudo ipvsadm --clear# Use the join command from the master node (example)
sudo kubeadm join <MASTER-IP>:6443 \
--token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH> \
--v=5
# Example:
# sudo kubeadm join 192.168.1.100:6443 \
# --token abcdef.0123456789abcdef \
# --discovery-token-ca-cert-hash sha256:1234567890abcdef... \
# --v=5π― Run on: Master node
# Check all nodes
kubectl get nodes -o wide
# Check system pods
kubectl get pods -n kube-system
# Check cluster info
kubectl cluster-info
# Check cluster components
kubectl get componentstatuses# Create a test deployment
kubectl create deployment nginx-test --image=nginx
# Expose the deployment
kubectl expose deployment nginx-test --port=80 --type=NodePort
# Check the service
kubectl get services nginx-test
# Scale the deployment
kubectl scale deployment nginx-test --replicas=3
# Verify pods are distributed across nodes
kubectl get pods -o wide| Command | Description |
|---|---|
kubectl get nodes |
View cluster nodes |
kubectl get pods -A |
View all pods across namespaces |
kubectl get services |
View services |
kubectl describe node <node-name> |
Get detailed node information |
kubectl logs <pod-name> |
View pod logs |
kubectl exec -it <pod-name> -- /bin/bash |
Access pod shell |
# Check kubelet logs
sudo journalctl -u kubelet -f
# Check cluster events
kubectl get events --sort-by=.metadata.creationTimestamp
# Reset a node completely
sudo kubeadm reset
sudo rm -rf /etc/cni/net.d
sudo ipvsadm --clear
# Regenerate join token
kubeadm token create --print-join-commandkubectl get pods -A
π Node Status Shows "NotReady"
Cause: Usually indicates network plugin issues.
Solution:
# Check if Calico pods are running
kubectl get pods -n kube-system | grep calico
# If pods are not running, reinstall Calico
kubectl delete -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.0/manifests/calico.yaml
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.0/manifests/calico.yamlπ "Connection Refused" Error
Cause: API server not accessible or kubeconfig issues.
Solution:
# Check if API server is running
sudo systemctl status kubelet
# Verify kubeconfig
kubectl config view
# Reconfigure kubeconfig
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/configπ Worker Node Join Fails
Cause: Token expired, network connectivity, or firewall issues.
Solution:
# Generate new token on master
kubeadm token create --print-join-command
# Check connectivity from worker to master
telnet <master-ip> 6443
# Reset worker node and try again
sudo kubeadm reset| Port | Protocol | Direction | Purpose |
|---|---|---|---|
| 6443 | TCP | Inbound | Kubernetes API server |
| 2379-2380 | TCP | Inbound | etcd server client API |
| 10250 | TCP | Inbound | kubelet API |
| 10259 | TCP | Inbound | kube-scheduler |
| 10257 | TCP | Inbound | kube-controller-manager |
Made with β€οΈ for the Kubernetes community
Note: This guide is tested on Ubuntu 20.04/22.04. For other distributions, package installation commands may differ.