> ## 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.

# Lab Solution Install a Kubernetes Cluster using kubeadm

> Step by step guide to install and configure a Kubernetes cluster with kubeadm, including node setup, initializing control plane, joining a worker, and deploying Flannel network

This guide walks you through installing kubeadm, kubelet, and kubectl on a control plane and a single worker node (node01), initializing the control plane, joining the worker, and deploying the Flannel pod network. Follow the steps in order on both the control plane and node01 where indicated.

Contents

* 1. Prepare kernel networking settings (both nodes)
* 2. Add the Kubernetes apt repository (both nodes)
* 3. Install kubeadm, kubelet, kubectl (pinned version) (both nodes)
* 4. Before initialization: kubectl on control plane
* 5. Initialize the control plane with kubeadm (control plane only)
* 6. Join node01 to the cluster (node01 only)
* 7. Install a pod network (Flannel) (control plane)
* Summary and Troubleshooting
* Links and references

***

## 1) Prepare kernel networking settings (both nodes)

Kubernetes requires bridged network traffic to be handled by iptables and IP forwarding to be enabled. Create a sysctl config file (for example `/etc/sysctl.d/k8s.conf`) with the required keys:

```bash theme={null}
# /etc/sysctl.d/k8s.conf (create as root)
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
```

Apply the settings immediately (no reboot required):

```bash theme={null}
sudo sysctl --system
```

You should see lines indicating the k8s settings were applied, e.g.:

```text theme={null}
* Applying /etc/sysctl.d/k8s.conf ...
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
```

<Callout icon="warning" color="#FF6B6B">
  Kubeadm requires swap to be disabled on all nodes. Disable swap now and make it persistent across reboots before running `kubeadm init` / `kubeadm join`:

  ```bash theme={null}
  # disable swap for current session
  sudo swapoff -a

  # make swap off persistent across reboots by commenting out swap lines in /etc/fstab
  sudo sed -i.bak -r 's/^([^#].*\sswap\s.*)$/#\1/' /etc/fstab
  ```
</Callout>

<Callout icon="lightbulb" color="#1CB2FE">
  Kubernetes needs an OCI runtime (for example containerd or Docker) installed and running on all nodes before initializing the control plane. One quick approach is to install and configure `containerd` with the systemd cgroup driver:

  ```bash theme={null}
  sudo apt-get update
  sudo apt-get install -y containerd
  sudo mkdir -p /etc/containerd
  sudo containerd config default | sudo tee /etc/containerd/config.toml
  # Ensure containerd uses the systemd cgroup driver
  sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
  sudo systemctl restart containerd
  sudo systemctl enable containerd
  ```

  Confirm the runtime is active before running `kubeadm init`/`join`.
</Callout>

***

## 2) Add the Kubernetes apt repository (both nodes)

Install prerequisites, create the keyrings directory (if missing), fetch the GPG key, and add the Kubernetes apt repository:

```bash theme={null}
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
sudo mkdir -p /etc/apt/keyrings
sudo curl -fsSLo /etc/apt/keyrings/kubernetes-archive-keyring.gpg \
  https://packages.cloud.google.com/apt/doc/apt-key.gpg

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

sudo apt-get update
```

If `apt` warns about duplicate package sources (e.g., "Target Packages ... is configured multiple times"), that indicates duplicate entries in `/etc/apt/sources.list` or `/etc/apt/sources.list.d/`. These warnings can be cleaned up but do not block installation.

<Callout icon="lightbulb" color="#1CB2FE">
  On older Debian/Ubuntu releases the directory `/etc/apt/keyrings` may not exist; creating it prevents `curl` from failing when writing the keyring (as shown above).
</Callout>

***

## 3) Install kubeadm, kubelet, kubectl (pinned version) (both nodes)

Pinning the Kubernetes packages prevents unexpected upgrades that can break cluster compatibility. This lab uses Kubernetes v1.26.0 — adjust `K8S_VERSION` if your environment requires a different release.

```bash theme={null}
# Replace the version string if required by your lab
K8S_VERSION=1.26.0-00

sudo apt-get install -y kubeadm=${K8S_VERSION} \
                        kubelet=${K8S_VERSION} \
                        kubectl=${K8S_VERSION}

sudo apt-mark hold kubeadm kubelet kubectl
```

Verify kubelet is installed:

```bash theme={null}
kubelet --version
```

Expected output example:

```text theme={null}
Kubernetes v1.26.0
```

Suggested quick reference: package vs purpose

| Package | Purpose | Example command |
| - | - | - |
| `kubeadm` | Bootstrap a Kubernetes cluster | `kubeadm init` / `kubeadm join` |
| `kubelet` | Node agent; runs pods on each node | `kubelet --version` |
| `kubectl` | CLI to interact with the API server | `kubectl get nodes` |

***

## 4) Before initialization: kubectl on control plane

On the control plane, `kubectl` cannot contact the API server until `kubeadm init` completes. Running `kubectl get nodes` before initialization will produce an error similar to:

