> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Demo Kubeadm Configuring Cluster With Kubeadm

> Step by step guide to build a three node Kubernetes cluster with kubeadm on Ubuntu using containerd and Calico, including commands, configuration, joining workers and basic validation.

In this lesson you'll build a simple Kubernetes cluster with kubeadm across three Ubuntu VMs: one control-plane node and two worker nodes. The steps follow the official Kubernetes documentation:

* Bootstrapping Clusters with kubeadm → Creating a Cluster with kubeadm
* Installing kubeadm / kubelet / kubectl

This guide shows the exact commands to run on each node and explains why each step is needed. It uses:

* Ubuntu 22.04 LTS VMs (`controlplane`, `node01`, `node02`)
* containerd as the container runtime
* Calico as the CNI plugin
* kubeadm/kubelet/kubectl installed from the official apt repository

<Callout icon="lightbulb" color="#1CB2FE">
  Before you begin: follow the official kubeadm docs while performing the steps:

  * [Bootstrapping Clusters with kubeadm](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/) → [Creating a Cluster with kubeadm](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/)
  * [Installing kubeadm / kubelet / kubectl](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)
</Callout>

***

## Environment (used in this demo)

| Item | Details |
| - | - |
| OS | Ubuntu 22.04 LTS |
| Nodes | `controlplane`, `node01`, `node02` |
| Container runtime | `containerd` |
| CNI plugin | Calico |
| Kubernetes packages | `kubeadm`, `kubelet`, `kubectl` from `apt.kubernetes.io` |

***

## Overview — Main steps

1. Install required packages and prerequisites.
2. Load kernel modules and set sysctl params required by Kubernetes.
3. Install and configure containerd with `SystemdCgroup = true`.
4. Install `kubeadm`, `kubelet`, and `kubectl`.
5. Ensure kubelet binds to the node IP used for cluster traffic.
6. Initialize the control-plane with `kubeadm init`.
7. Install a CNI plugin (Calico in this lesson).
8. Join worker nodes to the cluster.
9. Validate with an NGINX Deployment and a NodePort service.

***

## 1 — Install basic utilities on all nodes

Run on `controlplane`, `node01`, and `node02`:

```bash theme={null}
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
```

These utilities are required to add repositories, fetch manifests, and download packages.

***

## 2 — Load kernel modules and set sysctl parameters

Kubernetes requires packet forwarding and bridge network settings. Apply these on all nodes:

```bash theme={null}
# Persistently load modules
cat <<'EOF' | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# Persistently set sysctl params required by Kubernetes
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 sysctl settings immediately
sudo sysctl --system
```

These settings ensure iptables sees bridged traffic and IPv4 forwarding is enabled.

***

## 3 — Install and configure containerd

Install `containerd` and configure it to use systemd cgroups (recommended):

```bash theme={null}
# Install containerd and runc
sudo apt-get update
sudo apt-get install -y containerd runc

# Create containerd config and set SystemdCgroup = true
sudo mkdir -p /etc/containerd
containerd config default | sed 's/SystemdCgroup = false/SystemdCgroup = true/' | sudo tee /etc/containerd/config.toml

# Restart containerd to apply changes
sudo systemctl restart containerd
```

Verify the setting:

```bash theme={null}
grep -i systemd /etc/containerd/config.toml
# expected output: SystemdCgroup = true
```

<Callout icon="lightbulb" color="#1CB2FE">
  Why `SystemdCgroup = true`? Kubelet commonly uses the systemd cgroup driver on modern Kubernetes setups. Matching containerd avoids a cgroup driver mismatch between container runtime and kubelet.
</Callout>

***

## 4 — Install kubeadm, kubelet and kubectl

Add the Kubernetes apt repository and install the packages on all nodes:

```bash theme={null}
# Add Kubernetes apt repository key and repo
curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg

echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list

# Install kubelet, kubeadm, kubectl
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

# Ensure kubelet is enabled (it may be inactive until configured)
sudo systemctl enable --now kubelet
```

Optional: install `crictl`/`cri-tools` to inspect CRI containers:

```bash theme={null}
sudo apt-get install -y cri-tools
# Configure crictl for containerd
sudo crictl config --set runtime-endpoint=unix:///run/containerd/containerd.sock \
                  --set image-endpoint=unix:///run/containerd/containerd.sock
```

`crictl` is helpful for troubleshooting container runtime issues when Kubernetes API is unreachable.

***

## 5 — Ensure kubelet binds to the correct node IP (multi-interface environments)

In multi-interface VMs kubelet might pick the wrong IP. If your environment exposes a `PRIMARY_IP` environment variable per VM (pointing to the intended cluster interface), configure kubelet to use it:

On each node (`controlplane`, `node01`, `node02`):

```bash theme={null}
cat <<'EOF' | sudo tee /etc/default/kubelet
KUBELET_EXTRA_ARGS="--node-ip ${PRIMARY_IP}"
EOF

sudo systemctl daemon-reload
sudo systemctl restart kubelet
```

Verify `PRIMARY_IP` (example):

```bash theme={null}
echo "$PRIMARY_IP"
# expected: 192.168.86.48  (example)
```

If you don't have `PRIMARY_IP`, determine the correct interface with `ip addr` / `ip route` and replace `${PRIMARY_IP}` with that IP.

***

## 6 — Initialize the control-plane node

