Setting up Kubernetes with containerd
Complete step-by-step guide to set up a Kubernetes cluster using kubeadm with containerd as the container runtime. Learn installation, configuration, and validation for production-ready clusters.
Since Kubernetes v1.24, containerd is the recommended and default container runtime. This guide walks you through setting up a Kubernetes cluster using kubeadm with containerd as the runtime. You'll learn how to install and configure containerd, set up kubeadm, and initialize a production-ready cluster.
Using containerd with Kubernetes provides better performance, lower resource usage, and tighter integration compared to Docker. It's the runtime used by all major cloud providers (AWS EKS, Google GKE, Azure AKS) and is the most battle-tested runtime for Kubernetes.
- Linux server: Ubuntu 20.04+, Debian 11+, CentOS 8+, or RHEL 8+
- Minimum resources: 2 CPU cores, 2GB RAM, 20GB disk space
- Network: Internet access for pulling images, open ports (6443, 10250, etc.)
- User: Root or user with sudo privileges
- Hostname: Unique hostname (set with `hostnamectl set-hostname`)
First, install containerd on all nodes (control plane and workers). Here are the installation steps for Ubuntu/Debian and CentOS/RHEL.
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install -y containerd
# CentOS/RHEL
sudo yum install -y containerd
# Create default configuration
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
# Enable and start containerd
sudo systemctl enable containerd
sudo systemctl start containerd
# Verify installation
sudo systemctl status containerd
containerd --version
containerd needs specific configuration to work with Kubernetes. The key settings are: using systemd cgroups (for better resource management), setting the pause image (sandbox), and configuring the CRI plugin.
# Edit containerd config
sudo nano /etc/containerd/config.toml
# Key settings for Kubernetes:
[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.k8s.io/pause:3.9"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
# Apply configuration
sudo systemctl restart containerd
Install the Kubernetes components on all nodes. kubeadm is the tool for cluster initialization. kubelet runs on each node and manages containers. kubectl is the command-line tool for interacting with the cluster.
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/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.29/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
# CentOS/RHEL
cat <
On the control plane node, initialize the cluster using kubeadm. This sets up the API server, scheduler, controller manager, and etcd.
# Initialize the cluster
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
# The output will show the join command for worker nodes:
# kubeadm join :6443 --token --discovery-token-ca-cert-hash sha256:
# Set up 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 cluster status
kubectl get nodes
kubectl get pods -n kube-system
A CNI (Container Network Interface) plugin is required for pod networking. Without it, pods won't be able to communicate with each other. Here are the most popular CNI plugins:
# Flannel (simple and popular)
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
# Calico (advanced networking policies)
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27/manifests/calico.yaml
# Weave Net
kubectl apply -f https://github.com/weaveworks/weave/releases/download/v2.8.1/weave-daemonset-k8s.yaml
# Verify CNI is working
kubectl get pods -n kube-system
# All pods should be Running
On each worker node, run the kubeadm join command from the initialization output. This registers the node with the cluster and starts the kubelet.
# On worker nodes:
sudo kubeadm join :6443 --token --discovery-token-ca-cert-hash sha256:
# Check if join was successful (on control plane)
kubectl get nodes
# Should show all nodes with Ready status
After setup, verify everything is working correctly.
# Check node status
kubectl get nodes
# Check all system pods
kubectl get pods -n kube-system
# Deploy a test pod
kubectl run test-pod --image=nginx:alpine --restart=Never
# Verify the pod is running
kubectl get pods
# Test networking
kubectl run busybox --image=busybox --rm -it --restart=Never -- nslookup kubernetes
# Delete test pod
kubectl delete pod test-pod
# Issue: Nodes not ready (CNI not installed)
# Solution: Install CNI plugin (see Step 5)
# Issue: containerd not starting
sudo journalctl -u containerd -f
# Issue: kubelet not starting
sudo journalctl -u kubelet -f
# Issue: Token expired
# Generate new token on control plane
kubeadm token create --print-join-command
# Issue: Port conflicts
# Check if ports are already in use
sudo netstat -tulpn | grep 6443
# Reset cluster (if needed)
sudo kubeadm reset
sudo rm -rf /etc/kubernetes/
sudo rm -rf ~/.kube/
sudo systemctl restart containerd
Setting up Kubernetes with containerd is the modern way to build Kubernetes clusters. Follow this guide to get a production-ready cluster up and running.