Kubernetes looks complicated until you break it down. I'll walk you through a working cluster setup that you can run in production, starting from absolute zero. No experience required.
What is Kubernetes and why does it matter
Kubernetes orchestrates containers. That's it. Instead of manually starting containers on different servers and restarting them when they crash, Kubernetes does it automatically. You describe what you want running, and the system keeps it that way.
A cluster is a group of machines working together. One or more control plane nodes manage the cluster. Worker nodes run your actual applications. Each application runs inside a pod, which is one or more containers that share networking and storage.
The control plane makes scheduling decisions, tracks cluster state, and responds to events. The kubelet agent on each worker node talks to the control plane and manages containers locally. Everything communicates through the API server.
You'll interact with your cluster using kubectl, a command-line tool that sends instructions to the API server.
Step 1: Install container runtime and kubeadm
Every node needs a container runtime. Kubernetes stopped supporting Docker directly; containerd is now the standard choice. Install it on every machine that will join your cluster.
On Ubuntu or Debian:
sudo apt-get update
sudo apt-get install -y containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
sudo systemctl restart containerd
sudo systemctl enable containerd
Next, add the Kubernetes package repository and install three tools:
sudo apt-get install -y apt-transport-https ca-certificates curl
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/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.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
kubeadm bootstraps the cluster. kubelet runs on every node and manages containers. kubectl is your command-line interface.
The apt-mark hold line prevents accidental upgrades that could break your cluster. Version mismatches between nodes cause weird failures.
Disable swap on all nodes—Kubernetes requires it:
sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
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
Set sysctl parameters:
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 --system
These network settings allow containers to communicate across nodes.
Step 2: Initialize the control plane
Pick one machine to be your first control plane node. Run this command there:
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
The --pod-network-cidr flag reserves an IP range for pod networking. This specific range matches the default for Flannel, a popular network plugin we'll install shortly.
The init process takes a few minutes. It generates certificates, starts control plane components, and configures the API server. When it finishes, you'll see a join command that looks like this:
kubeadm join 192.168.1.10:6443 --token abc123.def456ghi789 \
--discovery-token-ca-cert-hash sha256:1234567890abcdef...
Save that command. You'll run it on every worker node.
Before anything works, 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
Now test it:
kubectl get nodes
You'll see one node in NotReady state. That's expected—pods can't run until you install a network plugin.
Step 3: Deploy pod networking
Pods need to communicate across nodes. A CNI plugin (Container Network Interface) creates a virtual network for your cluster. Without it, nothing runs.
Flannel is simple and reliable for most setups:
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
Watch the system pods start:
kubectl get pods -n kube-system -w
After a minute, everything should show Running. Check your node status again:
kubectl get nodes
The control plane node now shows Ready. Your cluster works.
Alternatives to Flannel include Calico (more features, steeper learning curve) and Cilium (eBPF-based, excellent observability). For your first cluster, stick with Flannel.
Step 4: Add worker nodes
Worker nodes run your applications. The control plane makes decisions; workers execute them.
On each worker machine, install containerd and the Kubernetes tools exactly as you did in step 1. Then run the join command you saved earlier:
sudo kubeadm join 192.168.1.10:6443 --token abc123.def456ghi789 \
--discovery-token-ca-cert-hash sha256:1234567890abcdef...
The join command includes a token that expires after 24 hours. If your token expired, generate a new one on the control plane:
kubeadm token create --print-join-command
After joining, verify the node from your control plane:
kubectl get nodes
Each worker takes about a minute to become Ready while the kubelet starts and registers with the control plane.
You need at least one worker node for production. Three gives you redundancy—if one fails, workloads reschedule automatically to the others.
What about actually running something
You've built infrastructure. Now test it with a real workload.
Create a simple nginx deployment:
kubectl create deployment nginx --image=nginx:latest --replicas=3
This tells Kubernetes to run three nginx pods. Watch them start:
kubectl get pods -o wide
The -o wide flag shows which node each pod landed on. Kubernetes automatically spreads them across available workers.
Expose the deployment with a service:
kubectl expose deployment nginx --port=80 --type=NodePort
Find the assigned port:
kubectl get service nginx
You'll see something like 80:30123/TCP. That means nginx is accessible on port 30123 of any node IP. Test it:
curl http://<any-node-ip>:30123
You should get the nginx welcome page.
To remove the test deployment:
kubectl delete deployment nginx
kubectl delete service nginx
Step 5: Secure and prepare for production
A working cluster isn't a production cluster. You need to lock down access, set resource limits, and plan for failures.
Control access with RBAC
The admin config you copied earlier gives unrestricted access. Create limited accounts for applications and team members.
Kubernetes uses Role-Based Access Control. You define roles with specific permissions, then bind them to users or service accounts.
Here's a read-only role:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: developer
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
Save that to rbac.yaml and apply it:
kubectl apply -f rbac.yaml
Never give cluster-admin rights to applications.
Set resource requests and limits
Without limits, a single pod can starve everything else. Every production deployment should specify CPU and memory:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:latest
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "500m"
Requests tell the scheduler how much the pod needs. Limits cap what it can use. The scheduler won't place a pod on a node that can't meet its requests.
Back up etcd regularly
Your cluster state lives in etcd, a key-value store running on the control plane. Lose etcd and you lose everything.
Snapshot it regularly:
sudo ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-snapshot.db \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
Automate this with a cron job and copy snapshots off the cluster.
Monitor control plane certificates
Kubernetes generates certificates that expire after one year. Check expiration dates:
sudo kubeadm certs check-expiration
Renew before they expire:
sudo kubeadm certs renew all
After renewal, restart control plane pods:
sudo systemctl restart kubelet
Use a load balancer for high availability
A single control plane is a single point of failure. Production clusters should run three control plane nodes behind a load balancer.
Initialize the cluster with the load balancer address:
sudo kubeadm init --control-plane-endpoint "loadbalancer.example.com:6443" --upload-certs
Then join additional control plane nodes using the special join command that kubeadm prints.
Common problems and fixes
Pods stuck in Pending
Check if the scheduler can find a suitable node:
kubectl describe pod <pod-name>
Look at the Events section. Common causes: insufficient CPU or memory, no nodes match the pod's requirements, or the network plugin isn't running.
Node shows NotReady
The kubelet might have stopped:
sudo systemctl status kubelet
sudo journalctl -u kubelet -f
Restart it if needed:
sudo systemctl restart kubelet
Can't connect to the API server
Verify the control plane pods are running:
sudo crictl ps
If API server pods keep restarting, check certificate validity and ensure etcd is healthy.
DNS resolution fails inside pods
Kubernetes runs CoreDNS for cluster DNS. Check if it's working:
kubectl get pods -n kube-system -l k8s-app=kube-dns
If CoreDNS pods are failing, check CNI plugin status and node resources.
What to check before going live
You built a cluster. Here's your pre-launch checklist:
- Run three control plane nodes with a load balancer.
- Set resource requests and limits on every deployment.
- Configure RBAC and remove cluster-admin access from applications.
- Automate etcd backups and test restoration.
- Monitor certificate expiration dates.
- Document your pod network CIDR and service CIDR ranges.
- Set up monitoring (Prometheus and Grafana are standard).
- Test node failure scenarios—kill a node and verify workloads reschedule.
Kubernetes is now a skill you have. Start small, deploy real applications, and learn from what breaks. The first cluster teaches you more than any documentation.
