Skip to main content

Command Palette

Search for a command to run...

Part 3: Installing Kubernetes and Bootstrapping the Cluster with kubeadm (Including Calico Network plugin)

Updated
8 min read
Part 3: Installing Kubernetes and Bootstrapping the Cluster with kubeadm (Including Calico Network plugin)

Why this part matters

In the previous parts, we prepared the operating system and installed the container runtime. Now we move to the core objective:

Bootstrapping a working Kubernetes cluster.

This article will take you from:

  • A prepared Linux node

    to

  • A functioning Kubernetes control plane

    to

  • A multi-node cluster with networking enabled.

We will use kubeadm, which is the official and recommended way to bootstrap Kubernetes clusters manually.

What we will do

In this part, we will:

  • Install Kubernetes components (kubeadm, kubelet, kubectl)

  • Enable kubelet

  • Initialize the control plane

  • Configure kubectl access

  • Install Calico network plugin

  • Join worker nodes

  • Verify cluster health

By the end of this article, your cluster will be fully operational.

Previous Parts in This Series

If you haven’t completed the earlier parts, follow them in order before proceeding.


Installing Kubernetes Components

Prerequisite: Trust Kubernetes repositories

This step is a bit different on Ubuntu/Debian distros and RHEL based distros as Debian uses AppArmor and RHEL family uses SELinux.

Please follow the appropriate section (A) or (B) below:


A. Update apt index and trust k8s repo gpg key (Only for Ubuntu and Debian flavors)

  1. Update the apt package index and install packages needed to use the Kubernetes apt repository:
sudo apt-get update

# apt-transport-https may be a dummy package; if so, you can skip that package

sudo apt-get install -y apt-transport-https ca-certificates curl gpg
  1. Download the public signing key for the Kubernetes package repositories. The same signing key is used for all repositories so you can disregard the version in the URL:
# If the directory `/etc/apt/keyrings` does not exist, it should be created before the curl command, read the note below.

# sudo mkdir -p -m 755 /etc/apt/keyrings

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.35/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

Note:

In releases older than Debian 12 and Ubuntu 22.04, directory /etc/apt/keyrings does not exist by default, and it should be created before the curl command.


B. Set SELinux to Permissive (Only on RHEL flavors)

These instructions are valid for all Kubernetes versions till 1.35 (latest at the time of writing this article).

# Set SELinux in permissive mode (effectively disabling enforcement)

sudo setenforce 0

# Make it persistent so that nodes do not break upon restart

sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

Why is this important?

As per official documentation:

  • Setting SELinux in permissive mode by running setenforce 0 and sed ... effectively disables it. This is required to allow containers to access the host filesystem; for example, some cluster network plugins require that. You have to do this until SELinux support is improved in the kubelet.

  • You can leave SELinux enabled if you know how to configure it, but it may require settings that are not supported by kubeadm.

1. Add the Kubernetes Repository

Ubuntu/Debian distros:

echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list

Note: For other Kubernetes minor versions, you need to change the Kubernetes minor version in the URL to match your desired minor version


RHEL family distros:

cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.33/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.33/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF

2. Install kubelet, kubeadm and kubectl

Ubuntu/Debian based distros:

sudo apt-get update

sudo apt-get install -y kubelet kubeadm kubectl

sudo apt-mark hold kubelet kubeadm kubectl

This will update apt index to include k8s repository added in Step 1, install kubernetes components and pin their versions to exclude them from auto-update when running apt upgrade.


RHEL family:

sudo dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes

Note: You can run above command with yum also.

This will install kubernetes components and exclude them from automatic updates during dnf upgrade or yum upgrade.


Bonus Tip: offline installation

If downloading .deb or .rpm for offline installation, then run:

Ubuntu: sudo apt install --download-only kubeadm kubelet kubectl

.deb files will be saved to /var/cache/apt/archives/

RHEL: sudo dnf install --downloadonly --downloaddir=/path/to/save kubeadm kubelet kubectl


Initializing the Control Plane

Prepare a configuration file:

kubeadm-config.yaml

apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: 1.33.0
#imageRepository: myrepository.in/k8s # Use only with private registry
networking:
    dnsDomain: cluster.local
    serviceSubnet: 10.96.0.0/12 # Calico subnet
    podSubnet: 192.168.0.0/16 # Calico subnet

✨Run init command:

kubeadm init --config kubeadm-config.yaml

What happens during kubeadm init :

kubeadm will:

  • Generate certificates

  • Start control plane components

  • Create the cluster configuration

  • Generate a join token for worker nodes

When it completes successfully, you will see a kubeadm join command printed in the output. Save it.

Congratulations! You have a successfully kickstarted your kubernetes cluster 🎉


Configure kubectl access

kubectl is the official command-line tool for interacting with the Kubernetes API, allowing users to deploy applications, manage cluster resources, and monitor logs.

After initialization, configure kubectl:

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

Verify:

kubectl get nodes

You should see the control-plane node in NotReady state.

This is expected — networking is not installed yet.


Installing Calico as Networking Plugin (Critical Step)

Before joining worker nodes, we have to install a CNI plugin into the cluster.

A CNI (Container Network Interface) plugin is a CNCF project consisting of executable binaries that configure network interfaces for Linux containers, handling IP address allocation, network attachment, and resource cleanup. Popular options like Calico and Cilium enable pod-to-pod communication, network policies, and traffic management in Kubernetes.

In this guide, we use Calico.

  1. Download the Calico networking manifest for the Kubernetes API datastore.

    curl https://raw.githubusercontent.com/projectcalico/calico/v3.31.4/manifests/calico.yaml -O
    
  2. Apply the manifest using the following command.

    kubectl apply -f calico.yaml
    

Why install Calico before joining workers?

Without a CNI plugin:

  • Nodes remain NotReady

  • Pods cannot receive IP addresses

  • Scheduling behavior becomes inconsistent

  • Services may not function correctly

Installing Calico ensures:

  • Pod-to-pod networking works

  • Nodes transition to Ready

  • Cluster behaves predictably

Verify Calico Installation

Check system pods:

kubectl get pods -n kube-system

Wait until:

  • Calico pods are running

  • CoreDNS is running

  • Control plane components are healthy


Tip: In case CoreDNS is stuck in pending or CrashLoopBackOff

  • Check logs: kubectl logs -f <coredns pod name> -n kube-system, it may show issues like ...AA record not found..., ...plugin not found...

  • Run: kubectl edit configmap coredns -n kube-system

This will open CoreDNS configmap editor.

  • Comment the forward block

  • Exit - Use Vim like controls (Esc followed by :wq )


Then verify:

kubectl get nodes

Your control-plane node should now show:

Ready

Do not proceed until the node is Ready.


Joining Worker Nodes

On the control-plane node, generate the join command if needed:

kubeadm token create --print-join-command

Copy the output.


Run on each worker node

  • Before adding nodes, make sure your worker node is also configured as per Part 1 and 2 of this series.

  • You can restrict your cluster to one worker also for learning purpose, that is absolutely fine given that your worker node has enough resources to run all the expected workloads.

On every worker node:

kubeadm join <MASTER-IP>:6443 --token <TOKEN> --discovery-token-ca-cert-hash sha256:<HASH>

If successful, the node will join the cluster.


Verify Worker Nodes

Back on the control-plane node:

kubectl get nodes

You should see:

  • Control plane → Ready

  • Worker nodes → Ready

It may take a few minutes for workers to transition to Ready.


Basic Cluster Health Checks

Run:

kubectl get pods -A

Verify:

  • kube-system pods are running

  • No CrashLoopBackOff states

  • Networking pods are healthy


Common Issues to Watch For

  • kubelet failing due to cgroup mismatch

  • Calico pods stuck in pending

  • Nodes stuck in NotReady (usually networking related)

  • Incorrect token or expired join command

If nodes do not become Ready, check:

journalctl -u kubelet -f

What’s Next

Your Kubernetes cluster is now:

  • Bootstrapped with kubeadm

  • Running with containerd

  • Networked using Calico

  • Expanded with worker nodes

In part 4, we will focus on DNS behavior and CoreDNS adjustments to ensure reliable service resolution inside the cluster.