```text theme={null}
error: couldn't get current server API group list
```

This is expected; proceed to initialize the control plane.

***

## 5) Initialize the control plane with kubeadm (control plane only)

1. Determine the control plane IP on the interface you want to advertise. For example:

```bash theme={null}
ip addr show eth0
# Look for the inet address, e.g., 192.7.220.6/24
```

2. Initialize the control plane, specifying the advertised API server address and the pod network CIDR. Flannel commonly uses `10.244.0.0/16`.

```bash theme={null}
sudo kubeadm init \
  --apiserver-advertise-address=192.7.220.6 \
  --pod-network-cidr=10.244.0.0/16
```

During `kubeadm init` you will see output that includes the join command and instructions to configure `kubectl` for your regular user. Example snippet:

```text theme={null}
Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

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

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
    https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.7.220.6:6443 --token <token> \
    --discovery-token-ca-cert-hash sha256:<hash>
```

Follow the init output to configure `kubectl` for your regular 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
```

Check node status (it may show `NotReady` until the pod network is installed):

```bash theme={null}
kubectl get nodes
```

Example immediate output:

```text theme={null}
NAME           STATUS    ROLES           AGE   VERSION
controlplane   NotReady  control-plane   33s   v1.26.0
```

The `NotReady` state is normal until the network addon pods are running.

***

## 6) Join node01 to the cluster (node01 only)

From the `kubeadm init` output you recorded the `kubeadm join ...` command. On node01 (run as root or with `sudo`) execute that join command. Example:

```bash theme={null}
sudo kubeadm join 192.7.220.6:6443 \
  --token 6cv4ox.ea6gvfim94c9igu \
  --discovery-token-ca-cert-hash sha256:eafde3643008264dbb45640c10ce6079c9a20ee28bc6732b2f404f46a8bff702
```

You should see output confirming the node joined successfully:

```text theme={null}
[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
 * Certificate signing request was sent to apiserver and a response was received.
 * The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
```

Back on the control plane, verify both nodes are visible (the worker may still be `NotReady` until the network is installed):

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

***

## 7) Install a pod network (Flannel) (control plane)

Deploy Flannel using the upstream manifest. This installs the Flannel DaemonSet and related resources into `kube-system`:

```bash theme={null}
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
```

After applying the manifest, wait a moment and verify pods in all namespaces:

```bash theme={null}
kubectl get pods -A
```

You should see Flannel pods in `kube-system` along with CoreDNS and kube-proxy. When Flannel becomes ready, node statuses will transition to `Ready`:

```bash theme={null}
kubectl get nodes
```

Example final output:

```text theme={null}
NAME           STATUS   ROLES           AGE    VERSION
controlplane   Ready    control-plane   10m    v1.26.0
node01         Ready    <none>          8m     v1.26.0
```

***

## Summary

* Configured required sysctl settings on both nodes and disabled swap.
* Added the Kubernetes apt repository and installed `kubeadm`, `kubelet`, and `kubectl` pinned to v1.26.0.
* Initialized the control plane with `kubeadm init`, advertising the control plane IP and specifying the pod network CIDR for Flannel.
* Joined node01 using the `kubeadm join` command produced by `kubeadm init`.
* Deployed Flannel as the pod network and verified nodes reached the `Ready` state.

***

## Troubleshooting & tips

* If nodes remain `NotReady` after installing the pod network, check `kubectl get pods -n kube-system` to see pod states for Flannel/CoreDNS/kube-proxy.
* Verify the `--apiserver-advertise-address` passed to `kubeadm init` is reachable from worker nodes.
* If `kubeadm join` fails due to token expiration, create a new token on the control plane:
  ```bash theme={null}
  sudo kubeadm token create --print-join-command
  ```
* Certificate or time-related errors can often be resolved by synchronizing clocks across nodes (e.g., enable and start `systemd-timesyncd` or `chronyd`).
* If kubelet uses the wrong cgroup driver, ensure container runtime (`containerd`) is configured to use `systemd` cgroup driver and restart the runtime.

***

## Links and references

* Kubernetes documentation: [https://kubernetes.io/docs/](https://kubernetes.io/docs/)
* kubeadm: [https://kubernetes.io/docs/reference/setup-tools/kubeadm/](https://kubernetes.io/docs/reference/setup-tools/kubeadm/)
* Flannel releases / manifest: [https://github.com/flannel-io/flannel/releases](https://github.com/flannel-io/flannel/releases)
* containerd: [https://containerd.io/](https://containerd.io/)

If you run into specific errors, include the exact `kubeadm` output and `kubectl -n kube-system get pods` output so you can be assisted more quickly.

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/cka-certification-course-certified-kubernetes-administrator/module/357c2670-c16c-49ac-aa27-8af52523afde/lesson/25b5216f-8a6f-4e43-ae75-0ea301b47f99" />
</CardGroup>


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