Kubeadm Cluster Setup
Installing a production-grade multi-node Kubernetes cluster from scratch using kubeadm — the official cluster bootstrapping tool. This is the standard method for self-managed clusters on VMs, bare metal, or private cloud, and a core topic in the CKA exam (~25% weight). Source: CKA Day 27
Installation Options Landscape
Kubernetes can be installed in many ways depending on your environment and control requirements:
| Category | Tools | Best For |
|---|---|---|
| Local / Learning | Kind, Minikube, K3s | POCs, CI/CD, CKA practice without cloud cost |
| Managed Cloud | EKS (AWS), AKS (Azure), GKE (GCP) | Production where the cloud provider manages the control plane |
| Self-Managed VMs | kubeadm + cloud VMs (EC2, Azure VM, GCP CE) | Full control, on-premise, cost optimization, CKA preparation |
| Self-Managed Bare Metal | kubeadm + physical servers | Data centers, edge deployments, air-gapped environments |
When to choose kubeadm: You need full control over control plane components, upgrades, etcd, and certificates. The CKA exam assumes this path.
Architecture Overview
┌─────────────────────────────────────────────────────────────┐
│ CONTROL PLANE NODE │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ kube-apiserver│ │kube-scheduler│ │kube-controller-│ │
│ │ :6443 │ │ :10259 │ │ manager │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ etcd (:2379-2380) │ │
│ └──────────────────────────────────────────────────────┘ │
│ ▲ │
│ kubeadm init │
└─────────────────────────────────────────────────────────────┘
│
join token (6443)
│
┌────────┴────────────────────────────────────────────────────┐
│ WORKER NODE 1 │ WORKER NODE 2 │
│ ┌─────────────┐ ┌─────────────┐ │ ┌─────────────┐ │
│ │ kubelet │ │ kube-proxy │ │ │ kubelet │ │
│ │ :10250 │ │ :10256 │ │ │ :10250 │ │
│ └─────────────┘ └─────────────┘ │ └─────────────┘ │
│ ▲ │ ▲ │
│ kubeadm join │ kubeadm join │
└─────────────────────────────────────────────────────────────┘
Prerequisites
Hardware Requirements (Minimum)
| Node Type | RAM | vCPU | Storage |
|---|---|---|---|
| Control plane | 2 GB | 2 | 20 GB |
| Worker | 1 GB | 1 | 20 GB |
For production, double or triple these values.
Network Requirements
All nodes must have:
- Full ** Layer 2 or Layer 3 connectivity** between each other
- Unique hostname, MAC address, and product_uuid per node
- Disabled swap (kubeadm refuses to proceed if swap is active)
- Internet access (for downloading container images) OR pre-loaded images in air-gapped environments
Required Ports
Control Plane Node:
| Port | Protocol | Component | Notes |
|---|---|---|---|
| 6443 | TCP | kube-apiserver | Must be reachable from all worker nodes and admin clients |
| 2379–2380 | TCP | etcd client & peer | Internal to control plane only; never expose publicly |
| 10248–10260 | TCP | Control plane components | Scheduler (10259), controller-manager (10257), kubelet (10248/10250) |
| 22 | TCP | SSH | Admin access only; restrict by IP |
Worker Nodes:
| Port | Protocol | Component | Notes |
|---|---|---|---|
| 10250 | TCP | kubelet API | Reachable from control plane |
| 10256 | TCP | kube-proxy | Health/metrics endpoint |
| 30000–32767 | TCP | NodePort services | Exposes Pods externally via Services |
| 22 | TCP | SSH | Admin access only |
CKA Exam Trap: The official Kubernetes docs list control plane and worker ports separately. Remember that etcd (2379–2380) is control-plane-only and NodePort range (30000–32767) is worker-only.
Common Steps on ALL Nodes
These steps run identically on the control plane and every worker node.
1. Disable Swap
Kubernetes requires swap to be off. kubeadm will fail preflight checks otherwise.
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab2. Load Kernel Modules & Set Sysctl Parameters
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
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
sudo sysctl --systemoverlay— required for containerd overlay filesystem driverbr_netfilter— enables iptables filtering on Linux bridgesnet.ipv4.ip_forward— allows IP forwarding for Pod networking
3. Install containerd (Container Runtime)
Since Kubernetes 1.24, Docker shim has been removed. containerd is the default and recommended runtime.
# Download containerd binary
wget https://github.com/containerd/containerd/releases/download/v1.7.4/containerd-1.7.4-linux-amd64.tar.gz
sudo tar Cxzvf /usr/local containerd-1.7.4-linux-amd64.tar.gz
# Install systemd service
sudo wget -O /etc/systemd/system/containerd.service \
https://raw.githubusercontent.com/containerd/containerd/main/containerd.service
# Generate default config and enable systemd cgroup driver
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# Start service
sudo systemctl daemon-reload
sudo systemctl enable --now containerd
sudo systemctl status containerd4. Install runc
wget https://github.com/opencontainers/runc/releases/download/v1.1.9/runc.amd64
sudo install -m 755 runc.amd64 /usr/local/sbin/runc5. Install CNI Plugins
wget https://github.com/containernetworking/plugins/releases/download/v1.3.0/cni-plugins-linux-amd64-v1.3.0.tgz
sudo mkdir -p /opt/cni/bin
sudo tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.3.0.tgz6. Install kubeadm, kubelet, and kubectl
# Add Kubernetes apt repository
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | \
sudo 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/ /' | \
sudo tee /etc/apt/sources.list.d/kubernetes.list
# Install and pin versions
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectlVersion alignment: Keep kubeadm, kubelet, and kubectl at the same minor version as the cluster. Mixing 1.30 control plane with 1.28 kubelet can cause compatibility issues.
7. Configure crictl (Optional but Recommended)
sudo crictl config runtime-endpoint unix:///var/run/containerd/containerd.sock
sudo chmod -R 775 /var/run/containerdControl Plane Initialization
kubeadm init
sudo kubeadm init \
--pod-network-cidr=192.168.0.0/16 \
--apiserver-advertise-address=<CONTROL_PLANE_PRIVATE_IP> \
--node-name=masterWhat happens during kubeadm init:
- Preflight checks (swap disabled, ports free, container runtime healthy)
- Generates a self-signed CA and all cluster certificates (stored in
/etc/kubernetes/pki) - Writes Static Pod manifests to
/etc/kubernetes/manifestsfor:kube-apiserverkube-controller-managerkube-scheduleretcd
- The kubelet (already running as a systemd service) reads these manifests and starts the containers
- Configures RBAC defaults, bootstrap tokens, and kubelet authentication
- Outputs a
kubeadm joincommand with a token and CA cert hash
Configure kubectl
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/configThis copies the admin kubeconfig into the default kubectl search path. See Kubernetes Kubeconfig for context switching patterns.
Install CNI (Calico)
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/tigera-operator.yaml
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/custom-resources.yamlWait for all pods:
kubectl get pods -n calico-system -w
kubectl get nodesPod CIDR Alignment Trap: Calico’s default IP pool is
192.168.0.0/16. If you pass--pod-network-cidr=10.244.0.0/16tokubeadm init, Calico will fail to reconcile and CoreDNS will hang inContainerCreating. The fix iskubeadm resetand re-initializing with the correct CIDR.
Join Worker Nodes
On each worker, after completing the Common Steps, run the join command printed by kubeadm init:
sudo kubeadm join <CONTROL_PLANE_IP>:6443 \
--token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH>Regenerating a Lost Join Command
# On control plane
kubeadm token create --print-join-commandVerify Cluster
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# master Ready control-plane 10m v1.30.2
# worker-1 Ready <none> 3m v1.30.2
# worker-2 Ready <none> 1m v1.30.2kubeadm Commands Reference
| Command | Purpose |
|---|---|
kubeadm init | Bootstraps a control plane node |
kubeadm join | Adds a worker node to an existing cluster |
kubeadm reset | Tears down kubeadm state on a node (containers, manifests, certificates) |
kubeadm token create --print-join-command | Generates a fresh join token and command |
kubeadm certs check-expiration | Lists certificate expiry dates |
kubeadm certs renew all | Renews all cluster certificates |
kubeadm upgrade plan | Shows available upgrade paths |
kubeadm upgrade apply v1.31.0 | Performs a cluster upgrade |
Troubleshooting Matrix
| Symptom | Root Cause | Fix |
|---|---|---|
kubeadm init fails preflight | Swap is enabled | sudo swapoff -a and comment out /etc/fstab swap line |
Node stays NotReady | CNI not installed or misconfigured | Install matching CNI; check kubectl get pods -n kube-system |
CoreDNS ContainerCreating indefinitely | Calico IP pool mismatch with --pod-network-cidr | kubeadm reset → re-run kubeadm init with correct CIDR |
kubeadm join hangs / times out | Security group blocks 6443 or worker → master routing broken | Verify TCP 6443 reachable from worker; check VPC routing tables |
crictl ps permission denied | containerd socket lacks permissions | sudo chmod -R 775 /var/run/containerd |
| kubectl fails on worker node | No kubeconfig present | Copy admin.conf from control plane to ~/.kube/config |
| Control plane pods crashloop | Certificate expired | kubeadm certs renew all and restart static pods |
Production Hardening Checklist
- Restrict etcd ports to internal VPC only (never public)
- Restrict SSH (22) to bastion/jump host IP ranges
- Use specific versions for kubeadm/kubelet/kubectl; avoid “latest”
- Pin packages with
apt-mark holdto prevent accidental upgrades - Back up certificates in
/etc/kubernetes/pkibefore any change - Enable audit logging on the API server
- Set up HA control plane (3+ control plane nodes + stacked or external etcd) for production
- Regular certificate monitoring — kubeadm certs expire after 1 year by default
CKA Exam Patterns
- Bootstrap a cluster: Given 3 VMs, install containerd, kubeadm, kubelet, run
kubeadm init, install CNI, join workers - Port knowledge: Know which ports are control plane vs worker vs internal-only
- Troubleshoot join failures: Check firewall, token validity, CA hash correctness
- Certificate management: Renew expired certs with
kubeadm certs renew all - Upgrade a cluster:
kubeadm upgrade plan→ drain node → upgrade packages →kubeadm upgrade apply - Know the default runtime: containerd (not Docker) since Kubernetes 1.24
See Also
- Kubernetes Architecture — How kubeadm uses Static Pod manifests to bootstrap the control plane
- Kubernetes Static Pods — The node-local mechanism that starts control plane containers before the API server exists
- Kind Cluster Setup — Local dev alternative; kubeadm is the production equivalent
- Kubernetes Kubeconfig — Context switching, certificate auth, and the
admin.conffile - Kubernetes Network Policies — Calico CNI installation and CNI selection guidance
- Kubernetes Taints and Tolerations — Built-in
node-role.kubernetes.io/control-plane:NoScheduletaint added by kubeadm - TLS Fundamentals — Certificate chain, CA, and PKI concepts underlying kubeadm’s cert generation
- CKA Day 21 — Manage TLS Certificates In a Kubernetes Cluster — kubeadm PKI and CertificateSigningRequests
Tags: kubernetes kubeadm cluster-installation cka production containerd calico cni devops certificates