All writing
Updated 5 min readNaren

Building a cluster with kubeadm

Prepare the nodes, install a container runtime and the Kubernetes packages, bootstrap the control plane, add a network plugin and join the workers. Then what each core component does.

The last post covered persistent data. Up to now this series has run on a ready-made local cluster. This post builds the cluster itself with kubeadm, then goes through the core components that make it work. All of it is material for the CKA exam.

kubeadm

Control Plane Node Worker Node 1 Worker Node 2 uses connects to uses connects to outputs kubeadm init Cluster Configuration Control Plane Components etcd, API Server, Scheduler, Controller Manager Join Token kubeadm join Kubelet starts kubeadm join Kubelet starts

You can set up a Kubernetes cluster by hand, but kubeadm is the official tool for bootstrapping a minimum viable, production-ready cluster. It automates many of the complicated steps.

Before you start, on every node

Every machine in the cluster, virtual or physical, needs:

  • a compatible Linux distribution, such as Ubuntu, CentOS or Debian
  • at least 2GB of RAM (4GB or more is better), and at least 2 CPUs on the control-plane node
  • full network connectivity to all the other machines
  • a unique hostname, MAC address and product_uuid

Kubernetes does not work well with swap memory. Disable it now, and on every boot:

sudo swapoff -a # Disable immediately
sudo sed -i '/ swap / s/^/#/' /etc/fstab # Disable on startup

Then enable IPv4 packet forwarding and bridged traffic:

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# sysctl params required by setup, params persist across reboots
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

sudo sysctl --system # Apply sysctl params without reboot

Step 1: Install a container runtime, on every node

Kubernetes needs a container runtime, such as containerd or CRI-O, to run containers. containerd is a popular choice, and the one used here.

# (Install containerd)
sudo apt-get update
sudo apt-get install -y containerd

# Configure containerd
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml

# Set the cgroup driver to systemd
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

# Restart containerd
sudo systemctl restart containerd

Step 2: Install kubeadm, kubelet and kubectl, on every node

These are the three core Kubernetes tools:

  • kubeadm bootstraps the cluster.
  • kubelet is the agent that runs on each node and talks to the control plane.
  • kubectl is the command-line tool for working with the cluster.
# Update the apt package index and install packages needed for Kubernetes apt repository
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg

# Download the public signing key for the Kubernetes package repositories
sudo mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

# Add the Kubernetes apt repository (replace v1.30 with your desired Kubernetes version)
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list

# Update apt package index, install kubelet, kubeadm and kubectl, and pin their version
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl # Prevent automatic updates

# Enable kubelet service
sudo systemctl enable --now kubelet

Step 3: Initialize the control plane, on the control-plane node only

This step bootstraps the Kubernetes control plane. It needs the IP address of your control-plane node and a Pod network CIDR, such as 192.168.0.0/16 for Calico.

# Replace <control-plane-ip> with the actual IP address of your control plane node
# and adjust --pod-network-cidr if you plan to use a different CNI.
sudo kubeadm init --apiserver-advertise-address=<control-plane-ip> --pod-network-cidr=192.168.0.0/16

When it succeeds, kubeadm prints the commands that configure kubectl, followed by a kubeadm join command for the worker nodes. Save the kubeadm join command. You need it for every worker node.

Configure kubectl access on the control-plane node:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Step 4: Install a Pod network add-on, on the control-plane node only

Pods on different nodes cannot talk to each other until a Container Network Interface (CNI) plugin is installed. Calico is a popular choice. Version 3.28 is tested with Kubernetes 1.30:

# Install Calico (example)
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico.yaml

Step 5: Join the worker nodes, on each worker node

On each worker node, run the kubeadm join command you saved in step 3:

# Example command (replace with your actual command from Step 3 output)
sudo kubeadm join <control-plane-ip>:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>

Step 6: Verify the cluster, on the control-plane node

Check the status of your nodes and Pods:

kubectl get nodes
kubectl get pods --all-namespaces

Nodes can show NotReady until the network plugin is fully deployed. After that, every node should show Ready.

The core components

With the cluster running, here is what each core component does and what to watch on it. They split into the control plane and the worker nodes.

Control Plane Worker Node talks to talks to watches watches talks to manages manages network for API Server etcd Scheduler Controller Manager Kubelet Kube-Proxy Container Runtime Pod

The control plane

The control plane keeps your cluster in its desired state. It usually runs on one or more dedicated machines. These are sometimes called master nodes, though control plane is the preferred term.

kube-apiserver, the API server, is the front door to the cluster. All communication goes through it, whether it comes from kubectl, other components or the worker nodes, and it validates and processes every API request. In production, make it highly available with several replicas behind a load balancer, and monitor its latency and error rates.

etcd is the cluster's consistent, highly available key-value store. It is the single source of truth for all cluster data and desired state, so the cluster's health depends on it. Back it up regularly, and monitor its read and write latency and its database size. In highly available setups, etcd usually runs as a quorum of 3 or 5 members.

kube-scheduler, the scheduler, watches for newly created Pods that have no node yet and picks the best node for each one, taking resource requirements, policies and other constraints into account. It mostly manages itself, but its performance affects how quickly Pods start.

kube-controller-manager, the controller manager, runs controllers that keep watching the cluster's shared state and move its current state toward the desired state. The node controller notices when nodes go down. The replication controller makes sure the desired number of Pod replicas are running. The endpoint controller fills in the Endpoints objects for Services. Watch its logs for errors, and make sure all the controllers are healthy.

cloud-controller-manager is optional. It connects Kubernetes to the cloud provider's API, for example to provision load balancers and managed disks. It only exists in cloud-hosted clusters, and its health depends on that cloud integration.

Worker nodes

Worker nodes are the machines that run your containerized applications, the Pods.

kubelet is an agent on each node. It talks to the API server, receives Pod specifications, and makes sure the containers those Pods describe are running and healthy. Make sure it is running and healthy on every worker node, and watch its logs for issues.

kube-proxy is a network proxy on each node. It maintains the network rules that let traffic reach your Pods from inside or outside the cluster, and it handles the Service abstraction and load balancing across Pods. Problems with it turn into network connectivity problems for your applications, so make sure it is running correctly.

The container runtime, such as containerd or CRI-O, is the software that runs the containers. Make sure it is installed, configured correctly and running.

Good practices

For production, always run several control-plane nodes behind a load balancer, so there is no single point of failure.

Monitor every control-plane and worker component. Track CPU and memory use, network traffic, API server latency and etcd performance, and centralize the logs from all components so troubleshooting is easier.

Secure access to the API server, for example with private endpoints and strict RBAC, and audit the component configurations regularly.

Back up etcd regularly. It holds the state of the entire cluster.

Keep the cluster's components updated, so you get new features, bug fixes and security patches.

The next post continues with cluster maintenance: upgrades and backups. Upgrading a cluster and backing up etcd.