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)
- Update the
aptpackage index and install packages needed to use the Kubernetesaptrepository:
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
- 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/keyringsdoes 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 0andsed ...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.
Download the Calico networking manifest for the Kubernetes API datastore.
curl https://raw.githubusercontent.com/projectcalico/calico/v3.31.4/manifests/calico.yaml -OApply the manifest using the following command.
kubectl apply -f calico.yaml
Why install Calico before joining workers?
Without a CNI plugin:
Nodes remain
NotReadyPods cannot receive IP addresses
Scheduling behavior becomes inconsistent
Services may not function correctly
Installing Calico ensures:
Pod-to-pod networking works
Nodes transition to
ReadyCluster 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
forwardblockExit - Use Vim like controls (
Escfollowed 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.






