Kubernetes has become the de-facto standard for container orchestration. In this guide, we’ll walk through setting up a production-ready cluster from scratch using kubeadm — no managed services, no shortcuts.
Prerequisites
Before you begin, make sure each node has:
- Ubuntu 22.04 LTS (or similar)
- At least 2 CPUs, 4 GB RAM
swapdisabled- Ports 6443, 10250, and 2379-2380 open
Run a quick sanity check on each node:
sudo swapoff -a
free -h # verify swap is 0
uname -r # should be 5.15+
Installing Container Runtime
We’ll use containerd as the CRI-compatible runtime:
# Install containerd
sudo apt-get update
sudo apt-get install -y containerd
# Generate default 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
sudo systemctl restart containerd
sudo systemctl enable containerd
Bootstrapping with kubeadm
On the control plane node:
sudo kubeadm init \
--pod-network-cidr=10.244.0.0/16 \
--apiserver-advertise-address=<CONTROL_PLANE_IP>
After initialization, set up kubectl:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Verify your cluster is running:
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# control-01 Ready control-plane 2m v1.29.0
Installing Cilium for Networking
Cilium provides eBPF-based networking, observability, and security:
# Install Cilium CLI
CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
curl -L --fail --remote-name-all \
https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-amd64.tar.gz
sudo tar xzvf cilium-linux-amd64.tar.gz -C /usr/local/bin
rm cilium-linux-amd64.tar.gz
# Install Cilium into the cluster
cilium install --version 1.15.0
cilium status --wait
Joining Worker Nodes
On each worker node, run the join command from the kubeadm init output:
sudo kubeadm join <CONTROL_PLANE_IP>:6443 \
--token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH>
Tip: If you lost the join token, regenerate it with:
kubeadm token create --print-join-command
Essential Post-Install Steps
1. Install Metrics Server
# metrics-server.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: metrics-server
namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: metrics-server
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: metrics-server
template:
metadata:
labels:
app: metrics-server
spec:
serviceAccountName: metrics-server
containers:
- name: metrics-server
image: registry.k8s.io/metrics-server/metrics-server:v0.7.0
args:
- --kubelet-insecure-tls
Apply it:
kubectl apply -f metrics-server.yaml
kubectl top nodes # should work after ~60s
2. Storage with Longhorn
For persistent volume management, install Longhorn:
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.6.0/deploy/longhorn.yaml
kubectl -n longhorn-system get pods # wait for all pods to be Running
Useful Links
Wrapping Up
You now have a production-ready Kubernetes cluster with:
- containerd as the container runtime
- Cilium for eBPF-powered networking
- Metrics Server for resource monitoring
- Longhorn for persistent storage
In the next post, we’ll cover setting up ArgoCD for GitOps deployments and Prometheus + Grafana for monitoring. Stay tuned!