πŸ“šBook Signing at KubeCon EU 2026Meet us at Booking.com HQ (Mon 18:30-21:00) & vCluster booth #521 (Tue 24 Mar, 12:30-1:30pm) β€” free book giveaway!RSVP Booking.com Event
Configuration advanced ⏱ 20 minutes K8s 1.28+

kubeadm init: Bootstrap K8s Cluster

Bootstrap a Kubernetes cluster with kubeadm init and join. Control plane setup, worker node joining, pod network installation.

By Luca Berton β€’ β€’ πŸ“– 5 min read

πŸ’‘ Quick Answer: kubeadm init --pod-network-cidr=10.244.0.0/16 bootstraps the control plane. Then install a CNI: kubectl apply -f calico.yaml. Join workers: kubeadm join <cp-ip>:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>. For HA: add --control-plane-endpoint with a load balancer and join additional control planes with --control-plane.

The Problem

Setting up a Kubernetes cluster from scratch requires:

  • Installing container runtime (containerd)
  • Installing kubeadm, kubelet, kubectl
  • Initializing the control plane
  • Configuring networking (CNI)
  • Joining worker nodes
  • Optional: multi-control-plane HA

The Solution

Prerequisites (All Nodes)

# Disable swap (required for kubelet)
swapoff -a
sed -i '/ swap / s/^/#/' /etc/fstab

# Enable required kernel modules
cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter

# Sysctl settings
cat <<EOF | 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
sysctl --system

# Install containerd
apt-get update
apt-get install -y containerd
mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml
# Set SystemdCgroup = true in config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl restart containerd

# Install kubeadm, kubelet, kubectl
apt-get install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list
apt-get update
apt-get install -y kubelet=1.30.0-1.1 kubeadm=1.30.0-1.1 kubectl=1.30.0-1.1
apt-mark hold kubelet kubeadm kubectl

Initialize Control Plane

# Basic init
kubeadm init --pod-network-cidr=10.244.0.0/16

# With configuration file (recommended)
cat <<EOF > kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.30.0
controlPlaneEndpoint: "cp.example.com:6443"  # For HA
networking:
  podSubnet: 10.244.0.0/16
  serviceSubnet: 10.96.0.0/12
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF

kubeadm init --config kubeadm-config.yaml

# Set up kubectl access
mkdir -p $HOME/.kube
cp /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

Install CNI (Pod Network)

# Calico
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml

# Or Flannel
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

# Or Cilium
helm repo add cilium https://helm.cilium.io/
helm install cilium cilium/cilium -n kube-system

# Wait for CNI pods
kubectl get pods -n kube-system -w
# calico-node-xxxxx   1/1   Running   0   30s  (one per node)

# Control plane should now be Ready
kubectl get nodes
# NAME           STATUS   ROLES           VERSION
# control-plane  Ready    control-plane   v1.30.0

Join Worker Nodes

# The kubeadm init output shows the join command:
kubeadm join cp.example.com:6443 \
  --token abcdef.0123456789abcdef \
  --discovery-token-ca-cert-hash sha256:abc123...

# If token expired, generate a new one:
kubeadm token create --print-join-command
# Outputs the full join command

# Verify nodes joined
kubectl get nodes
# NAME           STATUS   ROLES           VERSION
# control-plane  Ready    control-plane   v1.30.0
# worker-1       Ready    <none>          v1.30.0
# worker-2       Ready    <none>          v1.30.0

# Label workers (optional)
kubectl label node worker-1 node-role.kubernetes.io/worker=""
kubectl label node worker-2 node-role.kubernetes.io/worker=""

High Availability (Multi-CP)

# Prerequisites:
# - Load balancer in front of control planes (HAProxy, cloud LB)
# - Same --control-plane-endpoint on all CP nodes

# Init first control plane
kubeadm init \
  --control-plane-endpoint "lb.example.com:6443" \
  --upload-certs \
  --pod-network-cidr=10.244.0.0/16

# Join additional control planes (from init output)
kubeadm join lb.example.com:6443 \
  --token <token> \
  --discovery-token-ca-cert-hash sha256:<hash> \
  --control-plane \
  --certificate-key <cert-key>

# Verify HA
kubectl get nodes
# cp-1   Ready   control-plane   v1.30.0
# cp-2   Ready   control-plane   v1.30.0
# cp-3   Ready   control-plane   v1.30.0

Verify Cluster

# All nodes ready
kubectl get nodes -o wide

# System pods running
kubectl get pods -n kube-system
# coredns, kube-proxy, CNI, etcd, apiserver, controller-manager, scheduler

# Cluster info
kubectl cluster-info

# Run a test pod
kubectl run test --image=nginx:1.27 --rm -it --restart=Never -- curl -s localhost
kubectl delete pod test

# DNS test
kubectl run dnstest --image=busybox:1.36 --rm -it --restart=Never -- nslookup kubernetes

Reset (Start Over)

# On each node
kubeadm reset -f
iptables -F && iptables -t nat -F && iptables -t mangle -F
rm -rf /etc/cni/net.d /var/lib/etcd /etc/kubernetes

# Then kubeadm init again

Common Issues

β€œkubelet is not running” during init

Swap not disabled or containerd not configured with systemd cgroup. Check: journalctl -u kubelet.

Nodes stuck NotReady

CNI not installed. Install Calico/Flannel/Cilium after kubeadm init.

Token expired for join

Tokens expire after 24h. Generate new: kubeadm token create --print-join-command.

API server not reachable from workers

Firewall blocking port 6443. Open: 6443 (apiserver), 2379-2380 (etcd), 10250 (kubelet), 10259 (scheduler), 10257 (controller-manager).

Best Practices

  • Use configuration file over CLI flags β€” version-controlled and reproducible
  • Pin versions β€” apt-mark hold prevents accidental upgrades
  • HA with 3+ control planes β€” odd number for etcd quorum
  • Back up certificates β€” /etc/kubernetes/pki/ after init
  • Document the join command β€” or use kubeadm token create later

Key Takeaways

  • kubeadm init bootstraps the control plane; kubeadm join adds nodes
  • Must install CNI plugin after init β€” nodes stay NotReady without it
  • Disable swap, enable kernel modules, install containerd before kubeadm
  • For HA: load balancer + --control-plane-endpoint + 3 control planes
  • CKA exam tests the full initβ†’joinβ†’verify workflow
#kubeadm #cluster-setup #installation #administration #cka
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens