How to Install Kubernetes Cluster on Ubuntu 26.04 LTS Using kubeadm (2026 Guide)

how to install kubernetes cluster on ubuntu 26.04 lts using kubeadm 2026

Table of Contents

  1. What is kubeadm and Why Use It
  2. Ubuntu 26.04 Specifics: What Changed for Kubernetes
  3. Cluster Architecture
  4. Hardware Requirements
  5. Prerequisites: All Nodes
  6. Step 1: Disable Swap
  7. Step 2: Configure Kernel Modules and sysctl
  8. Step 3: Install containerd (Container Runtime)
  9. Step 4: Configure containerd for cgroup v2
  10. Step 5: Install kubeadm, kubelet, and kubectl
  11. Step 6: Initialize the Control Plane (Master Node Only)
  12. Step 7: Install Calico CNI Network Plugin
  13. Step 8: Join Worker Nodes to the Cluster
  14. Step 9: Verify the Cluster
  15. Step 10: Deploy Your First Application
  16. Install Metrics Server
  17. Optional: Install KubeSphere Web UI
  18. Single-Node Cluster: Remove Control Plane Taint
  19. Common Issues and Quick Fixes
  20. Next Steps

Setting up a Kubernetes cluster from scratch on Ubuntu 26.04 requires one non-obvious change: configuring containerd for cgroup v2. Ubuntu 26.04 dropped cgroup v1 entirely with systemd 259, so the old default won’t work. Skip this step and kubelet will fail to start with a cryptic cgroup error that sends you down the wrong rabbit hole.

This guide walks through installing a production-grade Kubernetes 1.36 cluster on Ubuntu 26.04 LTS (Resolute Raccoon) using kubeadm — covering everything from kernel module configuration to joining worker nodes and deploying your first application. Every Ubuntu 26.04-specific gotcha is called out explicitly so you don’t waste hours troubleshooting misconfigurations that aren’t obvious from older guides.

What is kubeadm and Why Use It

kubeadm is the official tool for bootstrapping Kubernetes clusters. It automates the complex setup of the control plane and worker nodes, and is the recommended approach for production-grade clusters that you manage yourself.

kubeadm gives you a real Kubernetes cluster — not a simplified local version like minikube or k3s. The tradeoff: more setup steps than managed alternatives, but full control over every component and no vendor lock-in.

kubeadm vs alternatives:

ToolUse caseComplexityProduction-ready
kubeadmSelf-managed production clusterMedium✅ Yes
k3sLightweight, edge, IoT, single-nodeLow✅ Yes (limited)
minikubeLocal development onlyLow❌ No
KubeKeyKubernetes + KubeSphere togetherMedium✅ Yes
Managed (EKS/GKE/AKS)Cloud-native, no node managementLow (ops)✅ Yes

Use kubeadm when you need a self-managed cluster with full control and you’re comfortable managing the underlying nodes.

Ubuntu 26.04 Specifics: What Changed for Kubernetes

If you’ve installed Kubernetes on Ubuntu 22.04 or 24.04 before, these changes in Ubuntu 26.04 affect the installation:

1. cgroup v2 only — cgroup v1 removed.
Ubuntu 26.04 uses cgroup v2 exclusively. systemd 259 removed cgroup v1 support entirely. containerd must be explicitly configured to use the systemd cgroup driver, or kubelet and containerd will use mismatched drivers and the cluster will fail to initialize.

2. Kubernetes 1.36 is the current stable release (July 2026).
v1.33 is the recommended LTS-supported release for production stability. v1.36 is the latest stable release as of mid-2026. This guide uses 1.36.

3. Old Kubernetes APT repository is removed.
The old repository https://apt.kubernetes.io kubernetes-xenial is deprecated and removed. Use the pkgs.k8s.io repository instead.

4. Calico installation method changed.
The docs.projectcalico.org/manifests/calico.yaml URL is dead. The current recommended approach is the Tigera Operator method, which handles lifecycle management automatically.

Cluster Architecture

This guide sets up a standard kubeadm cluster:

┌─────────────────────────────────┐
│  Control Plane Node (master)    │
│  ├── kube-apiserver             │
│  ├── kube-controller-manager   │
│  ├── kube-scheduler            │
│  ├── etcd                      │
│  └── CoreDNS                   │
└──────────────┬──────────────────┘
               │ cluster network
       ┌───────┴────────┐
       ▼                ▼
┌─────────────┐  ┌─────────────┐
│ Worker Node │  │ Worker Node │
│  ├── kubelet│  │  ├── kubelet│
│  ├── kube-  │  │  ├── kube-  │
│  │  proxy   │  │  │  proxy   │
│  └── Pods   │  │  └── Pods   │
└─────────────┘  └─────────────┘

For a single-node cluster (development/testing), run all steps on one machine and remove the control plane taint in Step 18.

Hardware Requirements

RoleCPURAMStorageCount
Control Plane2+ cores4GB+50GB+1 (or 3 for HA)
Worker Node2+ cores4GB+50GB+1+

Minimum per node: 2 CPUs, 2GB RAM. The control plane alone consumes about 1.5GB RAM — give it at least 4GB for anything beyond a quick test.

All nodes must have:

  • Unique hostname, MAC address, and product UUID
  • Full network connectivity to each other
  • Ubuntu 26.04 LTS installed
  • A user with sudo privileges

Prerequisites: All Nodes

Run all steps in this section on every node — both control plane and worker nodes — unless explicitly noted otherwise.

Set unique hostnames on each node:

# On control plane
sudo hostnamectl set-hostname k8s-master

# On worker 1
sudo hostnamectl set-hostname k8s-worker-1

# On worker 2
sudo hostnamectl set-hostname k8s-worker-2

Add all cluster nodes to /etc/hosts on every node:

sudo nano /etc/hosts
192.168.1.10  k8s-master
192.168.1.11  k8s-worker-1
192.168.1.12  k8s-worker-2

Replace with your actual IP addresses.

Step 1: Disable Swap

Kubernetes requires swap to be disabled. The kubelet cannot accurately determine memory usage when swap is enabled, which leads to unpredictable scheduling and resource management.

# Disable swap immediately
sudo swapoff -a

# Disable swap permanently (comment out swap entry in fstab)
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

Verify swap is off:

swapon --show
# Should return no output (empty = no swap active)

free -h
# Swap line should show: 0B 0B 0B

Step 2: Configure Kernel Modules and sysctl

Kubernetes requires two kernel modules and specific network settings:

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

sudo modprobe overlay
sudo modprobe br_netfilter

Configure sysctl settings for Kubernetes networking:

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

# Apply sysctl settings without reboot
sudo sysctl --system

Verify the settings are applied:

sysctl net.bridge.bridge-nf-call-iptables net.ipv4.ip_forward
# Expected:
# net.bridge.bridge-nf-call-iptables = 1
# net.ipv4.ip_forward = 1

Step 3: Install containerd (Container Runtime)

Kubernetes needs a CRI-compatible container runtime. containerd is the standard choice and ships in Docker’s apt repository with the latest stable builds. For kubeadm clusters, standalone containerd is the cleanest approach.

Note: We install containerd from Docker’s repository (not Ubuntu’s default repo) to get the latest version:

# Install prerequisites
sudo apt update
sudo apt install -y ca-certificates curl gnupg

# Add Docker's GPG key
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add Docker repository
echo \
  "Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc" | \
  sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null

# Install containerd (standalone — not the full Docker CE)
sudo apt update
sudo apt install -y containerd.io

Verify containerd is running:

sudo systemctl status containerd
# Should show: Active: active (running)

Step 4: Configure containerd for cgroup v2

This is the step that trips up most installations on Ubuntu 26.04. The default containerd configuration uses SystemdCgroup = false — which causes a mismatch with Ubuntu 26.04’s cgroup v2 setup:

# Generate default containerd config
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml

Enable SystemdCgroup:

sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

Verify the change was applied:

grep "SystemdCgroup" /etc/containerd/config.toml
# Expected: SystemdCgroup = true

Restart containerd:

sudo systemctl restart containerd
sudo systemctl enable containerd

Verify cgroup v2 is correctly detected:

stat -fc %T /sys/fs/cgroup
# Expected: cgroup2fs
# If it shows tmpfs — cgroup v2 is not active (should not happen on Ubuntu 26.04)

Step 5: Install kubeadm, kubelet, and kubectl

Add the Kubernetes APT repository. Use the version you intend to install — this example uses 1.36:

# Add Kubernetes GPG key
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.36/deb/Release.key | \
  sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
sudo chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg

# Add Kubernetes repository
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.36/deb/ /' | \
  sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo chmod 644 /etc/apt/sources.list.d/kubernetes.list

Install the Kubernetes tools:

sudo apt update
sudo apt install -y kubelet kubeadm kubectl

# Pin the version — prevent accidental upgrades that break the cluster
sudo apt-mark hold kubelet kubeadm kubectl

Verify:

kubeadm version
kubectl version --client

Enable and start kubelet:

sudo systemctl enable --now kubelet

Note: kubelet will restart continuously until kubeadm init is run — this is normal behavior before cluster initialization.

Step 6: Initialize the Control Plane (Master Node Only)

Run this only on the control plane node:

sudo kubeadm init \
  --pod-network-cidr=192.168.0.0/16 \
  --apiserver-advertise-address=<CONTROL-PLANE-IP>

Replace <CONTROL-PLANE-IP> with the actual IP address of your control plane node.

The --pod-network-cidr must match your CNI plugin:

  • Calico: 192.168.0.0/16 (used in this guide)
  • Flannel: 10.244.0.0/16
  • Cilium: 10.0.0.0/8

The initialization takes 2-3 minutes. On success, you’ll see:

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

...

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

kubeadm join 192.168.1.10:6443 --token xxxxx.xxxxxxxxxxxxxxxx \
    --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Save the kubeadm join command — you’ll need it for worker nodes. Tokens expire after 24 hours (see troubleshooting if it expires before you join all nodes).

Configure kubectl for your user:

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

Verify the control plane is running:

kubectl get nodes
# NAME         STATUS     ROLES           AGE   VERSION
# k8s-master   NotReady   control-plane   1m    v1.36.x

NotReady is expected at this point — the node needs a CNI network plugin before it shows Ready.

Step 7: Install Calico CNI Network Plugin

The current recommended Calico installation approach is the Tigera Operator method, which handles lifecycle management automatically.

Install the Tigera Operator:

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.3/manifests/tigera-operator.yaml

Download the custom resources manifest:

curl https://raw.githubusercontent.com/projectcalico/calico/v3.29.3/manifests/custom-resources.yaml -O

Verify the pod CIDR matches what you used in kubeadm init:

cat custom-resources.yaml | grep cidr
# Should show: cidr: 192.168.0.0/16

If your kubeadm init used a different CIDR, update it:

sed -i 's|192.168.0.0/16|YOUR_POD_CIDR|' custom-resources.yaml

Apply the custom resources:

kubectl create -f custom-resources.yaml

Watch Calico pods come up:

watch kubectl get pods -n calico-system

Wait until all pods show Running. This typically takes 2-3 minutes.

Verify the control plane node is now Ready:

kubectl get nodes
# NAME         STATUS   ROLES           AGE   VERSION
# k8s-master   Ready    control-plane   5m    v1.36.x

Step 8: Join Worker Nodes to the Cluster

Run this on each worker node using the kubeadm join command from Step 6:

sudo kubeadm join 192.168.1.10:6443 \
  --token xxxxx.xxxxxxxxxxxxxxxx \
  --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

If the token has expired (>24 hours), generate a new one on the control plane:

# Generate a new token
kubeadm token create --print-join-command
# Outputs the full join command with a fresh token

After joining, verify on the control plane:

kubectl get nodes
# NAME            STATUS   ROLES           AGE   VERSION
# k8s-master      Ready    control-plane   10m   v1.36.x
# k8s-worker-1    Ready    <none>          2m    v1.36.x
# k8s-worker-2    Ready    <none>          1m    v1.36.x

All nodes should show Ready. If a worker shows NotReady, check that containerd is running on that node (sudo systemctl status containerd) and that Steps 1-4 were completed correctly.

Step 9: Verify the Cluster

Check all system pods are running:

kubectl get pods -n kube-system
# All pods should show Running or Completed

Check cluster component health:

kubectl get componentstatuses
# All components should show Healthy

Check cluster info:

kubectl cluster-info
# Kubernetes control plane is running at https://192.168.1.10:6443
# CoreDNS is running at https://192.168.1.10:6443/api/v1/...

Check node resource usage:

kubectl top nodes
# (requires Metrics Server — see next section)

Step 10: Deploy Your First Application

Deploy an Nginx application to verify the cluster can run workloads:

# Create a deployment
kubectl create deployment nginx-test \
  --image=nginx:alpine \
  --replicas=2

# Expose it as a NodePort service
kubectl expose deployment nginx-test \
  --type=NodePort \
  --port=80

# Check deployment status
kubectl get deployments
kubectl get pods
kubectl get services

Get the NodePort assigned:

kubectl get service nginx-test
# NAME         TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
# nginx-test   NodePort   10.96.xxx.xxx   <none>        80:31234/TCP   1m

Access the application at http://<any-node-ip>:31234 — you should see the Nginx welcome page.

Clean up the test:

kubectl delete deployment nginx-test
kubectl delete service nginx-test

Install Metrics Server

kubeadm does not install the Metrics Server component during initialization. It must be installed separately. Without it, kubectl top nodes and kubectl top pods don’t work, and Horizontal Pod Autoscaler has no data to act on.

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

For clusters using self-signed certificates (common in kubeadm setups), patch the Metrics Server to skip TLS verification:

kubectl patch deployment metrics-server \
  -n kube-system \
  --type json \
  -p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'

Wait 60 seconds, then verify:

kubectl top nodes
# NAME            CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
# k8s-master      150m         7%     1800Mi          45%
# k8s-worker-1    80m          4%     900Mi           22%

Optional: Install KubeSphere Web UI

Once the cluster is running, add KubeSphere for a full web-based management interface. KubeSphere installs via Helm on your existing cluster:

helm upgrade --install \
  -n kubesphere-system \
  --create-namespace \
  ks-core \
  https://charts.kubesphere.io/main/ks-core-1.1.3.tgz \
  --debug --wait

Access the KubeSphere console:

kubectl get svc ks-console -n kubesphere-system
# NodePort is typically 30880

Open http://<control-plane-ip>:30880 — login: admin / P@88w0rd (change immediately).

For the full KubeSphere security hardening guide after installation, see our KubeSphere and Kubernetes Security guide.

Single-Node Cluster: Remove Control Plane Taint

By default, Kubernetes prevents workload pods from being scheduled on the control plane node. For a single-node cluster (development/testing), remove this restriction:

kubectl taint nodes --all node-role.kubernetes.io/control-plane-
# node/k8s-master untainted

Now pods can be scheduled on the control plane node, making it a fully functional single-node cluster.

Common Issues and Quick Fixes

SymptomLikely CauseFix
kubelet keeps restarting before initExpected — kubelet loops until init completesNormal behavior — run kubeadm init to complete
kubeadm init fails with cgroup errorSystemdCgroup = false in containerd configSet SystemdCgroup = true in /etc/containerd/config.toml, restart containerd
Node stays in NotReady after initCNI plugin not installedInstall Calico/Flannel CNI — Step 7
CoreDNS pods stuck in PendingNo CNI installed yetInstall CNI first — CoreDNS requires pod networking
Worker fails to join — token expiredBootstrap token only valid 24 hoursRun kubeadm token create --print-join-command on control plane
kubectl top nodes shows error: Metrics API not availableMetrics Server not installedInstall Metrics Server — Step 16
Pods can’t reach each otherbr_netfilter module not loadedRun sudo modprobe br_netfilter and check sysctl settings
kubeadm reset needed (failed init)Partial initializationRun: sudo kubeadm reset -f && sudo rm -rf $HOME/.kube && sudo iptables -F

Reset and retry after a failed init:

sudo kubeadm reset -f
sudo rm -rf $HOME/.kube
sudo iptables -F && sudo iptables -t nat -F && sudo iptables -t mangle -F && sudo iptables -X
# Then re-run kubeadm init from Step 6

Next Steps

With a working Kubernetes cluster on Ubuntu 26.04:

  • Security hardening — a fresh kubeadm cluster has many default insecurities. Our KubeSphere and Kubernetes Security guide covers RBAC, Pod Security Standards, network policies, and secret encryption — implement these before running production workloads.
  • Add monitoring — deploy kube-prometheus-stack to your cluster and integrate with Prometheus + Grafana for cluster-level metrics (node CPU, pod memory, API server latency).
  • Centralized logging — deploy Fluent Bit as a DaemonSet to ship all pod logs to Grafana Loki for centralized log search and alerting.
  • KubeSphere management UI — install KubeSphere (Step 17) for a web-based interface for workload management, multi-tenancy, and DevOps pipelines without writing YAML for every operation.
  • Upgrade Kubernetes — kubeadm supports in-place version upgrades. Always upgrade the control plane first, then worker nodes, one at a time. Never skip minor versions.

(Visited 6 times, 1 visits today)

You may also like