Run `kubeadm init` on the control-plane node only. Provide the API server advertise address and the pod network CIDR that matches your CNI (Calico expects you to configure its ipPool accordingly). Example:

```bash theme={null}
# Set values (example)
POD_CIDR="10.244.0.0/16"
SERVICE_CIDR="10.96.0.0/12"

sudo kubeadm init \
  --apiserver-advertise-address="${PRIMARY_IP}" \
  --pod-network-cidr="${POD_CIDR}" \
  --service-cidr="${SERVICE_CIDR}"
```

On success kubeadm prints:

* Commands to configure kubectl for your user (copy `/etc/kubernetes/admin.conf`)
* The `kubeadm join ...` command for worker nodes

Configure kubectl for the current (non-root) user:

```bash theme={null}
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
chmod 600 $HOME/.kube/config
```

Check the control plane status (note: the controlplane node may be `NotReady` until a CNI is installed):

```bash theme={null}
kubectl get nodes
# expected: a node named 'controlplane' (may be NotReady until CNI applied)
```

<Callout icon="warning" color="#FF6B6B">
  Protect the `admin.conf` and the `kubeadm join` token. Anyone with these can gain access to the cluster. If a token is lost, recreate it with `sudo kubeadm token create --print-join-command`.
</Callout>

***

## 7 — Install the CNI plugin (Calico example)

Calico needs to know the pod CIDR. Download the Calico custom resources and set the `ipPool` CIDR to match the `POD_CIDR` used during `kubeadm init` (`10.244.0.0/16` in this example):

On the control-plane node:

```bash theme={null}
# Download a specific Calico version used in the demo
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.30.1/manifests/custom-resources.yaml

# Edit custom-resources.yaml: set the ipPool cidr to match POD_CIDR (e.g., 10.244.0.0/16)
# Then apply it:
kubectl create -f custom-resources.yaml
```

Depending on the Calico release you may also need to apply the main Calico manifests; follow the official docs: [https://docs.tigera.io/calico/latest/getting-started/on-premises/](https://docs.tigera.io/calico/latest/getting-started/on-premises/).

Wait until Calico and core system pods are healthy:

```bash theme={null}
kubectl get pods -n kube-system -o wide
kubectl get nodes
# expected: 'controlplane' should transition to Ready once the CNI and core components are up
```

***

## 8 — Join worker nodes

When `kubeadm init` completed it printed a `kubeadm join` command. If you lost it, recreate it on the control-plane node:

```bash theme={null}
sudo kubeadm token create --print-join-command
```

Run the printed `kubeadm join` command with `sudo` on each worker node (`node01`, `node02`). Example (your token/hash will differ):

```bash theme={null}
sudo kubeadm join 192.168.86.48:6443 --token rj4yuz.s8x9lwovesze1gxc \
  --discovery-token-ca-cert-hash sha256:39dfd68f1dbdc5c92c03d3511be46e3316601a794e578a7ad69c6d28f118f9b9
```

Back on the control-plane node, verify all nodes are Ready:

```bash theme={null}
kubectl get nodes -o wide
# expected: controlplane, node01, node02 with STATUS Ready
```

If a node is briefly `NotReady`, wait for the CNI pods to be scheduled and become healthy.

***

## 9 — Quick test: deploy NGINX and expose it via NodePort

Create a Deployment and expose it via a NodePort (example uses node port `30143`):

```bash theme={null}
kubectl create deployment nginx --image=nginx:alpine
kubectl expose deployment nginx --type=NodePort --name=nginx-svc --port=80 --target-port=80 --node-port=30143
```

Find the node IPs:

```bash theme={null}
kubectl get nodes -o wide
```

From any node or a machine that can reach the nodes, curl the NodePort:

```bash theme={null}
curl http://<node-ip>:30143
# expected: HTML response from NGINX ("Welcome to nginx!")
```

If the NGINX page is reachable via the node IP and NodePort, cluster networking and services are functioning correctly.

***

## Troubleshooting notes and exam tips

<Callout icon="lightbulb" color="#1CB2FE">
  * Recreate a lost join command on the control-plane node:
    ```bash theme={null}
    sudo kubeadm token create --print-join-command
    ```
  * If nodes remain `NotReady`, check:
    1. `containerd` is running and configured with systemd cgroups (`SystemdCgroup = true` in `/etc/containerd/config.toml`),
    2. kubelet is bound to the correct `--node-ip`,
    3. the CNI is installed and its pod network CIDR matches the one passed to `kubeadm init`.
  * For certification exams: you will have the official docs available. Practice navigating:
    * [Bootstrapping Clusters with kubeadm](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/)
    * [Creating a Cluster with kubeadm](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/)
    * [Installing kubeadm](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)
</Callout>

***

This completes the kubeadm-based cluster installation and validation. You can substitute a different container runtime or CNI plugin: the kubeadm `init`/`join` flow remains the same — only the runtime or network plugin installation steps differ.

## Links and References

* [Bootstrapping Clusters with kubeadm](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/)
* [Creating a Cluster with kubeadm](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/)
* [Installing kubeadm / kubelet / kubectl](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)
* [Calico Getting Started (On-Premises)](https://docs.tigera.io/calico/latest/getting-started/on-premises/)

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/kubernetes-for-the-absolute-beginners-hands-on-tutorial/module/d62e1702-30be-44bb-87da-49bb2a18e406/lesson/8ba8fd51-ccbd-4055-b39a-4a3e66d33645" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.